Add classes for Python definitions

This commit is contained in:
2026-04-12 18:49:23 +02:00
parent d8fd2b1948
commit d6a35ea222
5 changed files with 519 additions and 0 deletions
@@ -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))
]
)