Add feature to get the signature representation of a Python function argument

This commit is contained in:
2026-04-13 12:19:08 +02:00
parent 10707d680d
commit a8626418da
4 changed files with 86 additions and 8 deletions
+1
View File
@@ -19,3 +19,4 @@
- Add classes for Python function arguments - Add classes for Python function arguments
- Add classes for Python definitions - Add classes for Python definitions
- Add class for Python modules - Add class for Python modules
- Add feature to get the signature representation of a Python function argument
+29 -1
View File
@@ -17,7 +17,7 @@ from enum import Enum
from typing import Union from typing import Union
import ast import ast
from ...utils import assert_that_is_instance from ...utils import assert_that_is_instance
from ...exceptions import InvalidPythonIdentifierError, InvalidPythonAnnotationError from ...exceptions import InvalidPythonIdentifierError, InvalidPythonAnnotationError, PythonArgumentStructureError
import keyword import keyword
import types import types
@@ -69,6 +69,9 @@ class PythonFunctionArgument:
self.default = default self.default = default
self.annotation = annotation self.annotation = annotation
if self.kind in (PythonArgumentKind.VARARG, PythonArgumentKind.KWARGS) and self.default is not None:
raise PythonArgumentStructureError('* or ** arguments cannot have default values', definition = self.signature_repr())
def __eq__(self, value: PythonFunctionArgument) -> bool: def __eq__(self, value: PythonFunctionArgument) -> bool:
if not isinstance(value, PythonFunctionArgument): if not isinstance(value, PythonFunctionArgument):
return False return False
@@ -82,6 +85,31 @@ class PythonFunctionArgument:
self.annotation self.annotation
)) ))
def signature_repr(self) -> str:
'''
Returns the function signature representation of the argument.
:return: The string representation of the argument.
:rtype: str
'''
if self.kind == PythonArgumentKind.KWARGS:
str_repr = '**'
elif self.kind == PythonArgumentKind.VARARG:
str_repr = '*'
else:
str_repr = ''
str_repr += self.name
if self.annotation is not None:
str_repr += ': ' + self.annotation
if self.default is not None:
str_repr += ' = ' + self.default
return str_repr
class PythonASTArgumentsListParser: class PythonASTArgumentsListParser:
''' '''
A parser for making ``PythonFunctionArgument`` objects from an A parser for making ``PythonFunctionArgument`` objects from an
+16
View File
@@ -61,3 +61,19 @@ class PythonAnnotationError(ValueError):
return f'{self.args[0]}{": " if self.annotation and self.args[0] else ""}{self.annotation if self.args[0] else ""}' return f'{self.args[0]}{": " if self.annotation and self.args[0] else ""}{self.annotation if self.args[0] else ""}'
class InvalidPythonAnnotationError(PythonAnnotationError): ... class InvalidPythonAnnotationError(PythonAnnotationError): ...
class PythonDefinitionError(ValueError):
'''
Base class for Python definition errors.
'''
def __init__(self, *args: object, definition: str = '') -> None:
super().__init__(*args)
self.definition = definition
def __str__(self):
if not self.args:
return ''
else:
return f'{self.args[0]}{": " if self.definition and self.args[0] else ""}{self.definition if self.args[0] else ""}'
class PythonArgumentStructureError(PythonDefinitionError): ...
@@ -13,8 +13,7 @@
# limitations under the License. # limitations under the License.
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError, InvalidPythonAnnotationError from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError, InvalidPythonAnnotationError, PythonArgumentStructureError
import ast
import pytest import pytest
@pytest.mark.parametrize('name,kind,default,annotation,expected_exception,expected_exception_msg', [ @pytest.mark.parametrize('name,kind,default,annotation,expected_exception,expected_exception_msg', [
@@ -59,6 +58,8 @@ import pytest
('lambda', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: lambda'), ('lambda', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: lambda'),
('None', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: None'), ('None', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: None'),
('True', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: True'), ('True', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: True'),
('a', PythonArgumentKind.VARARG, '1', None, PythonArgumentStructureError, '* or ** arguments cannot have default values: *a = 1'),
('a', PythonArgumentKind.KWARGS, '1', None, PythonArgumentStructureError, '* or ** arguments cannot have default values: **a = 1'),
]) ])
def test_PythonFunctionArgument_exceptions(name, kind, default, annotation, expected_exception, expected_exception_msg): def test_PythonFunctionArgument_exceptions(name, kind, default, annotation, expected_exception, expected_exception_msg):
with pytest.raises(expected_exception) as exc_info: with pytest.raises(expected_exception) as exc_info:
@@ -79,13 +80,14 @@ def test_PythonFunctionArgument_exceptions(name, kind, default, annotation, expe
'case', 'case',
'type', 'type',
'ä', 'ä',
'none',
'true',
'false',
]) ])
@pytest.mark.parametrize('kind', [ @pytest.mark.parametrize('kind', [
PythonArgumentKind.NORMAL, PythonArgumentKind.NORMAL,
PythonArgumentKind.POSITIONAL_ONLY, PythonArgumentKind.POSITIONAL_ONLY,
PythonArgumentKind.KEYWORD_ONLY, PythonArgumentKind.KEYWORD_ONLY,
PythonArgumentKind.VARARG,
PythonArgumentKind.KWARGS,
]) ])
@pytest.mark.parametrize('default', [ @pytest.mark.parametrize('default', [
None, None,
@@ -101,3 +103,34 @@ def test_PythonFunctionArgument_exceptions(name, kind, default, annotation, expe
]) ])
def test_PythonFunctionArgument(name, kind, default, annotation): def test_PythonFunctionArgument(name, kind, default, annotation):
PythonFunctionArgument(name, kind, default, annotation) PythonFunctionArgument(name, kind, default, annotation)
@pytest.mark.parametrize('name', [
'a',
'_a',
'a1',
'a0',
'a_',
'_',
'_abc',
'abc',
'abc_',
'match',
'case',
'type',
'ä',
'none',
'true',
'false',
])
@pytest.mark.parametrize('kind', [
PythonArgumentKind.VARARG,
PythonArgumentKind.KWARGS,
])
@pytest.mark.parametrize('annotation', [
None,
'a',
'list[int]',
'_',
])
def test_PythonFunctionArgument_asterik(name, kind, annotation):
PythonFunctionArgument(name, kind, None, annotation)