Add classes for Python function arguments

This commit is contained in:
2026-04-11 13:33:42 +02:00
parent d0e4ae54c3
commit 91cecae230
6 changed files with 508 additions and 2 deletions
@@ -0,0 +1,163 @@
# Copyright 2026 jCloud Services GbR
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from src.jcloud_docsgen.core.python.arguments import PythonASTArgumentsListParser, PythonFunctionArgument, PythonArgumentKind
import pytest
import ast
def argument_list(argument_list: str) -> ast.arguments:
'''
Returns an AST argument list from the argument list source code
:param argument_list: The argument list.
:type argument_list: str
:return: The AST argument list.
:rtype: ast.arguments
'''
return ast.parse('def func(' + argument_list + '): ...').body[0].args
@pytest.mark.parametrize('ast_arguments_list,expected', [
(argument_list('a'), [PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None)]),
(argument_list('a: str'), [PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'str')]),
(argument_list('a: str, b'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'str'),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None)
]),
(argument_list('a: str, b: int'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'str'),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int')
]),
(argument_list('a, b: int'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int')
]),
(argument_list('a, b, *args'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None)
]),
(argument_list('a: int, b, *args'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None)
]),
(argument_list('a, b: str, *args'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'str'),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None)
]),
(argument_list('a: int, b: str, *args'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'str'),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None)
]),
(argument_list('a: int, b: str, *args: float'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'str'),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, 'float')
]),
(argument_list('a: int, b, *args: float'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, 'float')
]),
(argument_list('a: int, *args: float'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, 'float')
]),
(argument_list('a: int, /, **kwargs'), [
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None)
]),
(argument_list('a: int, **kwargs: str'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, 'str')
]),
(argument_list('a: int, /, b, **kwargs'), [
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None)
]),
(argument_list('a: int, /, b'), [
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
]),
(argument_list('a: int, /, b, *, c'), [
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
]),
(argument_list('a, b, *, c'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
]),
(argument_list('a, b, *args, c'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
]),
(argument_list('a, b, *args, c, **kwargs'), [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
]),
(argument_list('a, b, /, *, c, **kwargs'), [
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None),
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
]),
(argument_list('a, b, /, c, *, d, **kwargs'), [
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None),
PythonFunctionArgument('c', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('d', PythonArgumentKind.KEYWORD_ONLY, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
]),
(argument_list('a: str, b: int, /, c: float, d: list, *, e: tuple, f: dict, **kwargs: set'), [
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'str'),
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
PythonFunctionArgument('c', PythonArgumentKind.NORMAL, None, 'float'),
PythonFunctionArgument('d', PythonArgumentKind.NORMAL, None, 'list'),
PythonFunctionArgument('e', PythonArgumentKind.KEYWORD_ONLY, None, 'tuple'),
PythonFunctionArgument('f', PythonArgumentKind.KEYWORD_ONLY, None, 'dict'),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, 'set'),
]),
(argument_list('a: str, b: int, /, c: float, d: list, *args: bytes, e: tuple, f: dict, **kwargs: set'), [
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'str'),
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
PythonFunctionArgument('c', PythonArgumentKind.NORMAL, None, 'float'),
PythonFunctionArgument('d', PythonArgumentKind.NORMAL, None, 'list'),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, 'bytes'),
PythonFunctionArgument('e', PythonArgumentKind.KEYWORD_ONLY, None, 'tuple'),
PythonFunctionArgument('f', PythonArgumentKind.KEYWORD_ONLY, None, 'dict'),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, 'set'),
]),
(argument_list('a: str, b: int, /, *args: bytes, c: tuple, d: dict, **kwargs: set'), [
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'str'),
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, 'bytes'),
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, 'tuple'),
PythonFunctionArgument('d', PythonArgumentKind.KEYWORD_ONLY, None, 'dict'),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, 'set'),
]),
])
def test_PythonASTArgumentsListParser_to_argument_list(ast_arguments_list, expected):
assert PythonASTArgumentsListParser(ast_arguments_list).to_argument_list() == expected
@@ -0,0 +1,103 @@
# Copyright 2026 jCloud Services GbR
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind, PythonFunctionArgumentDefault
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError, InvalidPythonAnnotationError
import ast
import pytest
@pytest.mark.parametrize('name,kind,default,annotation,expected_exception,expected_exception_msg', [
(None, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'NoneType\''),
(1, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'int\''),
(42, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'int\''),
(4.2, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'float\''),
(4.2, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'float\''),
('a', PythonArgumentKind.NORMAL, 1, None, TypeError, 'expected either \'PythonFunctionArgumentDefault\' or \'NoneType\', got \'int\''),
('a', PythonArgumentKind.NORMAL, None, 1, TypeError, 'expected either \'str\' or \'NoneType\', got \'int\''),
('a', 0, None, None, TypeError, 'expected \'PythonArgumentKind\', got \'int\''),
('a', PythonArgumentKind.NORMAL, None, '', InvalidPythonAnnotationError, 'invalid annotation'),
('', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier'),
('.', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: .'),
('#', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: #'),
('1a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: 1a'),
('1_', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: 1_'),
('1,', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: 1,'),
(',', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ,'),
(':', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: :'),
('-', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: -'),
(';', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ;'),
('|', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: |'),
('<', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: <'),
('>', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: >'),
('a,', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a,'),
(',a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ,a'),
('a:', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a:'),
(':a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: :a'),
('a-', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a-'),
('-a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: -a'),
('a;', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a;'),
(';a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ;a'),
('a|', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a|'),
('|a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: |a'),
('a<', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a<'),
('<a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: <a'),
('a>', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a>'),
('>a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: >a'),
('class', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: class'),
('def', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: def'),
('lambda', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: lambda'),
('None', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: None'),
('True', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: True'),
])
def test_PythonFunctionArgument_exceptions(name, kind, default, annotation, expected_exception, expected_exception_msg):
with pytest.raises(expected_exception) as exc_info:
PythonFunctionArgument(name, kind, default, annotation)
assert str(exc_info.value) == expected_exception_msg
@pytest.mark.parametrize('name', [
'a',
'_a',
'a1',
'a0',
'a_',
'_',
'_abc',
'abc',
'abc_',
'match',
'case',
'type',
'ä',
])
@pytest.mark.parametrize('kind', [
PythonArgumentKind.NORMAL,
PythonArgumentKind.POSITIONAL_ONLY,
PythonArgumentKind.KEYWORD_ONLY,
PythonArgumentKind.VARARG,
PythonArgumentKind.KWARGS,
])
@pytest.mark.parametrize('default', [
None,
PythonFunctionArgumentDefault(ast.Constant(value = 'x')),
PythonFunctionArgumentDefault(ast.Constant(value = 1)),
PythonFunctionArgumentDefault(ast.Constant(value = None)),
])
@pytest.mark.parametrize('annotation', [
None,
'a',
'list[int]',
'_',
])
def test_PythonFunctionArgument(name, kind, default, annotation):
PythonFunctionArgument(name, kind, default, annotation)
@@ -0,0 +1,55 @@
# Copyright 2026 jCloud Services GbR
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgumentDefault
import pytest
import ast
@pytest.mark.parametrize('default,expected', [
(PythonFunctionArgumentDefault(ast.Constant(value = '')), ast.Constant),
(PythonFunctionArgumentDefault(ast.Constant(value = 'x')), ast.Constant),
(PythonFunctionArgumentDefault(ast.Constant(value = None)), ast.Constant),
(PythonFunctionArgumentDefault(ast.Constant(value = 1)), ast.Constant),
])
def test_PythonFunctionArgumentDefault_value_attribute_type(default, expected):
assert isinstance(default.value, expected)
@pytest.mark.parametrize('value,expected_exception_msg', [
(None, 'expected \'expr\', got \'NoneType\''),
(1, 'expected \'expr\', got \'int\''),
('x', 'expected \'expr\', got \'str\''),
(42, 'expected \'expr\', got \'int\''),
(4.2, 'expected \'expr\', got \'float\''),
])
def test_PythonFunctionArgumentDefault_raises_TypeError(value, expected_exception_msg):
with pytest.raises(TypeError) as exc_info:
PythonFunctionArgumentDefault(value)
assert str(exc_info.value) == expected_exception_msg
@pytest.mark.parametrize('default1,default2,expected', [
(PythonFunctionArgumentDefault(ast.Constant(value = 1)), PythonFunctionArgumentDefault(ast.Constant(value = 1)), True),
(PythonFunctionArgumentDefault(ast.Constant(value = 1)), PythonFunctionArgumentDefault(ast.Constant(value = 2)), False),
(PythonFunctionArgumentDefault(ast.Constant(value = 'a')), PythonFunctionArgumentDefault(ast.Constant(value = 2)), False),
(PythonFunctionArgumentDefault(ast.Constant(value = 'a')), PythonFunctionArgumentDefault(ast.Constant(value = 'a')), True),
(PythonFunctionArgumentDefault(ast.Constant(value = 'a')), PythonFunctionArgumentDefault(ast.Constant(value = 'b')), False),
(PythonFunctionArgumentDefault(ast.List(elts = [])), PythonFunctionArgumentDefault(ast.List(elts = [])), True),
(PythonFunctionArgumentDefault(ast.List(elts = [1])), PythonFunctionArgumentDefault(ast.List(elts = [])), False),
(PythonFunctionArgumentDefault(ast.List(elts = [1])), PythonFunctionArgumentDefault(ast.List(elts = [1])), True),
(PythonFunctionArgumentDefault(ast.List(elts = [1])), PythonFunctionArgumentDefault(ast.List(elts = [2])), False),
(PythonFunctionArgumentDefault(ast.List(elts = [1])), PythonFunctionArgumentDefault(ast.List(elts = [1, 2])), False),
(PythonFunctionArgumentDefault(ast.List(elts = [1, 2])), PythonFunctionArgumentDefault(ast.List(elts = [1, 2])), True),
(PythonFunctionArgumentDefault(ast.List(elts = [2, 1])), PythonFunctionArgumentDefault(ast.List(elts = [1, 2])), False),
])
def test_PythonFunctionArgumentDefault___eq__(default1, default2, expected):
assert (default1 == default2) == expected