Files
jcloud-docsgen/src/jcloud_docsgen/core/python/definitions.py
T

260 lines
7.3 KiB
Python

# 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)
)
],
)