generated from jCloud/repository-template
Add classes for Python definitions
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
# 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 __future__ import annotations
|
||||
from typing import Union
|
||||
import ast
|
||||
from .arguments import PythonFunctionArgument, PythonASTArgumentsListParser
|
||||
import keyword
|
||||
from ...exceptions import InvalidPythonIdentifierError
|
||||
|
||||
__all__ = [
|
||||
'PythonDefinition',
|
||||
'PythonFunctionDefinition',
|
||||
'PythonAsyncFunctionDefinition',
|
||||
'PythonClassDefinition'
|
||||
]
|
||||
|
||||
class PythonDefinition:
|
||||
'''
|
||||
A base class for Python definition (a class, function or asynchronous
|
||||
function definition) classes.
|
||||
|
||||
:param name: The name of the definition.
|
||||
:type name: str
|
||||
:param doc: The docstring.
|
||||
:type doc: Union[str, None]
|
||||
:param decorators: The definition decorators.
|
||||
:type decorators: list[str]
|
||||
|
||||
:raises InvalidPythonIdentifierError: If the name is not a valid
|
||||
Python identifier.
|
||||
'''
|
||||
|
||||
def __init__(self, name: str, doc: Union[str, None], decorators: list[str]) -> None:
|
||||
if keyword.iskeyword(name) or not name.isidentifier() or not name:
|
||||
raise InvalidPythonIdentifierError('invalid identifier', identifier = name)
|
||||
|
||||
self.name = name
|
||||
self.doc = doc
|
||||
self.decorators = decorators
|
||||
|
||||
def _arg_list(self) -> tuple:
|
||||
return (
|
||||
self.name,
|
||||
self.doc,
|
||||
self.decorators
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return type(self).__name__ + repr(self._arg_list())
|
||||
|
||||
@classmethod
|
||||
def from_node(cls, node: ast.stmt) -> PythonDefinition:
|
||||
'''
|
||||
From an AST node.
|
||||
|
||||
:param node: The AST node.
|
||||
:type node: ast.stmt
|
||||
'''
|
||||
|
||||
return cls(
|
||||
node.name,
|
||||
ast.get_docstring(node),
|
||||
[ast.unparse(d) for d in node.decorator_list]
|
||||
)
|
||||
|
||||
def __eq__(self, value: object) -> bool:
|
||||
if not isinstance(value, PythonDefinition):
|
||||
return False
|
||||
return self._arg_list() == value._arg_list()
|
||||
|
||||
class PythonFunctionDefinition(PythonDefinition):
|
||||
'''
|
||||
A PythonDefinition subclass representing a Python function
|
||||
definition.
|
||||
|
||||
:param name: The name of the definition.
|
||||
:type name: str
|
||||
:param args: The arguments.
|
||||
:type args: list[PythonFunctionArgument]
|
||||
:param returns: The return type of the function or class.
|
||||
:type returns: Union[str, None]
|
||||
:param doc: The docstring.
|
||||
:type doc: Union[str, None]
|
||||
:param decorators: The definition decorators.
|
||||
:type decorators: list[str]
|
||||
|
||||
:raises InvalidPythonIdentifierError: If the name is not a valid
|
||||
Python identifier.
|
||||
'''
|
||||
|
||||
def __init__(self, name: str, args: list[PythonFunctionArgument], returns: Union[str, None], doc: Union[str, None], decorators: list[str]) -> None:
|
||||
super().__init__(name, doc, decorators)
|
||||
self.args = args
|
||||
self.returns = returns
|
||||
|
||||
def _arg_list(self) -> tuple:
|
||||
return (
|
||||
self.name,
|
||||
self.args,
|
||||
self.returns,
|
||||
self.doc,
|
||||
self.decorators
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_node(cls, node: ast.stmt) -> PythonFunctionDefinition:
|
||||
'''
|
||||
From an AST node.
|
||||
|
||||
:param node: The AST node.
|
||||
:type node: ast.stmt
|
||||
'''
|
||||
|
||||
return cls(
|
||||
node.name,
|
||||
PythonASTArgumentsListParser(node.args).to_argument_list(),
|
||||
ast.unparse(node.returns) if node.returns is not None else None,
|
||||
ast.get_docstring(node),
|
||||
[ast.unparse(d) for d in node.decorator_list]
|
||||
)
|
||||
|
||||
class PythonAsyncFunctionDefinition(PythonFunctionDefinition):
|
||||
'''
|
||||
A PythonDefinition subclass representing an asynchronous Python function
|
||||
definition.
|
||||
|
||||
:param name: The name of the definition.
|
||||
:type name: str
|
||||
:param args: The arguments.
|
||||
:type args: list[PythonFunctionArgument]
|
||||
:param returns: The return type of the function or class.
|
||||
:type returns: Union[str, None]
|
||||
:param doc: The docstring.
|
||||
:type doc: Union[str, None]
|
||||
:param decorators: The definition decorators.
|
||||
:type decorators: list[str]
|
||||
|
||||
:raises InvalidPythonIdentifierError: If the name is not a valid
|
||||
Python identifier.
|
||||
'''
|
||||
|
||||
class PythonClassDefinition(PythonDefinition):
|
||||
'''
|
||||
A PythonDefinition subclass representing a Python class definition.
|
||||
|
||||
:param name: The name of the definition.
|
||||
:type name: str
|
||||
:param bases: The base classes.
|
||||
:type bases: list[str]
|
||||
:param doc: The docstring.
|
||||
:type doc: Union[str, None]
|
||||
:param decorators: The definition decorators.
|
||||
:type decorators: list[str]
|
||||
:param body:
|
||||
:type body: list[PythonDefinition]
|
||||
|
||||
:raises InvalidPythonIdentifierError: If the name is not a valid
|
||||
Python identifier.
|
||||
'''
|
||||
|
||||
def __init__(self, name: str, bases: list[str], doc: Union[str, None], decorators: list[str], body: list[PythonDefinition]) -> None:
|
||||
super().__init__(name, doc, decorators)
|
||||
self.bases = bases
|
||||
self.body = body
|
||||
|
||||
def _arg_list(self) -> tuple:
|
||||
return (
|
||||
self.name,
|
||||
self.bases,
|
||||
self.doc,
|
||||
self.decorators,
|
||||
self.body
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_node(cls, node: ast.stmt) -> PythonClassDefinition:
|
||||
'''
|
||||
From an AST node.
|
||||
|
||||
:param node: The AST node.
|
||||
:type node: ast.stmt
|
||||
'''
|
||||
|
||||
return cls(
|
||||
node.name,
|
||||
[ast.unparse(b) for b in node.bases],
|
||||
ast.get_docstring(node),
|
||||
[ast.unparse(d) for d in node.decorator_list],
|
||||
[
|
||||
PythonFunctionDefinition.from_node(expr)
|
||||
if isinstance(expr, ast.FunctionDef)
|
||||
|
||||
else PythonAsyncFunctionDefinition.from_node(expr)
|
||||
if isinstance(expr, ast.AsyncFunctionDef)
|
||||
|
||||
else PythonClassDefinition.from_node(expr)
|
||||
|
||||
for expr in node.body
|
||||
if isinstance(expr, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,96 @@
|
||||
# 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.definitions import PythonFunctionDefinition, PythonClassDefinition
|
||||
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
|
||||
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError
|
||||
from tests.utils.ast_node import ast_node
|
||||
import pytest
|
||||
|
||||
@pytest.mark.parametrize('definition,expected', [
|
||||
(PythonClassDefinition('A', [], None, None, []), 'PythonClassDefinition(\'A\', [], None, None, [])'),
|
||||
(PythonClassDefinition('A', [], None, 'Hello, World!', []), 'PythonClassDefinition(\'A\', [], None, \'Hello, World!\', [])'),
|
||||
])
|
||||
def test_PythonDefinition_string_representation(definition, expected):
|
||||
assert repr(definition) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name,args,returns,doc,decorators,expected_exception,expected_exception_message', [
|
||||
('', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier'),
|
||||
('.', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: .'),
|
||||
('#', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: #'),
|
||||
('1a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: 1a'),
|
||||
('1_', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: 1_'),
|
||||
('1,', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: 1,'),
|
||||
(',', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: ,'),
|
||||
(':', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: :'),
|
||||
('-', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: -'),
|
||||
(';', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: ;'),
|
||||
('|', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: |'),
|
||||
('<', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: <'),
|
||||
('>', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: >'),
|
||||
('a,', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a,'),
|
||||
(',a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: ,a'),
|
||||
('a:', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a:'),
|
||||
(':a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: :a'),
|
||||
('a-', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a-'),
|
||||
('-a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: -a'),
|
||||
('a;', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a;'),
|
||||
(';a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: ;a'),
|
||||
('a|', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a|'),
|
||||
('|a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: |a'),
|
||||
('a<', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a<'),
|
||||
('<a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: <a'),
|
||||
('a>', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a>'),
|
||||
('>a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: >a'),
|
||||
('class', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: class'),
|
||||
('def', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: def'),
|
||||
('lambda', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: lambda'),
|
||||
('None', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: None'),
|
||||
('True', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: True'),
|
||||
])
|
||||
def test_PythonFunctionDefinition_exceptions(name, args, returns, doc, decorators, expected_exception, expected_exception_message):
|
||||
with pytest.raises(expected_exception) as exc_info:
|
||||
PythonClassDefinition(name, args, returns, doc, decorators)
|
||||
assert str(exc_info.value) == expected_exception_message
|
||||
|
||||
|
||||
@pytest.mark.parametrize('node,expected', [
|
||||
(ast_node('@decorator\nclass Class(BaseClass1, BaseClass2):\n\tdef __init__(self, value: object) -> None:\n\t\tself._value = value\n\n\t@property\n\tdef value(self) -> object:\n\t\treturn self._value'), PythonClassDefinition('Class', [
|
||||
'BaseClass1',
|
||||
'BaseClass2'
|
||||
], None, ['decorator'], [
|
||||
PythonFunctionDefinition('__init__', [
|
||||
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None),
|
||||
PythonFunctionArgument('value', PythonArgumentKind.NORMAL, None, 'object')
|
||||
], 'None', None, []),
|
||||
PythonFunctionDefinition('value', [
|
||||
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None)
|
||||
], 'object', None, ['property'])
|
||||
])),
|
||||
(ast_node('class Class: ...'), PythonClassDefinition('Class', [], None, [], [])),
|
||||
(ast_node('class Class: \'\'\'docstring\'\'\''), PythonClassDefinition('Class', [], 'docstring', [], [])),
|
||||
(ast_node('class Class:\n\tdef __init__(self): ...'), PythonClassDefinition('Class', [], None, [], [
|
||||
PythonFunctionDefinition('__init__', [
|
||||
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None)
|
||||
], None, None, [])
|
||||
])),
|
||||
(ast_node('class Class:\n\t\'\'\'doc\'\'\'\n\tdef __init__(self): ...'), PythonClassDefinition('Class', [], 'doc', [], [
|
||||
PythonFunctionDefinition('__init__', [
|
||||
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None)
|
||||
], None, None, [])
|
||||
])),
|
||||
])
|
||||
def test_PythonDefinition_from_node(node, expected):
|
||||
assert PythonClassDefinition.from_node(node) == expected
|
||||
@@ -0,0 +1,84 @@
|
||||
# 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.definitions import PythonDefinition
|
||||
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError
|
||||
from tests.utils.ast_node import ast_node
|
||||
import pytest
|
||||
|
||||
@pytest.mark.parametrize('definition,expected', [
|
||||
(PythonDefinition('a', None, []), 'PythonDefinition(\'a\', None, [])'),
|
||||
(PythonDefinition('a', 'Hello, World!', []), 'PythonDefinition(\'a\', \'Hello, World!\', [])'),
|
||||
])
|
||||
def test_PythonDefinition_string_representation(definition, expected):
|
||||
assert repr(definition) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name,doc,decorators,expected_exception,expected_exception_message', [
|
||||
('', None, [], InvalidPythonIdentifierError, 'invalid identifier'),
|
||||
('.', None, [], InvalidPythonIdentifierError, 'invalid identifier: .'),
|
||||
('#', None, [], InvalidPythonIdentifierError, 'invalid identifier: #'),
|
||||
('1a', None, [], InvalidPythonIdentifierError, 'invalid identifier: 1a'),
|
||||
('1_', None, [], InvalidPythonIdentifierError, 'invalid identifier: 1_'),
|
||||
('1,', None, [], InvalidPythonIdentifierError, 'invalid identifier: 1,'),
|
||||
(',', None, [], InvalidPythonIdentifierError, 'invalid identifier: ,'),
|
||||
(':', None, [], InvalidPythonIdentifierError, 'invalid identifier: :'),
|
||||
('-', None, [], InvalidPythonIdentifierError, 'invalid identifier: -'),
|
||||
(';', None, [], InvalidPythonIdentifierError, 'invalid identifier: ;'),
|
||||
('|', None, [], InvalidPythonIdentifierError, 'invalid identifier: |'),
|
||||
('<', None, [], InvalidPythonIdentifierError, 'invalid identifier: <'),
|
||||
('>', None, [], InvalidPythonIdentifierError, 'invalid identifier: >'),
|
||||
('a,', None, [], InvalidPythonIdentifierError, 'invalid identifier: a,'),
|
||||
(',a', None, [], InvalidPythonIdentifierError, 'invalid identifier: ,a'),
|
||||
('a:', None, [], InvalidPythonIdentifierError, 'invalid identifier: a:'),
|
||||
(':a', None, [], InvalidPythonIdentifierError, 'invalid identifier: :a'),
|
||||
('a-', None, [], InvalidPythonIdentifierError, 'invalid identifier: a-'),
|
||||
('-a', None, [], InvalidPythonIdentifierError, 'invalid identifier: -a'),
|
||||
('a;', None, [], InvalidPythonIdentifierError, 'invalid identifier: a;'),
|
||||
(';a', None, [], InvalidPythonIdentifierError, 'invalid identifier: ;a'),
|
||||
('a|', None, [], InvalidPythonIdentifierError, 'invalid identifier: a|'),
|
||||
('|a', None, [], InvalidPythonIdentifierError, 'invalid identifier: |a'),
|
||||
('a<', None, [], InvalidPythonIdentifierError, 'invalid identifier: a<'),
|
||||
('<a', None, [], InvalidPythonIdentifierError, 'invalid identifier: <a'),
|
||||
('a>', None, [], InvalidPythonIdentifierError, 'invalid identifier: a>'),
|
||||
('>a', None, [], InvalidPythonIdentifierError, 'invalid identifier: >a'),
|
||||
('class', None, [], InvalidPythonIdentifierError, 'invalid identifier: class'),
|
||||
('def', None, [], InvalidPythonIdentifierError, 'invalid identifier: def'),
|
||||
('lambda', None, [], InvalidPythonIdentifierError, 'invalid identifier: lambda'),
|
||||
('None', None, [], InvalidPythonIdentifierError, 'invalid identifier: None'),
|
||||
('True', None, [], InvalidPythonIdentifierError, 'invalid identifier: True'),
|
||||
])
|
||||
def test_PythonDefinition_exceptions(name, doc, decorators, expected_exception, expected_exception_message):
|
||||
with pytest.raises(expected_exception) as exc_info:
|
||||
PythonDefinition(name, doc, decorators)
|
||||
assert str(exc_info.value) == expected_exception_message
|
||||
|
||||
|
||||
@pytest.mark.parametrize('definition1,definition2,expected', [
|
||||
(PythonDefinition('a', None, []), PythonDefinition('a', None, []), True),
|
||||
(PythonDefinition('a', 'doc', []), PythonDefinition('a', 'doc', []), True),
|
||||
(PythonDefinition('a', 'doc', ['a']), PythonDefinition('a', 'doc', ['a']), True),
|
||||
(PythonDefinition('a', 'doc', ['a', 'b']), PythonDefinition('a', 'doc', ['a', 'b']), True),
|
||||
(PythonDefinition('a', None, []), PythonDefinition('b', None, []), False),
|
||||
(PythonDefinition('a', 'doc', []), PythonDefinition('a', None, []), False),
|
||||
(PythonDefinition('a', 'doc', []), PythonDefinition('a', 'docs', []), False),
|
||||
(PythonDefinition('a', None, ['a']), PythonDefinition('a', None, ['b']), False),
|
||||
(PythonDefinition('a', None, ['a']), PythonDefinition('a', None, []), False),
|
||||
(PythonDefinition('a', None, ['a']), PythonDefinition('a', None, ['a', 'b']), False),
|
||||
(PythonDefinition('a', None, ['b', 'a']), PythonDefinition('a', None, ['a', 'b']), False),
|
||||
(PythonDefinition('a', 'doc', []), PythonDefinition('b', None, ['a', 'b']), False),
|
||||
])
|
||||
def test_PythonDefinition___eq__(definition1, definition2, expected):
|
||||
assert (definition1 == definition2) == expected
|
||||
assert (definition2 == definition1) == expected
|
||||
@@ -0,0 +1,90 @@
|
||||
# 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.definitions import PythonFunctionDefinition
|
||||
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
|
||||
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError
|
||||
from tests.utils.ast_node import ast_node
|
||||
import pytest
|
||||
|
||||
@pytest.mark.parametrize('definition,expected', [
|
||||
(PythonFunctionDefinition('a', [], None, None, []), 'PythonFunctionDefinition(\'a\', [], None, None, [])'),
|
||||
(PythonFunctionDefinition('a', [], None, 'Hello, World!', []), 'PythonFunctionDefinition(\'a\', [], None, \'Hello, World!\', [])'),
|
||||
])
|
||||
def test_PythonDefinition_string_representation(definition, expected):
|
||||
assert repr(definition) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name,args,returns,doc,decorators,expected_exception,expected_exception_message', [
|
||||
('', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier'),
|
||||
('.', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: .'),
|
||||
('#', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: #'),
|
||||
('1a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: 1a'),
|
||||
('1_', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: 1_'),
|
||||
('1,', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: 1,'),
|
||||
(',', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: ,'),
|
||||
(':', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: :'),
|
||||
('-', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: -'),
|
||||
(';', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: ;'),
|
||||
('|', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: |'),
|
||||
('<', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: <'),
|
||||
('>', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: >'),
|
||||
('a,', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a,'),
|
||||
(',a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: ,a'),
|
||||
('a:', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a:'),
|
||||
(':a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: :a'),
|
||||
('a-', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a-'),
|
||||
('-a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: -a'),
|
||||
('a;', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a;'),
|
||||
(';a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: ;a'),
|
||||
('a|', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a|'),
|
||||
('|a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: |a'),
|
||||
('a<', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a<'),
|
||||
('<a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: <a'),
|
||||
('a>', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a>'),
|
||||
('>a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: >a'),
|
||||
('class', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: class'),
|
||||
('def', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: def'),
|
||||
('lambda', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: lambda'),
|
||||
('None', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: None'),
|
||||
('True', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: True'),
|
||||
])
|
||||
def test_PythonFunctionDefinition_exceptions(name, args, returns, doc, decorators, expected_exception, expected_exception_message):
|
||||
with pytest.raises(expected_exception) as exc_info:
|
||||
PythonFunctionDefinition(name, args, returns, doc, decorators)
|
||||
assert str(exc_info.value) == expected_exception_message
|
||||
|
||||
|
||||
@pytest.mark.parametrize('node,expected', [
|
||||
(ast_node('def add(a: int, b: int) -> int: return a + b'), PythonFunctionDefinition('add', [
|
||||
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
|
||||
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int'),
|
||||
], 'int', None, [])),
|
||||
(ast_node('@decorator\ndef add(a: int, b: int) -> int: return a + b'), PythonFunctionDefinition('add', [
|
||||
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
|
||||
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int'),
|
||||
], 'int', None, ['decorator'])),
|
||||
(ast_node('@decorator1\n@decorator2\ndef add(a: int, b: int) -> int: return a + b'), PythonFunctionDefinition('add', [
|
||||
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
|
||||
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int'),
|
||||
], 'int', None, ['decorator1', 'decorator2'])),
|
||||
(ast_node('def func(): ...'), PythonFunctionDefinition('func', [], None, None, [])),
|
||||
(ast_node('@decorator\ndef func(): ...'), PythonFunctionDefinition('func', [], None, None, ['decorator'])),
|
||||
# (ast_node('class Class(BaseClass1, BaseClass2):\n\tdef __init__(self, value: object) -> None:\n\t\tself._value = value\n\n\t@property\n\tdef value(self) -> object:\n\t\treturn self._value'), PythonFunctionDefinition('add', [
|
||||
# PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
|
||||
# PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int'),
|
||||
# ], 'int', None, [])),
|
||||
])
|
||||
def test_PythonDefinition_from_node(node, expected):
|
||||
assert PythonFunctionDefinition.from_node(node) == expected
|
||||
@@ -0,0 +1,36 @@
|
||||
# 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 typing import Union
|
||||
import ast
|
||||
|
||||
__all__ = [
|
||||
'ast_node'
|
||||
]
|
||||
|
||||
def ast_node(statement: str) -> Union[ast.stmt, None]:
|
||||
'''
|
||||
Returns an AST node from the statement.
|
||||
|
||||
:param statement: The statement.
|
||||
:type statement: str
|
||||
|
||||
:return: The node.
|
||||
:rtype: Union[ast.stmt, None]
|
||||
'''
|
||||
|
||||
body = ast.parse(statement).body
|
||||
if not body:
|
||||
return None
|
||||
return body[0]
|
||||
Reference in New Issue
Block a user