generated from jCloud/repository-template
Add class for Python modules
This commit is contained in:
+2
-1
@@ -17,4 +17,5 @@
|
||||
- Add `PythonDocumentationGenerator` method to collect all namespaces
|
||||
- Add class for existing files
|
||||
- Add classes for Python function arguments
|
||||
- Add classes for Python definitions
|
||||
- Add classes for Python definitions
|
||||
- Add class for Python modules
|
||||
@@ -13,23 +13,61 @@
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
from ...utils import ExistingDirectory, assert_that_is_instance
|
||||
from .namespaces import PythonModuleNamespace, PythonPackageNamespace, PythonNamespace
|
||||
from typing import List
|
||||
from ...utils import ExistingDirectory, assert_that_is_instance, ExistingFile
|
||||
from .namespaces import PythonModuleNamespace, PythonPackageNamespace
|
||||
from .arguments import PythonASTArgumentsListParser
|
||||
from .definitions import PythonDefinition, PythonFunctionDefinition, PythonAsyncFunctionDefinition, PythonClassDefinition
|
||||
import pathlib
|
||||
import ast
|
||||
from collections.abc import Iterator
|
||||
|
||||
__all__ = [
|
||||
'PythonDocumentationGenerator'
|
||||
'PythonModuleDocumentationGenerator',
|
||||
'PythonDocumentationGenerator',
|
||||
]
|
||||
|
||||
# class PythonModuleDocumentationGenerator:
|
||||
# '''
|
||||
# A documentation generator for a Python module.
|
||||
def _collect_definitions(tree_or_node) -> Iterator[PythonDefinition]:
|
||||
'''
|
||||
Collects all definitions of the tree or node.
|
||||
|
||||
:param tree_or_node: The tree or node.
|
||||
|
||||
# '''
|
||||
:return: The definitions.
|
||||
:rtype: Iterator[PythonDefinition]
|
||||
'''
|
||||
|
||||
# def __init__(self, module_path: )
|
||||
for node in tree_or_node.body:
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
yield PythonFunctionDefinition.from_node(node)
|
||||
if isinstance(node, ast.AsyncFunctionDef):
|
||||
yield PythonAsyncFunctionDefinition.from_node(node)
|
||||
if isinstance(node, ast.ClassDef):
|
||||
yield PythonClassDefinition.from_node(node)
|
||||
|
||||
class PythonModuleDocumentationGenerator:
|
||||
'''
|
||||
A documentation generator for a Python module.
|
||||
|
||||
:param module_path: The path of the module file.
|
||||
:type module_path: ExistingFile
|
||||
'''
|
||||
|
||||
def __init__(self, module_path: ExistingFile) -> None:
|
||||
assert_that_is_instance(module_path, ExistingFile)
|
||||
|
||||
self.module_path = pathlib.Path(str(module_path))
|
||||
|
||||
def collect_definitions(self) -> Iterator[PythonDefinition]:
|
||||
'''
|
||||
Collects all function and class definitions of the module.
|
||||
|
||||
:return: All definitions of the module
|
||||
:rtype: Iterator[PythonDefinition]
|
||||
'''
|
||||
|
||||
tree = ast.parse(self.module_path.read_text())
|
||||
|
||||
return _collect_definitions(tree)
|
||||
|
||||
class PythonDocumentationGenerator:
|
||||
'''
|
||||
@@ -59,12 +97,12 @@ class PythonDocumentationGenerator:
|
||||
|
||||
return namespace
|
||||
|
||||
def namespace(self) -> List[PythonPackageNamespace]:
|
||||
def namespace(self) -> list[PythonPackageNamespace]:
|
||||
'''
|
||||
Returns the project as a namespace.
|
||||
|
||||
:return: The project as a namespace.
|
||||
:rtype: PythonPackageNamespace
|
||||
:rtype: list[PythonPackageNamespace]
|
||||
'''
|
||||
|
||||
src_dir = self.project_directory / 'src'
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
# 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._core import _collect_definitions
|
||||
from src.jcloud_docsgen.core.python.definitions import PythonClassDefinition, PythonFunctionDefinition
|
||||
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
|
||||
import pytest
|
||||
import ast
|
||||
|
||||
def module(source_code: str) -> ast.Module:
|
||||
'''
|
||||
Returns an AST module of the source code.
|
||||
|
||||
:param source_code: The module source code.
|
||||
:type source_code: str
|
||||
|
||||
:return: The AST module.
|
||||
:rtype: ast.Module
|
||||
'''
|
||||
|
||||
return ast.parse(source_code)
|
||||
|
||||
@pytest.mark.parametrize('module,expected', [
|
||||
(module('def add(a, b):\n\treturn a + b'), [
|
||||
PythonFunctionDefinition('add', [
|
||||
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
|
||||
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||
], None, None, [])
|
||||
]),
|
||||
(module('''def add(a: int | float, b: int | float) -> int | float:
|
||||
"""Adds two numbers."""
|
||||
return a + b
|
||||
|
||||
import typing
|
||||
|
||||
class Something:
|
||||
"""A class."""
|
||||
def __init__(self, value: typing.Any = 42) -> None:
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def value(self) -> typing.Any:
|
||||
"""
|
||||
The value.
|
||||
"""
|
||||
return self.get_value()
|
||||
|
||||
def get_value(self) -> typing.Any:
|
||||
"""
|
||||
Returns the value.
|
||||
"""
|
||||
return self._value
|
||||
'''), [
|
||||
PythonFunctionDefinition('add', [
|
||||
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int | float'),
|
||||
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int | float'),
|
||||
], 'int | float', 'Adds two numbers.', []),
|
||||
PythonClassDefinition('Something', [], 'A class.', [], [
|
||||
PythonFunctionDefinition('__init__', [
|
||||
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None),
|
||||
PythonFunctionArgument('value', PythonArgumentKind.NORMAL, '42', 'typing.Any'),
|
||||
], 'None', None, []),
|
||||
PythonFunctionDefinition('value', [
|
||||
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None),
|
||||
], 'typing.Any', 'The value.', ['property']),
|
||||
PythonFunctionDefinition('get_value', [
|
||||
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None),
|
||||
], 'typing.Any', 'Returns the value.', []),
|
||||
])
|
||||
]),
|
||||
(module('''from src.jcloud_docsgen.core.python import PythonDocumentationGenerator
|
||||
from src.jcloud_docsgen.core.python.namespaces import PythonModuleNamespace, PythonPackageNamespace
|
||||
import pytest
|
||||
from src.jcloud_docsgen.utils import ExistingDirectory
|
||||
|
||||
@pytest.mark.parametrize('project_directory,docs_directory', [
|
||||
(1, 1),
|
||||
(1, None),
|
||||
(42, None),
|
||||
(ExistingDirectory('tests'), 1),
|
||||
(ExistingDirectory('tests/'), 1),
|
||||
(1, ExistingDirectory('tests/')),
|
||||
(1, ExistingDirectory('tests')),
|
||||
(None, ExistingDirectory('tests')),
|
||||
])
|
||||
def test_PythonDocumentationGenerator_type_exceptions(project_directory, docs_directory):
|
||||
with pytest.raises(TypeError):
|
||||
PythonDocumentationGenerator(project_directory, docs_directory)
|
||||
|
||||
@pytest.mark.parametrize('python_documentation_generator,expected', [
|
||||
(PythonDocumentationGenerator(ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_1'), ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_1/docs')), []),
|
||||
(PythonDocumentationGenerator(ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_2'), ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_2/docs')), [PythonPackageNamespace('pkg', [PythonModuleNamespace('module')])]),
|
||||
(PythonDocumentationGenerator(ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_3'), ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_3/docs')), [PythonPackageNamespace('pkg', [PythonModuleNamespace('module'), PythonPackageNamespace('pkg', [PythonModuleNamespace('module2')])])]),
|
||||
])
|
||||
def test_PythonDocumentationGenerator_collect_modules(python_documentation_generator: PythonDocumentationGenerator, expected):
|
||||
assert python_documentation_generator.namespace() == expected'''), [
|
||||
PythonFunctionDefinition('test_PythonDocumentationGenerator_type_exceptions', [
|
||||
PythonFunctionArgument('project_directory', PythonArgumentKind.NORMAL, None, None),
|
||||
PythonFunctionArgument('docs_directory', PythonArgumentKind.NORMAL, None, None),
|
||||
], None, None, ['''pytest.mark.parametrize('project_directory,docs_directory', [(1, 1), (1, None), (42, None), (ExistingDirectory('tests'), 1), (ExistingDirectory('tests/'), 1), (1, ExistingDirectory('tests/')), (1, ExistingDirectory('tests')), (None, ExistingDirectory('tests'))])''']),
|
||||
PythonFunctionDefinition('test_PythonDocumentationGenerator_collect_modules', [
|
||||
PythonFunctionArgument('python_documentation_generator', PythonArgumentKind.NORMAL, None, 'PythonDocumentationGenerator'),
|
||||
PythonFunctionArgument('expected', PythonArgumentKind.NORMAL, None, None)
|
||||
], None, None, ['''pytest.mark.parametrize('python_documentation_generator,expected', [(PythonDocumentationGenerator(ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_1'), ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_1/docs')), []), (PythonDocumentationGenerator(ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_2'), ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_2/docs')), [PythonPackageNamespace('pkg', [PythonModuleNamespace('module')])]), (PythonDocumentationGenerator(ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_3'), ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_3/docs')), [PythonPackageNamespace('pkg', [PythonModuleNamespace('module'), PythonPackageNamespace('pkg', [PythonModuleNamespace('module2')])])])])'''])
|
||||
]),
|
||||
(module('''class C1:
|
||||
class C2:
|
||||
class C3:
|
||||
def function(self, value: bool, /, arg) -> bool:
|
||||
def should_be_not_in_the_result(arg: int) -> object:
|
||||
pass
|
||||
return not value'''), [
|
||||
PythonClassDefinition('C1', [], None, [], [
|
||||
PythonClassDefinition('C2', [], None, [], [
|
||||
PythonClassDefinition('C3', [], None, [], [
|
||||
PythonFunctionDefinition('function', [
|
||||
PythonFunctionArgument('self', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||
PythonFunctionArgument('value', PythonArgumentKind.POSITIONAL_ONLY, None, 'bool'),
|
||||
PythonFunctionArgument('arg', PythonArgumentKind.NORMAL, None, None),
|
||||
], 'bool', None, [])
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
def test___collect_definitions(module, expected):
|
||||
assert list(_collect_definitions(module)) == expected
|
||||
Reference in New Issue
Block a user