# Copyright 2026 jCloud # 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 import ast import keyword import types from ...exceptions import InvalidPythonIdentifierError from ...utils import assert_that_is_instance from .arguments import PythonASTArgumentsListParser, PythonFunctionArgument from .docstrings import PythonDocstring __all__ = [ 'PythonDefinition', 'PythonFunctionDefinition', 'PythonAsyncFunctionDefinition', 'PythonClassDefinition', ] def _python_docstring(doc: str | None) -> PythonDocstring | None: """ Returns the PythonDocstring object if the docstring is a string and not ``None``. :param doc: The docstring. :type doc: str | None :return: The PythonDocstring object or ``None``. :rtype: PythonDocstring | None """ return PythonDocstring(doc) if doc is not None else None 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: PythonDocstring | 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: PythonDocstring | None, decorators: list[str], ) -> None: if keyword.iskeyword(name) or not name.isidentifier() or not name: raise InvalidPythonIdentifierError( 'invalid identifier', identifier=name, ) assert_that_is_instance(doc, (PythonDocstring, types.NoneType)) 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, _python_docstring(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: str | None :param doc: The docstring. :type doc: PythonDocstring | 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: str | None, doc: PythonDocstring | 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, _python_docstring(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: str | None :param doc: The docstring. :type doc: PythonDocstring | 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: PythonDocstring | 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: PythonDocstring | 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], _python_docstring(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) ) ], )