generated from jCloud/repository-template
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44f80dbb6a
|
||
|
|
14380b6515
|
||
|
|
d3bb51eb61
|
||
|
|
b4f4c4860a
|
||
|
|
86c012ef5c
|
||
|
|
a74a500e7e
|
||
|
|
16ce11b852
|
||
|
|
cf8875463c
|
||
|
|
81b2be7efc
|
||
|
|
55c4c74ee8
|
||
|
|
3315448fc5
|
||
|
|
ce244df494
|
||
|
|
d12263f552
|
||
|
|
22f3233381
|
||
|
|
a17e56fb3d
|
||
|
|
ecd3f7e76b
|
||
|
|
f581774649
|
||
|
|
3fd51d126f
|
||
|
|
3b45c1a8ed
|
||
|
|
b71b93000e
|
||
|
|
16852f3d68
|
||
|
|
7f02b5b7ec
|
||
|
|
a8626418da
|
||
|
|
10707d680d
|
||
|
|
42a568223b
|
||
|
|
72b1024a16
|
||
|
|
3bc7953541
|
||
|
|
d5f9368d04
|
||
|
|
a79b538de8
|
||
|
|
8c3866a948
|
||
|
|
ed3b47bd81
|
||
|
|
e4fe88b60b
|
||
|
|
73730520c1
|
||
|
|
076e04a67a
|
||
|
|
d6a35ea222
|
||
|
|
d8fd2b1948
|
||
|
|
29714798f7
|
||
|
|
91cecae230
|
||
|
|
d0e4ae54c3
|
||
|
|
d3e022fb13
|
||
|
|
3e96072361
|
||
|
|
a550ba6832
|
||
|
|
74cb599ed6
|
||
|
|
9d86a64418
|
||
|
|
8f48f591e6
|
||
|
|
ff45f836b3
|
||
|
|
9e2fe5f2df
|
||
|
|
9e524bec03
|
||
|
|
512a07a773
|
||
|
|
d0e2855b58
|
||
|
|
54e3a30ef0
|
||
|
|
66f06960a9
|
||
|
|
286ba68c34
|
||
|
|
2cac1727e1
|
||
|
|
0dcf34c068
|
||
|
|
6cb5d25f5c
|
||
|
|
fa75b6f036
|
||
|
|
1c0f0ba81a
|
||
|
|
45b488b8c2
|
||
|
|
f3523549a9
|
||
|
|
e2a4db89ba
|
||
|
|
b0a0d3e45b
|
||
|
|
0f817e961c
|
||
|
|
e2ebde7cc4
|
||
|
|
57a395727d
|
||
|
|
6119c69a3c
|
||
|
|
6f23068e0b
|
||
|
|
2f6e373ba7
|
||
|
|
5087645069
|
||
|
|
4988491018
|
||
|
|
9af9e18304
|
||
|
|
7f5430188d
|
||
|
|
47ee0f23b3
|
||
|
|
7423f19cfd
|
||
|
|
1a6208888e
|
||
|
|
bdf7cb4af7
|
||
|
|
157c8712b2
|
||
|
|
cc346c983c
|
||
|
|
d79a22e98f
|
||
|
|
00fbd89746
|
@@ -1,4 +1,4 @@
|
|||||||
Copyright 2026 jCloud Services GbR
|
Copyright 2026 jCloud
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
Third-Party Notices
|
||||||
|
===================
|
||||||
|
|
||||||
|
This software uses the following third-party libraries. Full license texts are available in the 'licenses/' directory.
|
||||||
|
|
||||||
|
1. docstring-parser 0.17.0
|
||||||
|
Author: Marcin Kurczewski
|
||||||
|
License: MIT License
|
||||||
|
See licenses/docstring-parser.txt for full license text.
|
||||||
|
|
||||||
|
2. iniconfig 2.3.0
|
||||||
|
Author: Holger Krekel and others
|
||||||
|
License: MIT License
|
||||||
|
See licenses/iniconfig.txt for full license text.
|
||||||
|
|
||||||
|
3. packaging 26.0
|
||||||
|
Author: Donald Stufft and individual contributors
|
||||||
|
License: Apache License 2.0
|
||||||
|
URL: None
|
||||||
|
See licenses/packaging.txt for full license text.
|
||||||
|
|
||||||
|
4. pluggy 1.6.0
|
||||||
|
Author: Holger Krekel
|
||||||
|
License: MIT License
|
||||||
|
See licenses/pluggy.txt for full license text.
|
||||||
|
|
||||||
|
5. pygments 2.18.0
|
||||||
|
Author: Georg Brandl, Tim Hatch, Armin Ronacher, Others
|
||||||
|
License: BSD-2-Clause
|
||||||
|
See licenses/pygments.txt for full license text.
|
||||||
|
|
||||||
|
6. pytest 9.0.2
|
||||||
|
Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin, Others
|
||||||
|
License: MIT License
|
||||||
|
See licenses/pytest.txt for full license text.
|
||||||
|
|
||||||
|
7. pytest-expectdir 1.2.0
|
||||||
|
Author: Léo Falventin Hauchecorne
|
||||||
|
License: MIT
|
||||||
|
URL: https://github.com/hl037/pytest-expectdir
|
||||||
|
See licenses/pytest-expectdir.txt for full license text.
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# jcloud-docsgen
|
||||||
|
|
||||||
|
A tool that generates Markdown documentation from software project source code.
|
||||||
|
|
||||||
|
Currently supported programming languages:
|
||||||
|
- Python
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
You can install it using pip:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install jcloud-docsgen --index-url https://repo.jcloud-services.ddns.net/simple/ --extra-index-url https://pypi.org/simple/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
API reference and generated documentation can be found in [docs/reference](docs/reference).
|
||||||
|
|
||||||
|
## Notice
|
||||||
|
|
||||||
|
This project is under active development. The documentation will be expanded over time.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add class for existing directories
|
||||||
|
- Add function to format a list human-readable
|
||||||
|
- Add function to assert that an object is an instance of a specific type
|
||||||
|
- Add Python project documentation generator class
|
||||||
|
- Add Python namespaces classes
|
||||||
|
- Add type checking for the `name` attribute at the constructor of `core.python.namespaces.PythonNamespace`
|
||||||
|
- Add check whether sub-namespaces are not empty in `core.python.namespaces.PythonPackageNamespace.namespace`
|
||||||
|
- Add feature to get the names of all sub-namespaces of a `core.python.namespaces.PythonPackageNamespace`
|
||||||
|
- Add feature to compare `core.python.namespaces.PythonPackageNamespace` instances or `core.python.namespaces.PythonModuleNamespace` instances
|
||||||
|
- Add string representation for Python namespace objects
|
||||||
|
- Add `PythonDocumentationGenerator` method to collect all namespaces
|
||||||
|
- Add class for existing files
|
||||||
|
- Add classes for Python function arguments
|
||||||
|
- Add classes for Python definitions
|
||||||
|
- Add class for Python modules
|
||||||
|
- Add feature to get the signature representation of a Python function argument
|
||||||
|
- Add class for Python docstrings
|
||||||
|
- Add feature to parse Python docstrings
|
||||||
|
- Add function to ensure a non-empty string.
|
||||||
|
- Add Python definition documentation generator
|
||||||
|
- Add function to get the path segments of a path relative to another
|
||||||
|
- Add Python module documentation generator
|
||||||
|
- Add Python project documentation generator
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,205 @@
|
|||||||
|
# Module `jcloud_docsgen.core.python.arguments`
|
||||||
|
|
||||||
|
## Class `PythonArgumentKind`
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `Enum`
|
||||||
|
|
||||||
|
## Class `PythonFunctionArgument`
|
||||||
|
|
||||||
|
Represents an argument of a Python function.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str, kind: PythonArgumentKind, default: Union[str, None], annotation: Union[str, None]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
| `kind` | `PythonArgumentKind` | normal | -- | -- |
|
||||||
|
| `default` | `Union[str, None]` | normal | -- | -- |
|
||||||
|
| `annotation` | `Union[str, None]` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `__eq__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__eq__(self, value: PythonFunctionArgument) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `bool`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `value` | `PythonFunctionArgument` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `__repr__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__repr__(self) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `signature_repr`
|
||||||
|
|
||||||
|
Returns the function signature representation of the argument.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
signature_repr(self) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The string representation of the argument.
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns the function signature representation of the argument.
|
||||||
|
|
||||||
|
:return: The string representation of the argument.
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Represents an argument of a Python function.
|
||||||
|
|
||||||
|
:param name: The name of the argument.
|
||||||
|
:type name: str
|
||||||
|
:param kind: The kind of the argument.
|
||||||
|
:type kind: ArgumentKind
|
||||||
|
:param default: The default value of the argument.
|
||||||
|
:type default: Union[str, None]
|
||||||
|
:param annotation: The type annotation of the argument.
|
||||||
|
:type annotation: Union[str, None]
|
||||||
|
|
||||||
|
## Class `PythonASTArgumentsListParser`
|
||||||
|
|
||||||
|
A parser for making ``PythonFunctionArgument`` objects from an
|
||||||
|
``ast.arguments`` arguments list.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, ast_arguments_list: ast.arguments) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `ast_arguments_list` | `ast.arguments` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `to_argument_list`
|
||||||
|
|
||||||
|
Converts the AST arguments list to a list of Python function
|
||||||
|
argument objects.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
to_argument_list(self) -> list[PythonFunctionArgument]
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The list of Python function argument objects.
|
||||||
|
|
||||||
|
Return type: `list[PythonFunctionArgument]`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Converts the AST arguments list to a list of Python function
|
||||||
|
argument objects.
|
||||||
|
|
||||||
|
:return: The list of Python function argument objects.
|
||||||
|
:rtype: list[PythonFunctionArgument]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
A parser for making ``PythonFunctionArgument`` objects from an
|
||||||
|
``ast.arguments`` arguments list.
|
||||||
|
|
||||||
|
:param ast_arguments_list: The ``ast.arguments`` arguments list.
|
||||||
|
:type ast_arguments_list: ast.arguments
|
||||||
@@ -0,0 +1,445 @@
|
|||||||
|
# Module `jcloud_docsgen.core.python.definitions`
|
||||||
|
|
||||||
|
## Function `_python_docstring`
|
||||||
|
|
||||||
|
Returns the PythonDocstring object if the docstring is a string and
|
||||||
|
not ``None``.
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
_python_docstring(doc: Union[str, None]) -> Union[PythonDocstring, None]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The PythonDocstring object or ``None``.
|
||||||
|
|
||||||
|
Return type: `Union[PythonDocstring, None]`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `doc` | `Union[str, None]` | normal | -- | The docstring. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Returns the PythonDocstring object if the docstring is a string and
|
||||||
|
not ``None``.
|
||||||
|
|
||||||
|
:param doc: The docstring.
|
||||||
|
:type doc: Union[str, None]
|
||||||
|
|
||||||
|
:return: The PythonDocstring object or ``None``.
|
||||||
|
:rtype: Union[PythonDocstring, None]
|
||||||
|
|
||||||
|
## Class `PythonDefinition`
|
||||||
|
|
||||||
|
A base class for Python definition (a class, function or asynchronous
|
||||||
|
function definition) classes.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str, doc: Union[PythonDocstring, None], decorators: list[str]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
| `doc` | `Union[PythonDocstring, None]` | normal | -- | -- |
|
||||||
|
| `decorators` | `list[str]` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `_arg_list`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
_arg_list(self) -> tuple
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `tuple`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `__repr__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__repr__(self) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `from_node`
|
||||||
|
|
||||||
|
From an AST node.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
from_node(cls, node: ast.stmt) -> PythonDefinition
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `PythonDefinition`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `cls` | -- | normal | -- | -- |
|
||||||
|
| `node` | `ast.stmt` | normal | -- | The AST node. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Decorators
|
||||||
|
|
||||||
|
```python
|
||||||
|
classmethod
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
From an AST node.
|
||||||
|
|
||||||
|
:param node: The AST node.
|
||||||
|
:type node: ast.stmt
|
||||||
|
|
||||||
|
#### Method `__eq__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__eq__(self, value: object) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `bool`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `value` | `object` | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
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[PythonDocstring, None]
|
||||||
|
:param decorators: The definition decorators.
|
||||||
|
:type decorators: list[str]
|
||||||
|
|
||||||
|
:raises InvalidPythonIdentifierError: If the name is not a valid
|
||||||
|
Python identifier.
|
||||||
|
|
||||||
|
## Class `PythonFunctionDefinition`
|
||||||
|
|
||||||
|
A PythonDefinition subclass representing a Python function
|
||||||
|
definition.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `PythonDefinition`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str, args: list[PythonFunctionArgument], returns: Union[str, None], doc: Union[PythonDocstring, None], decorators: list[str]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
| `args` | `list[PythonFunctionArgument]` | normal | -- | -- |
|
||||||
|
| `returns` | `Union[str, None]` | normal | -- | -- |
|
||||||
|
| `doc` | `Union[PythonDocstring, None]` | normal | -- | -- |
|
||||||
|
| `decorators` | `list[str]` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `_arg_list`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
_arg_list(self) -> tuple
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `tuple`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `from_node`
|
||||||
|
|
||||||
|
From an AST node.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
from_node(cls, node: ast.stmt) -> PythonFunctionDefinition
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `PythonFunctionDefinition`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `cls` | -- | normal | -- | -- |
|
||||||
|
| `node` | `ast.stmt` | normal | -- | The AST node. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Decorators
|
||||||
|
|
||||||
|
```python
|
||||||
|
classmethod
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
From an AST node.
|
||||||
|
|
||||||
|
:param node: The AST node.
|
||||||
|
:type node: ast.stmt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
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[PythonDocstring, None]
|
||||||
|
:param decorators: The definition decorators.
|
||||||
|
:type decorators: list[str]
|
||||||
|
|
||||||
|
:raises InvalidPythonIdentifierError: If the name is not a valid
|
||||||
|
Python identifier.
|
||||||
|
|
||||||
|
## Class `PythonAsyncFunctionDefinition`
|
||||||
|
|
||||||
|
A PythonDefinition subclass representing an asynchronous Python function
|
||||||
|
definition.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `PythonFunctionDefinition`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
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[PythonDocstring, None]
|
||||||
|
:param decorators: The definition decorators.
|
||||||
|
:type decorators: list[str]
|
||||||
|
|
||||||
|
:raises InvalidPythonIdentifierError: If the name is not a valid
|
||||||
|
Python identifier.
|
||||||
|
|
||||||
|
## Class `PythonClassDefinition`
|
||||||
|
|
||||||
|
A PythonDefinition subclass representing a Python class definition.
|
||||||
|
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `PythonDefinition`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str, bases: list[str], doc: Union[PythonDocstring, None], decorators: list[str], body: list[PythonDefinition]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
| `bases` | `list[str]` | normal | -- | -- |
|
||||||
|
| `doc` | `Union[PythonDocstring, None]` | normal | -- | -- |
|
||||||
|
| `decorators` | `list[str]` | normal | -- | -- |
|
||||||
|
| `body` | `list[PythonDefinition]` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `_arg_list`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
_arg_list(self) -> tuple
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `tuple`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `from_node`
|
||||||
|
|
||||||
|
From an AST node.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
from_node(cls, node: ast.stmt) -> PythonClassDefinition
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `PythonClassDefinition`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `cls` | -- | normal | -- | -- |
|
||||||
|
| `node` | `ast.stmt` | normal | -- | The AST node. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Decorators
|
||||||
|
|
||||||
|
```python
|
||||||
|
classmethod
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
From an AST node.
|
||||||
|
|
||||||
|
:param node: The AST node.
|
||||||
|
:type node: ast.stmt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
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[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.
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
# Module `jcloud_docsgen.core.python.docstrings`
|
||||||
|
|
||||||
|
## Class `PythonDocstring`
|
||||||
|
|
||||||
|
Represents a Python docstring.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, docstring: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `docstring` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `__repr__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__repr__(self) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `__eq__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__eq__(self, value) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `bool`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `value` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `parse`
|
||||||
|
|
||||||
|
Parses the docstring.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
parse(self) -> docstring_parser.Docstring
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The parsed docstring.
|
||||||
|
|
||||||
|
Return type: `docstring_parser.Docstring`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Parses the docstring.
|
||||||
|
|
||||||
|
:return: The parsed docstring.
|
||||||
|
:rtype: docstring_parser.Docstring
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Represents a Python docstring.
|
||||||
|
|
||||||
|
:param docstring: The docstring as a string.
|
||||||
|
:type docstring: str
|
||||||
@@ -0,0 +1,442 @@
|
|||||||
|
# Module `jcloud_docsgen.core.python.namespaces`
|
||||||
|
|
||||||
|
## Class `PythonNamespace`
|
||||||
|
|
||||||
|
The base class for all Python namespaces such as modules or packages.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `__repr__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__repr__(self) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
The base class for all Python namespaces such as modules or packages.
|
||||||
|
|
||||||
|
:param name: The name of the namespace.
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
## Class `PythonPackageNamespace`
|
||||||
|
|
||||||
|
A subclass of ``PythonNamespace`` for Python package namespaces.
|
||||||
|
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `PythonNamespace`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str, sub_namespaces: List[PythonNamespace]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
| `sub_namespaces` | `List[PythonNamespace]` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `namespace`
|
||||||
|
|
||||||
|
Returns the namespace object with a specific identifier, such as
|
||||||
|
``['package', 'subpackage', 'module']``.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
namespace(self, sub_namespaces: List[str]) -> PythonNamespace
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The namespace.
|
||||||
|
|
||||||
|
Return type: `PythonNamespace`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `sub_namespaces` | `List[str]` | normal | -- | The identifier of the namespace. |
|
||||||
|
|
||||||
|
##### Exceptions
|
||||||
|
|
||||||
|
| Exception | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `InvalidNamespaceError` | If the namespace identifier is invalid. |
|
||||||
|
| `NamespaceNotFoundError` | If the namespace does not exist. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns the namespace object with a specific identifier, such as
|
||||||
|
``['package', 'subpackage', 'module']``.
|
||||||
|
|
||||||
|
:param sub_namespaces: The identifier of the namespace.
|
||||||
|
:type sub_namespaces: list[str]
|
||||||
|
|
||||||
|
:raises InvalidNamespaceError: If the namespace identifier is
|
||||||
|
invalid.
|
||||||
|
:raises NamespaceNotFoundError: If the namespace does not exist.
|
||||||
|
|
||||||
|
:return: The namespace.
|
||||||
|
:rtype: PythonNamespace
|
||||||
|
|
||||||
|
#### Method `_sub_namespaces_names`
|
||||||
|
|
||||||
|
Returns the names of all sub-namespaces of the specified package
|
||||||
|
namespace and keeps the package structure.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
_sub_namespaces_names(self, package: PythonPackageNamespace) -> PythonPackageNamespaceDictType
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The names if all sub-namespaces.
|
||||||
|
|
||||||
|
Return type: `PythonPackageNamespaceDictType`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `package` | `PythonPackageNamespace` | normal | -- | The package. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns the names of all sub-namespaces of the specified package
|
||||||
|
namespace and keeps the package structure.
|
||||||
|
|
||||||
|
:param package: The package.
|
||||||
|
:type package: PythonPackageNamespace
|
||||||
|
|
||||||
|
:return: The names if all sub-namespaces.
|
||||||
|
:rtype: PythonPackageNamespaceDictType
|
||||||
|
|
||||||
|
#### Method `sub_namespace_names`
|
||||||
|
|
||||||
|
Returns the names of all sub-namespaces and keeps the package
|
||||||
|
structure.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
sub_namespace_names(self) -> PythonPackageNamespaceDictType
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The names if all sub-namespaces.
|
||||||
|
|
||||||
|
Return type: `PythonPackageNamespaceDictType`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns the names of all sub-namespaces and keeps the package
|
||||||
|
structure.
|
||||||
|
|
||||||
|
:return: The names if all sub-namespaces.
|
||||||
|
:rtype: PythonPackageNamespaceDictType
|
||||||
|
|
||||||
|
#### Method `__eq__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__eq__(self, value: PythonPackageNamespace) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `bool`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `value` | `PythonPackageNamespace` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `__repr__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__repr__(self) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `__getitem__`
|
||||||
|
|
||||||
|
Returns a namespace with the specified name.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__getitem__(self, name: str) -> PythonNamespace
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The namespace
|
||||||
|
|
||||||
|
Return type: `PythonNamespace`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | The name of the namespace |
|
||||||
|
|
||||||
|
##### Exceptions
|
||||||
|
|
||||||
|
| Exception | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `NamespaceNotFoundError` | If the namespace does not exist. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns a namespace with the specified name.
|
||||||
|
|
||||||
|
:param name: The name of the namespace
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
:raises NamespaceNotFoundError: If the namespace does not exist.
|
||||||
|
|
||||||
|
:return: The namespace
|
||||||
|
:rtype: PythonNamespace
|
||||||
|
|
||||||
|
#### Method `add`
|
||||||
|
|
||||||
|
Adds a namespace.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
add(self, namespace: PythonNamespace) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `namespace` | `PythonNamespace` | normal | -- | The namespace to add. |
|
||||||
|
|
||||||
|
##### Exceptions
|
||||||
|
|
||||||
|
| Exception | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `NamespaceExistsError` | If a namespace with the name of the namespace that is attempted to add already exists. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Adds a namespace.
|
||||||
|
|
||||||
|
:param namespace: The namespace to add.
|
||||||
|
:type namespace: PythonNamespace
|
||||||
|
|
||||||
|
:raises NamespaceExistsError: If a namespace with the name of the
|
||||||
|
namespace that is attempted to add
|
||||||
|
already exists.
|
||||||
|
|
||||||
|
#### Method `sub_namespaces`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
sub_namespaces(self) -> List[PythonNamespace]
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `List[PythonNamespace]`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Decorators
|
||||||
|
|
||||||
|
```python
|
||||||
|
property
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
A subclass of ``PythonNamespace`` for Python package namespaces.
|
||||||
|
|
||||||
|
:param name: The name of the namespace.
|
||||||
|
:type name: str
|
||||||
|
:param sub_namespaces: The sub-namespaces.
|
||||||
|
:type sub_namespaces: list[PythonNamespace]
|
||||||
|
|
||||||
|
## Class `PythonModuleNamespace`
|
||||||
|
|
||||||
|
A subclass of ``PythonNamespace`` for Python module namespaces.
|
||||||
|
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `PythonNamespace`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `__eq__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__eq__(self, value: PythonModuleNamespace) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `bool`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `value` | `PythonModuleNamespace` | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
A subclass of ``PythonNamespace`` for Python module namespaces.
|
||||||
|
|
||||||
|
:param name: The name of the namespace.
|
||||||
|
:type name: str
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
# Module `jcloud_docsgen.exceptions`
|
||||||
|
|
||||||
|
## Class `NamespaceError`
|
||||||
|
|
||||||
|
Base class for namespace errors.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `ValueError`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, *args: object, namespace_identifier: str = '') -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `args` | `object` | vararg | -- | -- |
|
||||||
|
| `namespace_identifier` | `str` | keyword-only | `''` | -- |
|
||||||
|
|
||||||
|
#### Method `__str__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__str__(self)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Base class for namespace errors.
|
||||||
|
|
||||||
|
## Class `InvalidNamespaceError`
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `NamespaceError`
|
||||||
|
|
||||||
|
## Class `NamespaceNotFoundError`
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `NamespaceError`
|
||||||
|
|
||||||
|
## Class `NamespaceExistsError`
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `NamespaceError`
|
||||||
|
|
||||||
|
## Class `PythonIdentifierError`
|
||||||
|
|
||||||
|
Base class for Python identifier errors.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `ValueError`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, *args: object, identifier: str = '') -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `args` | `object` | vararg | -- | -- |
|
||||||
|
| `identifier` | `str` | keyword-only | `''` | -- |
|
||||||
|
|
||||||
|
#### Method `__str__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__str__(self)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Base class for Python identifier errors.
|
||||||
|
|
||||||
|
## Class `InvalidPythonIdentifierError`
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `PythonIdentifierError`
|
||||||
|
|
||||||
|
## Class `PythonAnnotationError`
|
||||||
|
|
||||||
|
Base class for Python annotation errors.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `ValueError`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, *args: object, annotation: str = '') -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `args` | `object` | vararg | -- | -- |
|
||||||
|
| `annotation` | `str` | keyword-only | `''` | -- |
|
||||||
|
|
||||||
|
#### Method `__str__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__str__(self)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Base class for Python annotation errors.
|
||||||
|
|
||||||
|
## Class `InvalidPythonAnnotationError`
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `PythonAnnotationError`
|
||||||
|
|
||||||
|
## Class `PythonDefinitionError`
|
||||||
|
|
||||||
|
Base class for Python definition errors.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `ValueError`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, *args: object, definition: str = '') -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `args` | `object` | vararg | -- | -- |
|
||||||
|
| `definition` | `str` | keyword-only | `''` | -- |
|
||||||
|
|
||||||
|
#### Method `__str__`
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__str__(self)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Base class for Python definition errors.
|
||||||
|
|
||||||
|
## Class `PythonArgumentStructureError`
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `PythonDefinitionError`
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
# Module `jcloud_docsgen.utils`
|
||||||
|
|
||||||
|
The utilities for jcloud_docsgen
|
||||||
|
|
||||||
|
## Class `ExistingDirectory`
|
||||||
|
|
||||||
|
pathlib.Path subclass that is a directory.
|
||||||
|
|
||||||
|
ExistingDirectory represents a filesystem path but it also checks
|
||||||
|
whether the path exists and is not a file but a directory. It
|
||||||
|
inherits from pathlib.Path.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `pathlib.Path`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, path, *args, **kwargs) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `path` | -- | normal | -- | -- |
|
||||||
|
| `args` | -- | vararg | -- | -- |
|
||||||
|
| `kwargs` | -- | kwargs | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
pathlib.Path subclass that is a directory.
|
||||||
|
|
||||||
|
ExistingDirectory represents a filesystem path but it also checks
|
||||||
|
whether the path exists and is not a file but a directory. It
|
||||||
|
inherits from pathlib.Path.
|
||||||
|
|
||||||
|
## Class `ExistingFile`
|
||||||
|
|
||||||
|
pathlib.Path subclass that is a file.
|
||||||
|
|
||||||
|
ExistingFile represents a filesystem path but it also checks whether
|
||||||
|
the path exists and is not a directory but a file. It inherits from
|
||||||
|
pathlib.Path.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `pathlib.Path`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, path, *args, **kwargs) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `path` | -- | normal | -- | -- |
|
||||||
|
| `args` | -- | vararg | -- | -- |
|
||||||
|
| `kwargs` | -- | kwargs | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
pathlib.Path subclass that is a file.
|
||||||
|
|
||||||
|
ExistingFile represents a filesystem path but it also checks whether
|
||||||
|
the path exists and is not a directory but a file. It inherits from
|
||||||
|
pathlib.Path.
|
||||||
|
|
||||||
|
## Function `_quote`
|
||||||
|
|
||||||
|
Quotes a string.
|
||||||
|
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
_quote(string: str, quotation_mark: str) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The quoted string
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `string` | `str` | normal | -- | The string to quote |
|
||||||
|
| `quotation_mark` | `str` | normal | -- | The quotation mark |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Quotes a string.
|
||||||
|
|
||||||
|
:param string: The string to quote
|
||||||
|
:type string: str
|
||||||
|
:param quotation_mark: The quotation mark
|
||||||
|
:type quotation_mark: str
|
||||||
|
|
||||||
|
:return: The quoted string
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
## Function `human_readable_list`
|
||||||
|
|
||||||
|
Generates a human-readable list from a list.
|
||||||
|
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
human_readable_list(ls: list, final_separator: str = 'and', quotation_mark: str = '') -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The human-readable list.
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `ls` | `list` | normal | -- | The list. |
|
||||||
|
| `final_separator` | `str` | normal | `'and'` | The seperator between the last and the second-to-last element of the list. Default is 'and'. |
|
||||||
|
| `quotation_mark` | `str` | normal | `''` | The characters that are inserted before and after an element. Default is ''. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Generates a human-readable list from a list.
|
||||||
|
|
||||||
|
:param ls: The list.
|
||||||
|
:type ls: list
|
||||||
|
:param final_separator: The seperator between the last and the
|
||||||
|
second-to-last element of the list. Default
|
||||||
|
is 'and'.
|
||||||
|
:type final_separator: str
|
||||||
|
:param quotation_mark: The characters that are inserted before and
|
||||||
|
after an element. Default is ''.
|
||||||
|
|
||||||
|
:return: The human-readable list.
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
## Function `_list_type_names`
|
||||||
|
|
||||||
|
Converts a list of types into a list of their names (``__name__``
|
||||||
|
attribute)
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
_list_type_names(types: list[type]) -> list[str]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The list of the names of the types
|
||||||
|
|
||||||
|
Return type: `list[str]`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `types` | `list[type]` | normal | -- | The list of types |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Converts a list of types into a list of their names (``__name__``
|
||||||
|
attribute)
|
||||||
|
|
||||||
|
:param types: The list of types
|
||||||
|
:type types: list[type]
|
||||||
|
|
||||||
|
:return: The list of the names of the types
|
||||||
|
:rtype: list[str]
|
||||||
|
|
||||||
|
## Function `assert_that_is_instance`
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
assert_that_is_instance(obj: object, class_or_tuple: Union[type, types.UnionType, tuple[type, ...]]) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `obj` | `object` | normal | -- | -- |
|
||||||
|
| `class_or_tuple` | `Union[type, types.UnionType, tuple[type, ...]]` | normal | -- | -- |
|
||||||
|
|
||||||
|
## Function `non_empty_str`
|
||||||
|
|
||||||
|
Returns the value or ``None``.
|
||||||
|
|
||||||
|
Returns the value if it is not empty and not `Ǹone``; otherwise,
|
||||||
|
returns ``None``.
|
||||||
|
Please note: values are stripped, i. e. if the value consists only
|
||||||
|
of spaces, it is also considered empty.
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
non_empty_str(value: Union[str, None]) -> Union[str, None]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The value or ``None``.
|
||||||
|
|
||||||
|
Return type: `Union[str, None]`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `value` | `Union[str, None]` | normal | -- | The value. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Returns the value or ``None``.
|
||||||
|
|
||||||
|
Returns the value if it is not empty and not `Ǹone``; otherwise,
|
||||||
|
returns ``None``.
|
||||||
|
Please note: values are stripped, i. e. if the value consists only
|
||||||
|
of spaces, it is also considered empty.
|
||||||
|
|
||||||
|
:param value: The value.
|
||||||
|
:type value: Union[str, None]
|
||||||
|
|
||||||
|
:return: The value or ``None``.
|
||||||
|
:rtype: Union[str, None]
|
||||||
|
|
||||||
|
## Function `get_relative_path_segments`
|
||||||
|
|
||||||
|
Returns the path segments relative to ``root``.
|
||||||
|
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
get_relative_path_segments(path: pathlib.Path, root: pathlib.Path) -> list[str]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The path segments relative to ``root``.
|
||||||
|
|
||||||
|
Return type: `list[str]`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `path` | `pathlib.Path` | normal | -- | The path. |
|
||||||
|
| `root` | `pathlib.Path` | normal | -- | The root path. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Returns the path segments relative to ``root``.
|
||||||
|
|
||||||
|
:param path: The path.
|
||||||
|
:type path: pathlib.Path
|
||||||
|
:param root: The root path.
|
||||||
|
:type root: pathlib.Path
|
||||||
|
|
||||||
|
:return: The path segments relative to ``root``.
|
||||||
|
:rtype: list[str]
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2018 Marcin Kurczewski
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2010 - 2023 Holger Krekel and others
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2006-2022 by the respective authors (see AUTHORS file).
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
|
Copyright (c) 2015-2022 the pytest-datadir authors and contributors <see AUTHORS file>.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2004 Holger Krekel and others
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "jcloud-docsgen"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "A tool that generates Markdown documentation for software project source code"
|
||||||
|
dependencies = ["docstring-parser"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
package-dir = {"" = "src"}
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 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 . import cli
|
||||||
|
from .core import *
|
||||||
|
from .core import __all__ as _core__all__
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'cli',
|
||||||
|
*_core__all__
|
||||||
|
]
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
'''
|
||||||
|
The command line interface
|
||||||
|
'''
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'main'
|
||||||
|
]
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import pathlib
|
||||||
|
from .utils import ExistingDirectory
|
||||||
|
|
||||||
|
def existing_directory(path: str, *args, **kwargs) -> pathlib.Path:
|
||||||
|
try:
|
||||||
|
return ExistingDirectory(path, *args, **kwargs)
|
||||||
|
except (FileNotFoundError, NotADirectoryError) as e:
|
||||||
|
raise argparse.ArgumentTypeError(f'{e.filename}{": " + e.args[1] if len(e.args) > 1 else ""}')
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
argument_parser = argparse.ArgumentParser()
|
||||||
|
argument_parser.add_argument('project_directory', type = existing_directory, default = pathlib.Path('.'), help = 'The directory of the project')
|
||||||
|
args = argument_parser.parse_args()
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# 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 . import python
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'python'
|
||||||
|
]
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 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 ._core import *
|
||||||
|
from ._core import __all__ as _core__all__
|
||||||
|
from . import namespaces
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
*_core__all__,
|
||||||
|
'namespaces'
|
||||||
|
]
|
||||||
@@ -0,0 +1,954 @@
|
|||||||
|
# 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
|
||||||
|
from ...utils import ExistingDirectory, assert_that_is_instance, ExistingFile, non_empty_str, get_relative_path_segments
|
||||||
|
from ...exceptions import PythonArgumentStructureError, InvalidNamespaceError
|
||||||
|
from .namespaces import PythonModuleNamespace, PythonPackageNamespace
|
||||||
|
from .definitions import PythonDefinition, PythonFunctionDefinition, PythonAsyncFunctionDefinition, PythonClassDefinition
|
||||||
|
from .arguments import PythonArgumentKind, PythonFunctionArgument
|
||||||
|
from .docstrings import PythonDocstring
|
||||||
|
from collections.abc import Iterator
|
||||||
|
from typing import Union
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
import docstring_parser
|
||||||
|
import pathlib
|
||||||
|
import ast
|
||||||
|
import types
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'PythonDefinitionDocumentationIncludeSections',
|
||||||
|
'PythonDefinitionDocumentationGenerator',
|
||||||
|
'PythonModuleDocumentationGenerator',
|
||||||
|
'PythonDocumentationGenerator',
|
||||||
|
]
|
||||||
|
|
||||||
|
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]
|
||||||
|
'''
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def _group_argument_list(argument_list: list[PythonFunctionArgument]) -> tuple[list[PythonFunctionArgument], list[PythonFunctionArgument], Union[PythonFunctionArgument, None], list[PythonFunctionArgument], Union[PythonFunctionArgument, None]]:
|
||||||
|
'''
|
||||||
|
Groups the argument list into a list of positional-only arguments, a
|
||||||
|
list of normal arguments, the * argument if there is one, a list of
|
||||||
|
keyword-only arguments and the var-keyword-argument if there is one.
|
||||||
|
|
||||||
|
:param argument_list: The argument list.
|
||||||
|
:type argument_list: list[PythonFunctionArgument]
|
||||||
|
|
||||||
|
:rtype: tuple[list[PythonFunctionArgument], list[PythonFunctionArgument], Union[PythonFunctionArgument, None], list[PythonFunctionArgument], Union[PythonFunctionArgument, None]]
|
||||||
|
'''
|
||||||
|
|
||||||
|
if argument_list:
|
||||||
|
if argument_list[-1].kind != PythonArgumentKind.KWARGS and any([
|
||||||
|
arg.kind == PythonArgumentKind.KWARGS
|
||||||
|
for arg in argument_list
|
||||||
|
]):
|
||||||
|
raise PythonArgumentStructureError('arguments cannot follow var-keyword argument')
|
||||||
|
|
||||||
|
positional_only_args = []
|
||||||
|
normal_args = []
|
||||||
|
vararg = None
|
||||||
|
keyword_only_args = []
|
||||||
|
kwargs = None
|
||||||
|
|
||||||
|
for arg in argument_list:
|
||||||
|
if arg.kind == PythonArgumentKind.POSITIONAL_ONLY:
|
||||||
|
positional_only_args.append(arg)
|
||||||
|
|
||||||
|
if arg.kind == PythonArgumentKind.NORMAL:
|
||||||
|
normal_args.append(arg)
|
||||||
|
|
||||||
|
if arg.kind == PythonArgumentKind.VARARG:
|
||||||
|
if vararg is not None:
|
||||||
|
raise PythonArgumentStructureError('* argument may only appear once', definition = arg)
|
||||||
|
vararg = arg
|
||||||
|
|
||||||
|
if arg.kind == PythonArgumentKind.KEYWORD_ONLY:
|
||||||
|
keyword_only_args.append(arg)
|
||||||
|
|
||||||
|
if arg.kind == PythonArgumentKind.KWARGS:
|
||||||
|
kwargs = arg
|
||||||
|
|
||||||
|
return positional_only_args, normal_args, vararg, keyword_only_args, kwargs
|
||||||
|
|
||||||
|
def _signature_argument_list(argument_list: list[PythonFunctionArgument]) -> list[str]:
|
||||||
|
'''
|
||||||
|
Returns a list of the arguments as strings for a function signature.
|
||||||
|
|
||||||
|
:param argument_list: The argument list.
|
||||||
|
:type argument_list: list[PythonFunctionArgument]
|
||||||
|
|
||||||
|
:return: The list of function signature argument strings.
|
||||||
|
:rtype: list[str]
|
||||||
|
'''
|
||||||
|
|
||||||
|
positional_only_args, normal_args, vararg, keyword_only_args, kwargs = _group_argument_list(argument_list)
|
||||||
|
|
||||||
|
args = []
|
||||||
|
|
||||||
|
for arg in positional_only_args:
|
||||||
|
args.append(arg.signature_repr())
|
||||||
|
|
||||||
|
if positional_only_args:
|
||||||
|
args.append('/')
|
||||||
|
|
||||||
|
for arg in normal_args:
|
||||||
|
args.append(arg.signature_repr())
|
||||||
|
|
||||||
|
if vararg is not None:
|
||||||
|
args.append(vararg.signature_repr())
|
||||||
|
|
||||||
|
if keyword_only_args and vararg is None:
|
||||||
|
args.append('*')
|
||||||
|
|
||||||
|
for arg in keyword_only_args:
|
||||||
|
args.append(arg.signature_repr())
|
||||||
|
|
||||||
|
if kwargs is not None:
|
||||||
|
args.append(kwargs.signature_repr())
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
def _fit_row_length(expected: int, row: tuple) -> tuple:
|
||||||
|
'''
|
||||||
|
Fits the row length to the expected length.
|
||||||
|
|
||||||
|
If the row is too long, the tail, that makes it too long, is omitted.
|
||||||
|
If it is too short, it is filled with ``None`` (at the end).
|
||||||
|
|
||||||
|
:param expected: The expected length.
|
||||||
|
:type expected: int
|
||||||
|
:param row: The row.
|
||||||
|
:type row: tuple
|
||||||
|
'''
|
||||||
|
|
||||||
|
if expected < 0:
|
||||||
|
raise ValueError('expected must be larger than or equal 0.')
|
||||||
|
|
||||||
|
if len(row) > expected:
|
||||||
|
return row[:expected]
|
||||||
|
else:
|
||||||
|
return row + (None,) * (expected - len(row))
|
||||||
|
|
||||||
|
def _markdown_table(header: tuple, body: list[tuple], *, allow_markdown_table: bool = False, force_row_lengths: bool = False, one_line_values: bool = True, missing_value: str = '--') -> str:
|
||||||
|
'''
|
||||||
|
Generates a table.
|
||||||
|
|
||||||
|
If ``allow_markdown_table`` is ``False``, an alternative
|
||||||
|
representation is used.
|
||||||
|
|
||||||
|
:param headers: The table headers.
|
||||||
|
:type headers: tuple
|
||||||
|
:param body: The table body.
|
||||||
|
:type body: list[tuple]
|
||||||
|
:param allow_markdown_table: Controls whether a markdown table may be
|
||||||
|
used.
|
||||||
|
:type allow_markdown_table: bool
|
||||||
|
:param force_row_lengths: Controls whether an exception is raised if
|
||||||
|
a row does not have the correct length.
|
||||||
|
:type force_row_lengths: bool
|
||||||
|
:param one_line_values: Controls whether all newlines in all values
|
||||||
|
are replaced with spaces.
|
||||||
|
:type one_line_values: bool
|
||||||
|
:param missing_value: The string that is used if a value is missing
|
||||||
|
(only if ``force_row_lengths`` is ``False``).
|
||||||
|
:type missing_value: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not header or not body:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
header_len = len(header)
|
||||||
|
|
||||||
|
if allow_markdown_table:
|
||||||
|
table = '| ' + ' | '.join(header) + ' |\n|' + ' --- |' * len(header) + '\n'
|
||||||
|
|
||||||
|
|
||||||
|
for row in body:
|
||||||
|
if len(row) != header_len:
|
||||||
|
if force_row_lengths:
|
||||||
|
raise ValueError(f'expected {header_len} value{"s" if header_len > 1 else ""}, got {len(row)}')
|
||||||
|
else:
|
||||||
|
row = _fit_row_length(header_len, row)
|
||||||
|
|
||||||
|
table += '| ' + ' | '.join([
|
||||||
|
str(col if col is not None else missing_value).replace('\n', ' ')
|
||||||
|
if one_line_values
|
||||||
|
else str(col if col is not None else missing_value)
|
||||||
|
for col in row
|
||||||
|
]) + ' |\n'
|
||||||
|
else:
|
||||||
|
table = ''
|
||||||
|
|
||||||
|
for row in body:
|
||||||
|
if len(row) != header_len:
|
||||||
|
if force_row_lengths:
|
||||||
|
raise ValueError(f'expected {header_len} value{"s" if header_len > 1 else ""}, got {len(row)}')
|
||||||
|
else:
|
||||||
|
row = _fit_row_length(header_len, row)
|
||||||
|
|
||||||
|
sep = '- '
|
||||||
|
for i, col in enumerate(row):
|
||||||
|
table += sep + header[i] + ': ' + (str(col if col is not None else missing_value).replace('\n', ' ') \
|
||||||
|
if one_line_values \
|
||||||
|
else str(col if col is not None else missing_value)) + '\n\n'
|
||||||
|
sep = ' '
|
||||||
|
|
||||||
|
return table.strip()
|
||||||
|
|
||||||
|
def _is_docstring(docstring: Union[str, PythonDocstring, None]) -> bool:
|
||||||
|
'''
|
||||||
|
Checks whether the docstring is a docstring or an empty docstring.
|
||||||
|
|
||||||
|
:param docstring: The docstring.
|
||||||
|
:type docstring: Union[str, PythonDocstring, None]
|
||||||
|
|
||||||
|
:return: Whether there is a docstring.
|
||||||
|
:rtype: bool
|
||||||
|
'''
|
||||||
|
|
||||||
|
if docstring is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if isinstance(docstring, PythonDocstring):
|
||||||
|
docstring = docstring.docstring
|
||||||
|
|
||||||
|
return docstring.strip() != ''
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _FunctionParamInfo:
|
||||||
|
annotation: Union[str, None]
|
||||||
|
description: Union[str, None]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _NoDocstringTemplate:
|
||||||
|
description = None
|
||||||
|
params = []
|
||||||
|
raises = []
|
||||||
|
returns = None
|
||||||
|
many_returns = []
|
||||||
|
deprecation = None
|
||||||
|
examples = []
|
||||||
|
meta = []
|
||||||
|
short_description = None
|
||||||
|
long_description = None
|
||||||
|
blank_after_short_description = None
|
||||||
|
|
||||||
|
_NoDocstring = _NoDocstringTemplate()
|
||||||
|
|
||||||
|
def _argument_kind_human_readable(argument_kind: PythonArgumentKind) -> str:
|
||||||
|
'''
|
||||||
|
Returns the human-readable representation of a
|
||||||
|
``PythonArgumentKind``.
|
||||||
|
|
||||||
|
:param argument_kind: The argument kind.
|
||||||
|
:type argument_kind: PythonArgumentKind
|
||||||
|
|
||||||
|
:return: The human-readable representation.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
return argument_kind.name.lower().replace('_', '-')
|
||||||
|
|
||||||
|
def _function_params_info(arg_list: list[PythonFunctionArgument], docstring_arg_list: list[docstring_parser.DocstringParam]) -> dict[str, _FunctionParamInfo]:
|
||||||
|
'''
|
||||||
|
Returns a dictionary with the parameter info.
|
||||||
|
|
||||||
|
The keys of the dictionary are the parameter names and the values are
|
||||||
|
their types. If there is no type annotation for a parameter, the type
|
||||||
|
in the docstring is used as a fallback. If the docstring also does
|
||||||
|
not contain the type for a parameter, its value in the dictionary is
|
||||||
|
set to ``None``.
|
||||||
|
|
||||||
|
:param arg_list: The list of parameters from the function parameter
|
||||||
|
list.
|
||||||
|
:type arg_list: list[PythonFunctionArgument]
|
||||||
|
:param docstring_arg_list: The list of parameters from the docstring
|
||||||
|
parameter list.
|
||||||
|
|
||||||
|
:return: The dictionary with the parameter types.
|
||||||
|
:rtype: dict[str, _FunctionParamInfo]
|
||||||
|
'''
|
||||||
|
|
||||||
|
arg_annotations = {arg.name: _FunctionParamInfo(arg.annotation, None) for arg in arg_list}
|
||||||
|
|
||||||
|
for arg in docstring_arg_list:
|
||||||
|
if arg.arg_name in arg_annotations.keys():
|
||||||
|
arg_annotations[arg.arg_name] = _FunctionParamInfo(
|
||||||
|
arg_annotations[arg.arg_name].annotation or arg.type_name,
|
||||||
|
arg.description
|
||||||
|
)
|
||||||
|
|
||||||
|
return arg_annotations
|
||||||
|
|
||||||
|
class PythonDefinitionDocumentationIncludeSections:
|
||||||
|
'''
|
||||||
|
A class representing the included sections in a Python definition
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
:param signature: Whether include the signature section.
|
||||||
|
:type signature: bool
|
||||||
|
:param returns: Whether include the returns section.
|
||||||
|
:type returns: bool
|
||||||
|
:param params: Whether include the params section.
|
||||||
|
:type params: bool
|
||||||
|
:param exceptions: Whether include the exceptions section.
|
||||||
|
:type exceptions: bool
|
||||||
|
:param examples: Whether include the examples section.
|
||||||
|
:type examples: bool
|
||||||
|
:param docstring: Whether include the docstring section.
|
||||||
|
:type docstring: bool
|
||||||
|
:param bases: Whether include the bases section.
|
||||||
|
:type bases: bool
|
||||||
|
:param decorators: Whether include the decorators section.
|
||||||
|
:type decorators: bool
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
signature: bool = True,
|
||||||
|
returns: bool = True,
|
||||||
|
params: bool = True,
|
||||||
|
exceptions: bool = True,
|
||||||
|
examples: bool = True,
|
||||||
|
docstring: bool = True,
|
||||||
|
bases: bool = True,
|
||||||
|
decorators: bool = True,
|
||||||
|
class_body: bool = True
|
||||||
|
):
|
||||||
|
self.signature = signature
|
||||||
|
self.returns = returns
|
||||||
|
self.params = params
|
||||||
|
self.exceptions = exceptions
|
||||||
|
self.examples = examples
|
||||||
|
self.docstring = docstring
|
||||||
|
self.bases = bases
|
||||||
|
self.decorators = decorators
|
||||||
|
self.class_body = class_body
|
||||||
|
|
||||||
|
self._args = {
|
||||||
|
'signature': self.signature,
|
||||||
|
'returns': self.returns,
|
||||||
|
'params': self.params,
|
||||||
|
'exceptions': self.exceptions,
|
||||||
|
'examples': self.examples,
|
||||||
|
'docstring': self.docstring,
|
||||||
|
'bases': self.bases,
|
||||||
|
'decorators': self.decorators,
|
||||||
|
'class_body': self.class_body,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return type(self).__name__+ '(' + ', '.join(
|
||||||
|
[
|
||||||
|
key + ' = ' + repr(value)
|
||||||
|
for key, value in self._args.items()
|
||||||
|
]
|
||||||
|
) + ')'
|
||||||
|
|
||||||
|
def __eq__(self, value) -> str:
|
||||||
|
if not isinstance(value, PythonDefinitionDocumentationIncludeSections):
|
||||||
|
return False
|
||||||
|
return self._args == value._args
|
||||||
|
|
||||||
|
class PythonDefinitionDocumentationGenerator:
|
||||||
|
'''
|
||||||
|
A documentation generator for a Python definition.
|
||||||
|
|
||||||
|
:param definition: The Python definition.
|
||||||
|
:type definition: PythonDefinition
|
||||||
|
:param include_sections: The sections that will be included.
|
||||||
|
:type include_sections: PythonDefinitionDocumentationIncludeSections
|
||||||
|
:param level: The level of the definition in the markdown content.
|
||||||
|
:type level: int
|
||||||
|
:param allow_html: Controls whether the generated documentation may
|
||||||
|
contain HTML.
|
||||||
|
:type allow_html: bool
|
||||||
|
:param allow_tables: Controls whether the generated documentation may
|
||||||
|
contain tables.
|
||||||
|
:type allow_tables: bool
|
||||||
|
:param skip_empty_sections: Controls whether sections that does not
|
||||||
|
have any content (such as the parameters
|
||||||
|
section if there are no parameters taken)
|
||||||
|
are skipped. If ``False``, a short text
|
||||||
|
(such as 'This function takes no
|
||||||
|
parameters') is used.
|
||||||
|
:type skip_empty_sections: bool
|
||||||
|
:param is_method: Controls whether the definition is a class method.
|
||||||
|
:type is_method: bool
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
definition: PythonDefinition,
|
||||||
|
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(),
|
||||||
|
level: int = 1,
|
||||||
|
*,
|
||||||
|
allow_html: bool = True,
|
||||||
|
allow_tables: bool = False,
|
||||||
|
skip_empty_sections: bool = True,
|
||||||
|
is_method: bool = False
|
||||||
|
) -> None:
|
||||||
|
self.definition = definition
|
||||||
|
self.include_sections = include_sections
|
||||||
|
|
||||||
|
self.level = level
|
||||||
|
self.allow_html = allow_html
|
||||||
|
self.allow_tables = allow_tables
|
||||||
|
self.skip_empty_sections = skip_empty_sections
|
||||||
|
self.is_method = is_method
|
||||||
|
|
||||||
|
self._docstring_cache = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _docstring(self):
|
||||||
|
if self._docstring_cache is None:
|
||||||
|
self._docstring_cache = self.definition.doc.parse() if _is_docstring(self.definition.doc) else _NoDocstring
|
||||||
|
return self._docstring_cache
|
||||||
|
|
||||||
|
def generate_signature(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the signature of the definition. Works only if the
|
||||||
|
definition is a function or asynchronous function definition.
|
||||||
|
|
||||||
|
:raises TypeError: If the definition is a class definition.
|
||||||
|
|
||||||
|
:return: The signature.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
if isinstance(self.definition, PythonClassDefinition):
|
||||||
|
raise TypeError('Cannot generate a signature of a class definition.')
|
||||||
|
|
||||||
|
signature = self.definition.name + '(' + ', '.join(_signature_argument_list(self.definition.args)) + ')'
|
||||||
|
|
||||||
|
if self.definition.returns is not None:
|
||||||
|
signature += ' -> ' + self.definition.returns
|
||||||
|
|
||||||
|
return signature.strip()
|
||||||
|
|
||||||
|
def _generate_signature_section(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the signature section.
|
||||||
|
|
||||||
|
:return: The signature section.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self.level * '#' + '# ' + ('Function' if not self.is_method else 'Method') +' signature\n\n```python\n' + self.generate_signature() + '\n```'
|
||||||
|
|
||||||
|
def _generate_docstring_section(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the docstring section.
|
||||||
|
|
||||||
|
:return: The docstring section.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
md = ''
|
||||||
|
|
||||||
|
if _is_docstring(self.definition.doc) or not self.skip_empty_sections:
|
||||||
|
if self.allow_html:
|
||||||
|
md += '<details><summary><h' + str(self.level + 1) + ' style="display:inline">Docstring' + '</h' + str(self.level + 1) + '></summary>'
|
||||||
|
else:
|
||||||
|
md += self.level * '#' + '# Docstring\n\n'
|
||||||
|
|
||||||
|
if _is_docstring(self.definition.doc):
|
||||||
|
md += self.definition.doc.docstring
|
||||||
|
elif not self.skip_empty_sections:
|
||||||
|
md += 'There is no docstring to show.'
|
||||||
|
|
||||||
|
if _is_docstring(self.definition.doc) or not self.skip_empty_sections:
|
||||||
|
if self.allow_html:
|
||||||
|
md += '</details>'
|
||||||
|
md += '\n\n'
|
||||||
|
|
||||||
|
return md.strip()
|
||||||
|
|
||||||
|
def _generate_params_section(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the parameters section.
|
||||||
|
|
||||||
|
:return: The parameters section.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
md = ''
|
||||||
|
|
||||||
|
if self.definition.args or not self.skip_empty_sections:
|
||||||
|
md += self.level * '#' + '# Parameters\n\n'
|
||||||
|
|
||||||
|
params_types = _function_params_info(self.definition.args, self._docstring.params)
|
||||||
|
|
||||||
|
if params_types:
|
||||||
|
md += _markdown_table(['Parameter', 'Type', 'Kind', 'Default value', 'Description'], [
|
||||||
|
(
|
||||||
|
'`' + arg.name + '`',
|
||||||
|
('`' + params_types.get(arg.name).annotation + '`') if params_types.get(arg.name).annotation is not None else None,
|
||||||
|
_argument_kind_human_readable(arg.kind),
|
||||||
|
('`' + arg.default + '`') if arg.default is not None else None,
|
||||||
|
non_empty_str(params_types.get(arg.name).description)
|
||||||
|
)
|
||||||
|
for arg in self.definition.args
|
||||||
|
], allow_markdown_table = self.allow_tables)
|
||||||
|
|
||||||
|
md += '\n\n'
|
||||||
|
|
||||||
|
elif not self.skip_empty_sections:
|
||||||
|
md += 'This function takes no parameters.'
|
||||||
|
|
||||||
|
return md.strip()
|
||||||
|
|
||||||
|
def _generate_returns_section(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the returns section.
|
||||||
|
|
||||||
|
:return: The returns section.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
md = ''
|
||||||
|
|
||||||
|
if self._docstring.returns or self.definition.returns or not self.skip_empty_sections:
|
||||||
|
md += self.level * '#' + '# Returns\n\n'
|
||||||
|
|
||||||
|
returns = False
|
||||||
|
if self._docstring.returns:
|
||||||
|
if self._docstring.returns.description is not None:
|
||||||
|
returns = True
|
||||||
|
md += self._docstring.returns.description + '\n\n'
|
||||||
|
|
||||||
|
rtype = self.definition.returns
|
||||||
|
if rtype is None and self._docstring.returns is not None:
|
||||||
|
rtype = self._docstring.returns.type_name
|
||||||
|
|
||||||
|
if rtype is not None:
|
||||||
|
returns = True
|
||||||
|
md += 'Return type: `' + rtype + '`'
|
||||||
|
|
||||||
|
if not self.skip_empty_sections and not returns:
|
||||||
|
md += 'This function does not provide any information about its return value.'
|
||||||
|
|
||||||
|
return md.strip()
|
||||||
|
|
||||||
|
def _generate_exceptions_section(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the exceptions section.
|
||||||
|
|
||||||
|
:return: The exceptions section.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
md = ''
|
||||||
|
|
||||||
|
if self._docstring.raises or not self.skip_empty_sections:
|
||||||
|
md += self.level * '#' + '# Exceptions\n\n'
|
||||||
|
|
||||||
|
if self._docstring.raises:
|
||||||
|
md += _markdown_table(('Exception', 'Description'), [
|
||||||
|
(('`' + exception.type_name + '`') if exception.type_name is not None else None, non_empty_str(exception.description))
|
||||||
|
for exception in self._docstring.raises
|
||||||
|
], allow_markdown_table = self.allow_tables)
|
||||||
|
elif not self.skip_empty_sections:
|
||||||
|
md += 'This function raises no exceptions.'
|
||||||
|
|
||||||
|
return md.strip()
|
||||||
|
|
||||||
|
def _generate_examples_section(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the examples section.
|
||||||
|
|
||||||
|
:return: The examples section.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
md = ''
|
||||||
|
|
||||||
|
if self._docstring.examples or not self.skip_empty_sections:
|
||||||
|
md += self.level * '#' + '# Examples\n\n'
|
||||||
|
|
||||||
|
examples = []
|
||||||
|
for example in self._docstring.examples:
|
||||||
|
examples.append('```python\n' +\
|
||||||
|
(example.snippet or example.description) + \
|
||||||
|
'\n```')
|
||||||
|
md += '\n\n---\n\n'.join(examples)
|
||||||
|
|
||||||
|
if not self._docstring.examples and not self.skip_empty_sections:
|
||||||
|
md += 'There are no examples.'
|
||||||
|
|
||||||
|
return md.strip()
|
||||||
|
|
||||||
|
def _generate_decorators_section(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the decorators section.
|
||||||
|
|
||||||
|
:return: The decorators section.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
md = ''
|
||||||
|
|
||||||
|
if self.definition.decorators or not self.skip_empty_sections:
|
||||||
|
md += self.level * '#' + '# Decorators\n\n'
|
||||||
|
|
||||||
|
for decorator in self.definition.decorators:
|
||||||
|
md += '```python\n' + decorator + '\n```\n\n'
|
||||||
|
|
||||||
|
if not self.definition.decorators and not self.skip_empty_sections:
|
||||||
|
md += 'This function or class does not have any decorators.'
|
||||||
|
|
||||||
|
return md.strip()
|
||||||
|
|
||||||
|
def _generate_bases_section(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the bases section.
|
||||||
|
|
||||||
|
:return: The bases section.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
md = ''
|
||||||
|
|
||||||
|
if self.definition.bases or not self.skip_empty_sections:
|
||||||
|
md += self.level * '#' + '# Base classes\n\n'
|
||||||
|
|
||||||
|
for base in self.definition.bases:
|
||||||
|
md += '- `' + base + '`\n'
|
||||||
|
|
||||||
|
if not self.definition.bases and not self.skip_empty_sections:
|
||||||
|
md += 'This class does not have any base classes.'
|
||||||
|
|
||||||
|
return md.strip()
|
||||||
|
|
||||||
|
def _generate_body_section(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the body section.
|
||||||
|
|
||||||
|
:return: The body section.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
md = ''
|
||||||
|
|
||||||
|
if self.definition.body or not self.skip_empty_sections:
|
||||||
|
md += self.level * '#' + '# Body\n\n'
|
||||||
|
|
||||||
|
for method_or_class in self.definition.body:
|
||||||
|
md += PythonDefinitionDocumentationGenerator(
|
||||||
|
method_or_class,
|
||||||
|
self.include_sections,
|
||||||
|
level = self.level + 2,
|
||||||
|
allow_html = self.allow_html,
|
||||||
|
allow_tables = self.allow_tables,
|
||||||
|
skip_empty_sections = self.skip_empty_sections,
|
||||||
|
is_method = True
|
||||||
|
).generate_documentation() + '\n\n'
|
||||||
|
|
||||||
|
if not self.definition.body and not self.skip_empty_sections:
|
||||||
|
md += 'This class does not have a body.'
|
||||||
|
|
||||||
|
return md.strip()
|
||||||
|
|
||||||
|
def generate_documentation(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the documentation in the markdown format.
|
||||||
|
|
||||||
|
:return: The documentation.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
md = self.level * '#' + ' '
|
||||||
|
|
||||||
|
if isinstance(self.definition, PythonAsyncFunctionDefinition):
|
||||||
|
md += 'Asynchronous ' + ('function' if not self.is_method else 'method')
|
||||||
|
elif isinstance(self.definition, PythonFunctionDefinition):
|
||||||
|
if self.is_method and self.definition.name == '__init__':
|
||||||
|
md += 'Constructor (method'
|
||||||
|
else:
|
||||||
|
md += 'Function' if not self.is_method else 'Method'
|
||||||
|
else:
|
||||||
|
md += 'Class'
|
||||||
|
|
||||||
|
md += ' `' + self.definition.name + '`'
|
||||||
|
if isinstance(self.definition, PythonFunctionDefinition) and not isinstance(self.definition, PythonAsyncFunctionDefinition) and self.is_method and self.definition.name == '__init__':
|
||||||
|
md += ')'
|
||||||
|
md += '\n\n'
|
||||||
|
|
||||||
|
if self._docstring.description is not None:
|
||||||
|
md += self._docstring.description + '\n\n'
|
||||||
|
|
||||||
|
if isinstance(self.definition, PythonFunctionDefinition):
|
||||||
|
if self.include_sections.signature:
|
||||||
|
md += self._generate_signature_section() + '\n\n'
|
||||||
|
|
||||||
|
if self.include_sections.returns:
|
||||||
|
md += self._generate_returns_section() + '\n\n'
|
||||||
|
|
||||||
|
if self.include_sections.params:
|
||||||
|
md += self._generate_params_section() + '\n\n'
|
||||||
|
|
||||||
|
if self.include_sections.exceptions:
|
||||||
|
md += self._generate_exceptions_section() + '\n\n'
|
||||||
|
|
||||||
|
if isinstance(self.definition, PythonClassDefinition):
|
||||||
|
if self.include_sections.bases:
|
||||||
|
md += self._generate_bases_section() + '\n\n'
|
||||||
|
|
||||||
|
if self.include_sections.class_body:
|
||||||
|
md += self._generate_body_section() + '\n\n'
|
||||||
|
|
||||||
|
if self.include_sections.decorators:
|
||||||
|
md += self._generate_decorators_section() + '\n\n'
|
||||||
|
|
||||||
|
if self.include_sections.examples:
|
||||||
|
md += self._generate_examples_section() + '\n\n'
|
||||||
|
|
||||||
|
if self.include_sections.docstring:
|
||||||
|
md += self._generate_docstring_section() + '\n\n'
|
||||||
|
|
||||||
|
return md.strip()
|
||||||
|
|
||||||
|
class PythonModuleDocumentationGenerator:
|
||||||
|
'''
|
||||||
|
A documentation generator for a Python module.
|
||||||
|
|
||||||
|
:param module_path: The path of the module file.
|
||||||
|
:type module_path: ExistingFile
|
||||||
|
:param src_directory: The directory of the source code. It is
|
||||||
|
optional and only used for making the module
|
||||||
|
identifiers in the documentation relative.
|
||||||
|
:type src_directory: Union[ExistingDirectory, None]
|
||||||
|
:param include_sections: The sections that will be included.
|
||||||
|
:type include_sections: PythonDefinitionDocumentationIncludeSections
|
||||||
|
:param kwargs: The options for ``PythonDefinitionDocumentationGenerator``.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
module_path: ExistingFile,
|
||||||
|
src_directory: Union[ExistingDirectory, None] = None,
|
||||||
|
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(),
|
||||||
|
**kwargs
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
assert_that_is_instance(module_path, ExistingFile)
|
||||||
|
assert_that_is_instance(src_directory, (ExistingDirectory, types.NoneType))
|
||||||
|
|
||||||
|
if module_path.name.count('.') > 1:
|
||||||
|
raise InvalidNamespaceError('namespace identifier cannot contain more than one dot', namespace_identifier = module_path.name)
|
||||||
|
|
||||||
|
self.module_path = pathlib.Path(str(module_path))
|
||||||
|
self.src_directory = pathlib.Path(str(src_directory)) if src_directory is not None else None
|
||||||
|
|
||||||
|
self.include_sections = include_sections
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
self._source_code_cache = None
|
||||||
|
self._ast_cache = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _source_code(self) -> str:
|
||||||
|
if self._source_code_cache is None:
|
||||||
|
self._source_code_cache = self.module_path.read_text()
|
||||||
|
return self._source_code_cache
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _ast(self) -> str:
|
||||||
|
if self._ast_cache is None:
|
||||||
|
self._ast_cache = ast.parse(self._source_code)
|
||||||
|
return self._ast_cache
|
||||||
|
|
||||||
|
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 = self._ast
|
||||||
|
|
||||||
|
return _collect_definitions(tree)
|
||||||
|
|
||||||
|
def generate_documentation(self) -> str:
|
||||||
|
'''
|
||||||
|
Generates the documentation in the markdown format.
|
||||||
|
|
||||||
|
:return: The documentation.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
md = '# Module `'
|
||||||
|
|
||||||
|
if self.src_directory is not None:
|
||||||
|
module_identifier = '.'.join(get_relative_path_segments(self.module_path, self.src_directory))
|
||||||
|
if module_identifier.endswith('.py'):
|
||||||
|
md += module_identifier[:-3]
|
||||||
|
else:
|
||||||
|
md += module_identifier
|
||||||
|
else:
|
||||||
|
md += self.module_path.stem
|
||||||
|
|
||||||
|
md += '`\n\n'
|
||||||
|
|
||||||
|
docstring = ast.get_docstring(self._ast)
|
||||||
|
if docstring is not None:
|
||||||
|
md += docstring + '\n\n'
|
||||||
|
|
||||||
|
for definition in self.collect_definitions():
|
||||||
|
md += PythonDefinitionDocumentationGenerator(
|
||||||
|
definition,
|
||||||
|
self.include_sections,
|
||||||
|
2,
|
||||||
|
**self.kwargs
|
||||||
|
).generate_documentation() + '\n\n'
|
||||||
|
|
||||||
|
return md.strip()
|
||||||
|
|
||||||
|
def _generate_package_namespace_documentation(
|
||||||
|
namespace: PythonPackageNamespace,
|
||||||
|
documentation_directory: pathlib.Path,
|
||||||
|
root_directory: pathlib.Path,
|
||||||
|
dir: pathlib.Path,
|
||||||
|
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(),
|
||||||
|
kwargs: dict = {}
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Generates the documentation for a package namespace and writes it to
|
||||||
|
the documentation directory.
|
||||||
|
|
||||||
|
:param namespace: The namespace.
|
||||||
|
:type namespace: PythonPackageNamespace
|
||||||
|
:param documentation_directory: The directory for the documentation.
|
||||||
|
:type documentation_directory: pathlib.Path
|
||||||
|
:param root_directory: The root directory.
|
||||||
|
:type root_directory: pathlib.Path
|
||||||
|
:param dir: The directory of the namespace.
|
||||||
|
:type dir: pathlib.Path
|
||||||
|
:param include_sections: The sections to be included in the documentation.
|
||||||
|
:type include_sections: PythonDefinitionDocumentationIncludeSections
|
||||||
|
:param kwargs: The options for the documentation generator.
|
||||||
|
'''
|
||||||
|
|
||||||
|
documentation_directory = pathlib.Path(str(documentation_directory))
|
||||||
|
|
||||||
|
for sub_namespace in namespace.sub_namespaces:
|
||||||
|
if isinstance(sub_namespace, PythonPackageNamespace):
|
||||||
|
(documentation_directory / sub_namespace.name).mkdir(mode = 0o700, exist_ok = True)
|
||||||
|
_generate_package_namespace_documentation(
|
||||||
|
sub_namespace,
|
||||||
|
documentation_directory / sub_namespace.name,
|
||||||
|
root_directory, dir / sub_namespace.name,
|
||||||
|
include_sections,
|
||||||
|
kwargs
|
||||||
|
)
|
||||||
|
elif isinstance(sub_namespace, PythonModuleNamespace):
|
||||||
|
(
|
||||||
|
documentation_directory / (sub_namespace.name + '.md')
|
||||||
|
).write_text(
|
||||||
|
PythonModuleDocumentationGenerator(
|
||||||
|
ExistingFile(str(dir / (sub_namespace.name + '.py'))),
|
||||||
|
ExistingDirectory(str(root_directory)),
|
||||||
|
include_sections,
|
||||||
|
**kwargs
|
||||||
|
).generate_documentation()
|
||||||
|
)
|
||||||
|
|
||||||
|
class PythonDocumentationGenerator:
|
||||||
|
'''
|
||||||
|
The class for the documentation generator.
|
||||||
|
|
||||||
|
:param project_directory: The directory of the project.
|
||||||
|
:type project_directory: ExistingDirectory
|
||||||
|
:param docs_directory: The directory for the generated documentation.
|
||||||
|
:type docs_directory: ExistingDirectory
|
||||||
|
:param include_sections: The sections to be included in the documentation.
|
||||||
|
:type include_sections: PythonDefinitionDocumentationIncludeSections
|
||||||
|
:param kwargs: The options for ``PythonDefinitionDocumentationGenerator``.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
project_directory: ExistingDirectory,
|
||||||
|
docs_directory: ExistingDirectory,
|
||||||
|
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(),
|
||||||
|
**kwargs
|
||||||
|
) -> None:
|
||||||
|
assert_that_is_instance(project_directory, ExistingDirectory)
|
||||||
|
assert_that_is_instance(docs_directory, ExistingDirectory)
|
||||||
|
|
||||||
|
self.project_directory = pathlib.Path(str(project_directory))
|
||||||
|
self.docs_directory = pathlib.Path(str(docs_directory))
|
||||||
|
self.include_sections = include_sections
|
||||||
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def _namespace(self, directory: pathlib.Path) -> PythonPackageNamespace:
|
||||||
|
namespace = PythonPackageNamespace(directory.name, [])
|
||||||
|
|
||||||
|
for entry in directory.iterdir():
|
||||||
|
if entry.is_dir():
|
||||||
|
namespace.add(self._namespace(entry))
|
||||||
|
elif entry.suffix == '.py':
|
||||||
|
namespace.add(PythonModuleNamespace(entry.stem))
|
||||||
|
|
||||||
|
return namespace
|
||||||
|
|
||||||
|
def namespace(self) -> list[PythonPackageNamespace]:
|
||||||
|
'''
|
||||||
|
Returns the project as a namespace.
|
||||||
|
|
||||||
|
:return: The project as a namespace.
|
||||||
|
:rtype: list[PythonPackageNamespace]
|
||||||
|
'''
|
||||||
|
|
||||||
|
src_dir = self.project_directory / 'src'
|
||||||
|
|
||||||
|
return [
|
||||||
|
self._namespace(dir)
|
||||||
|
for dir in src_dir.iterdir() if dir.is_dir()
|
||||||
|
]
|
||||||
|
|
||||||
|
def generate_documentation(self) -> None:
|
||||||
|
'''
|
||||||
|
Generates the full project documentation and writes it to the
|
||||||
|
documentation directory.
|
||||||
|
'''
|
||||||
|
|
||||||
|
_generate_package_namespace_documentation(
|
||||||
|
PythonPackageNamespace('src', self.namespace()),
|
||||||
|
self.docs_directory,
|
||||||
|
self.project_directory / 'src',
|
||||||
|
self.project_directory / 'src',
|
||||||
|
self.include_sections,
|
||||||
|
self.kwargs
|
||||||
|
)
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
# 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
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Union
|
||||||
|
import ast
|
||||||
|
from ...utils import assert_that_is_instance
|
||||||
|
from ...exceptions import InvalidPythonIdentifierError, InvalidPythonAnnotationError, PythonArgumentStructureError
|
||||||
|
import keyword
|
||||||
|
import types
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'PythonArgumentKind',
|
||||||
|
'PythonFunctionArgument',
|
||||||
|
'PythonASTArgumentsListParser'
|
||||||
|
]
|
||||||
|
|
||||||
|
class PythonArgumentKind(Enum):
|
||||||
|
NORMAL = 0
|
||||||
|
POSITIONAL_ONLY = 1
|
||||||
|
KEYWORD_ONLY = 2
|
||||||
|
VARARG = 3
|
||||||
|
KWARGS = 4
|
||||||
|
|
||||||
|
class PythonFunctionArgument:
|
||||||
|
'''
|
||||||
|
Represents an argument of a Python function.
|
||||||
|
|
||||||
|
:param name: The name of the argument.
|
||||||
|
:type name: str
|
||||||
|
:param kind: The kind of the argument.
|
||||||
|
:type kind: ArgumentKind
|
||||||
|
:param default: The default value of the argument.
|
||||||
|
:type default: Union[str, None]
|
||||||
|
:param annotation: The type annotation of the argument.
|
||||||
|
:type annotation: Union[str, None]
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, name: str, kind: PythonArgumentKind, default: Union[str, None], annotation: Union[str, None]) -> None:
|
||||||
|
assert_that_is_instance(name, str)
|
||||||
|
assert_that_is_instance(kind, PythonArgumentKind)
|
||||||
|
assert_that_is_instance(default, (str, types.NoneType))
|
||||||
|
assert_that_is_instance(annotation, (str, types.NoneType))
|
||||||
|
|
||||||
|
# check whether name is a valid identifier
|
||||||
|
if not name or not name.isidentifier() or keyword.iskeyword(name):
|
||||||
|
raise InvalidPythonIdentifierError('invalid identifier', identifier = name)
|
||||||
|
|
||||||
|
# check whether the annotation is a valid annotation
|
||||||
|
# Currently, it is only checked whether the parameter is not
|
||||||
|
# empty.
|
||||||
|
if annotation is not None and not annotation:
|
||||||
|
raise InvalidPythonAnnotationError('invalid annotation', annotation = annotation)
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.kind = kind
|
||||||
|
self.default = default
|
||||||
|
self.annotation = annotation
|
||||||
|
|
||||||
|
if self.kind in (PythonArgumentKind.VARARG, PythonArgumentKind.KWARGS) and self.default is not None:
|
||||||
|
raise PythonArgumentStructureError('* or ** arguments cannot have default values', definition = self.signature_repr())
|
||||||
|
|
||||||
|
def __eq__(self, value: PythonFunctionArgument) -> bool:
|
||||||
|
if not isinstance(value, PythonFunctionArgument):
|
||||||
|
return False
|
||||||
|
return self.name == value.name and self.kind == value.kind and self.default == value.default and self.annotation == value.annotation
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return type(self).__name__ + repr((
|
||||||
|
self.name,
|
||||||
|
self.kind,
|
||||||
|
self.default,
|
||||||
|
self.annotation
|
||||||
|
))
|
||||||
|
|
||||||
|
def signature_repr(self) -> str:
|
||||||
|
'''
|
||||||
|
Returns the function signature representation of the argument.
|
||||||
|
|
||||||
|
:return: The string representation of the argument.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
if self.kind == PythonArgumentKind.KWARGS:
|
||||||
|
str_repr = '**'
|
||||||
|
elif self.kind == PythonArgumentKind.VARARG:
|
||||||
|
str_repr = '*'
|
||||||
|
else:
|
||||||
|
str_repr = ''
|
||||||
|
|
||||||
|
str_repr += self.name
|
||||||
|
|
||||||
|
if self.annotation is not None:
|
||||||
|
str_repr += ': ' + self.annotation
|
||||||
|
|
||||||
|
if self.default is not None:
|
||||||
|
str_repr += ' = ' + self.default
|
||||||
|
|
||||||
|
return str_repr
|
||||||
|
|
||||||
|
class PythonASTArgumentsListParser:
|
||||||
|
'''
|
||||||
|
A parser for making ``PythonFunctionArgument`` objects from an
|
||||||
|
``ast.arguments`` arguments list.
|
||||||
|
|
||||||
|
:param ast_arguments_list: The ``ast.arguments`` arguments list.
|
||||||
|
:type ast_arguments_list: ast.arguments
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, ast_arguments_list: ast.arguments) -> None:
|
||||||
|
self.ast_arguments_list = ast_arguments_list
|
||||||
|
|
||||||
|
def to_argument_list(self) -> list[PythonFunctionArgument]:
|
||||||
|
'''
|
||||||
|
Converts the AST arguments list to a list of Python function
|
||||||
|
argument objects.
|
||||||
|
|
||||||
|
:return: The list of Python function argument objects.
|
||||||
|
:rtype: list[PythonFunctionArgument]
|
||||||
|
'''
|
||||||
|
|
||||||
|
defaults = [None for _ in range(len(
|
||||||
|
self.ast_arguments_list.posonlyargs +
|
||||||
|
self.ast_arguments_list.args +
|
||||||
|
self.ast_arguments_list.kwonlyargs
|
||||||
|
) - len(self.ast_arguments_list.defaults) - len(self.ast_arguments_list.kw_defaults))] + \
|
||||||
|
[ast.unparse(d) for d in self.ast_arguments_list.defaults] + \
|
||||||
|
[ast.unparse(d) if d is not None else None for d in self.ast_arguments_list.kw_defaults]
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
arguments = []
|
||||||
|
for arg in self.ast_arguments_list.posonlyargs:
|
||||||
|
if arg.annotation is None:
|
||||||
|
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.POSITIONAL_ONLY, defaults[i], None))
|
||||||
|
else:
|
||||||
|
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.POSITIONAL_ONLY, defaults[i], ast.unparse(arg.annotation)))
|
||||||
|
i += 1
|
||||||
|
for arg in self.ast_arguments_list.args:
|
||||||
|
if arg.annotation is None:
|
||||||
|
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.NORMAL, defaults[i], None))
|
||||||
|
else:
|
||||||
|
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.NORMAL, defaults[i], ast.unparse(arg.annotation)))
|
||||||
|
i += 1
|
||||||
|
if self.ast_arguments_list.vararg is not None:
|
||||||
|
if self.ast_arguments_list.vararg.annotation is None:
|
||||||
|
arguments.append(PythonFunctionArgument(self.ast_arguments_list.vararg.arg, PythonArgumentKind.VARARG, None, None))
|
||||||
|
else:
|
||||||
|
arguments.append(PythonFunctionArgument(self.ast_arguments_list.vararg.arg, PythonArgumentKind.VARARG, None, ast.unparse(self.ast_arguments_list.vararg.annotation)))
|
||||||
|
for arg in self.ast_arguments_list.kwonlyargs:
|
||||||
|
if arg.annotation is None:
|
||||||
|
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.KEYWORD_ONLY, defaults[i], None))
|
||||||
|
else:
|
||||||
|
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.KEYWORD_ONLY, defaults[i], ast.unparse(arg.annotation)))
|
||||||
|
i += 1
|
||||||
|
if self.ast_arguments_list.kwarg is not None:
|
||||||
|
if self.ast_arguments_list.kwarg.annotation is None:
|
||||||
|
arguments.append(PythonFunctionArgument(self.ast_arguments_list.kwarg.arg, PythonArgumentKind.KWARGS, None, None))
|
||||||
|
else:
|
||||||
|
arguments.append(PythonFunctionArgument(self.ast_arguments_list.kwarg.arg, PythonArgumentKind.KWARGS, None, ast.unparse(self.ast_arguments_list.kwarg.annotation)))
|
||||||
|
|
||||||
|
return arguments
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
# 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
|
||||||
|
from typing import Union
|
||||||
|
import ast
|
||||||
|
from .arguments import PythonFunctionArgument, PythonASTArgumentsListParser
|
||||||
|
from .docstrings import PythonDocstring
|
||||||
|
import keyword
|
||||||
|
from ...exceptions import InvalidPythonIdentifierError
|
||||||
|
from ...utils import assert_that_is_instance
|
||||||
|
import types
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'PythonDefinition',
|
||||||
|
'PythonFunctionDefinition',
|
||||||
|
'PythonAsyncFunctionDefinition',
|
||||||
|
'PythonClassDefinition'
|
||||||
|
]
|
||||||
|
|
||||||
|
def _python_docstring(doc: Union[str, None]) -> Union[PythonDocstring, None]:
|
||||||
|
'''
|
||||||
|
Returns the PythonDocstring object if the docstring is a string and
|
||||||
|
not ``None``.
|
||||||
|
|
||||||
|
:param doc: The docstring.
|
||||||
|
:type doc: Union[str, None]
|
||||||
|
|
||||||
|
:return: The PythonDocstring object or ``None``.
|
||||||
|
:rtype: Union[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: Union[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: Union[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: Union[str, None]
|
||||||
|
:param doc: The docstring.
|
||||||
|
:type doc: Union[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: Union[str, None], doc: Union[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: Union[str, None]
|
||||||
|
:param doc: The docstring.
|
||||||
|
:type doc: Union[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: Union[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: Union[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))
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# 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 enum import Enum
|
||||||
|
import docstring_parser
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'PythonDocstring'
|
||||||
|
]
|
||||||
|
|
||||||
|
class PythonDocstring:
|
||||||
|
'''
|
||||||
|
Represents a Python docstring.
|
||||||
|
|
||||||
|
:param docstring: The docstring as a string.
|
||||||
|
:type docstring: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, docstring: str) -> None:
|
||||||
|
self.docstring = docstring
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return type(self).__name__ + '(' + repr(self.docstring) + ')'
|
||||||
|
|
||||||
|
def __eq__(self, value) -> bool:
|
||||||
|
if not isinstance(value, type(self)):
|
||||||
|
return False
|
||||||
|
return self.docstring == value.docstring
|
||||||
|
|
||||||
|
def parse(self) -> docstring_parser.Docstring:
|
||||||
|
'''
|
||||||
|
Parses the docstring.
|
||||||
|
|
||||||
|
:return: The parsed docstring.
|
||||||
|
:rtype: docstring_parser.Docstring
|
||||||
|
'''
|
||||||
|
|
||||||
|
return docstring_parser.parse(self.docstring)
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
# 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
|
||||||
|
from ...exceptions import InvalidNamespaceError, NamespaceNotFoundError, NamespaceExistsError
|
||||||
|
from ...utils import assert_that_is_instance
|
||||||
|
from typing import Union, TypeAlias, Dict, List
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'PythonNamespace',
|
||||||
|
'PythonPackageNamespace',
|
||||||
|
'PythonModuleNamespace'
|
||||||
|
]
|
||||||
|
|
||||||
|
class PythonNamespace:
|
||||||
|
'''
|
||||||
|
The base class for all Python namespaces such as modules or packages.
|
||||||
|
|
||||||
|
:param name: The name of the namespace.
|
||||||
|
:type name: str
|
||||||
|
'''
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
assert_that_is_instance(name, str)
|
||||||
|
|
||||||
|
if not name.strip():
|
||||||
|
raise InvalidNamespaceError('invalid namespace', namespace_identifier = name)
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'{type(self).__name__}({self.name!r})'
|
||||||
|
|
||||||
|
PythonPackageNamespaceDictType: TypeAlias = Dict[str, Union[str, 'PythonPackageNamespaceDictType']]
|
||||||
|
|
||||||
|
class PythonPackageNamespace(PythonNamespace):
|
||||||
|
'''
|
||||||
|
A subclass of ``PythonNamespace`` for Python package namespaces.
|
||||||
|
|
||||||
|
:param name: The name of the namespace.
|
||||||
|
:type name: str
|
||||||
|
:param sub_namespaces: The sub-namespaces.
|
||||||
|
:type sub_namespaces: list[PythonNamespace]
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, name: str, sub_namespaces: List[PythonNamespace]) -> None:
|
||||||
|
assert_that_is_instance(sub_namespaces, list)
|
||||||
|
|
||||||
|
super().__init__(name)
|
||||||
|
|
||||||
|
self._sub_namespaces = sub_namespaces
|
||||||
|
|
||||||
|
def namespace(self, sub_namespaces: List[str]) -> PythonNamespace:
|
||||||
|
'''
|
||||||
|
Returns the namespace object with a specific identifier, such as
|
||||||
|
``['package', 'subpackage', 'module']``.
|
||||||
|
|
||||||
|
:param sub_namespaces: The identifier of the namespace.
|
||||||
|
:type sub_namespaces: list[str]
|
||||||
|
|
||||||
|
:raises InvalidNamespaceError: If the namespace identifier is
|
||||||
|
invalid.
|
||||||
|
:raises NamespaceNotFoundError: If the namespace does not exist.
|
||||||
|
|
||||||
|
:return: The namespace.
|
||||||
|
:rtype: PythonNamespace
|
||||||
|
'''
|
||||||
|
|
||||||
|
if not sub_namespaces:
|
||||||
|
raise InvalidNamespaceError('invalid namespace')
|
||||||
|
|
||||||
|
for sn in sub_namespaces:
|
||||||
|
if not sn:
|
||||||
|
raise InvalidNamespaceError('invalid namespace')
|
||||||
|
|
||||||
|
sub_namespace = self.__getitem__(sub_namespaces[0])
|
||||||
|
|
||||||
|
# package namespace
|
||||||
|
if isinstance(sub_namespace, PythonPackageNamespace):
|
||||||
|
if len(sub_namespaces) == 1:
|
||||||
|
return sub_namespace
|
||||||
|
return sub_namespace.namespace(sub_namespaces[1:])
|
||||||
|
# module namespace
|
||||||
|
else:
|
||||||
|
return sub_namespace
|
||||||
|
|
||||||
|
def _sub_namespaces_names(self, package: PythonPackageNamespace) -> PythonPackageNamespaceDictType:
|
||||||
|
'''
|
||||||
|
Returns the names of all sub-namespaces of the specified package
|
||||||
|
namespace and keeps the package structure.
|
||||||
|
|
||||||
|
:param package: The package.
|
||||||
|
:type package: PythonPackageNamespace
|
||||||
|
|
||||||
|
:return: The names if all sub-namespaces.
|
||||||
|
:rtype: PythonPackageNamespaceDictType
|
||||||
|
'''
|
||||||
|
|
||||||
|
return {
|
||||||
|
sn.name: self._sub_namespaces_names(sn) if isinstance(sn, PythonPackageNamespace) else sn.name
|
||||||
|
for sn in package._sub_namespaces
|
||||||
|
}
|
||||||
|
|
||||||
|
def sub_namespace_names(self) -> PythonPackageNamespaceDictType:
|
||||||
|
'''
|
||||||
|
Returns the names of all sub-namespaces and keeps the package
|
||||||
|
structure.
|
||||||
|
|
||||||
|
:return: The names if all sub-namespaces.
|
||||||
|
:rtype: PythonPackageNamespaceDictType
|
||||||
|
'''
|
||||||
|
|
||||||
|
return self._sub_namespaces_names(self)
|
||||||
|
|
||||||
|
def __eq__(self, value: PythonPackageNamespace) -> bool:
|
||||||
|
# For performance reasons, is not called self.sub_namespace_names
|
||||||
|
# if the name attribute does not match, which can be quickly
|
||||||
|
# verified
|
||||||
|
if self.name != value.name:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return value.sub_namespace_names() == self.sub_namespace_names()
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return type(self).__name__ + repr((self.name, self._sub_namespaces))
|
||||||
|
|
||||||
|
def __getitem__(self, name: str) -> PythonNamespace:
|
||||||
|
'''
|
||||||
|
Returns a namespace with the specified name.
|
||||||
|
|
||||||
|
:param name: The name of the namespace
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
:raises NamespaceNotFoundError: If the namespace does not exist.
|
||||||
|
|
||||||
|
:return: The namespace
|
||||||
|
:rtype: PythonNamespace
|
||||||
|
'''
|
||||||
|
|
||||||
|
for sub in self._sub_namespaces:
|
||||||
|
if sub.name == name:
|
||||||
|
return sub
|
||||||
|
|
||||||
|
raise NamespaceNotFoundError('no such namespace', namespace_identifier = name)
|
||||||
|
|
||||||
|
def add(self, namespace: PythonNamespace) -> None:
|
||||||
|
'''
|
||||||
|
Adds a namespace.
|
||||||
|
|
||||||
|
:param namespace: The namespace to add.
|
||||||
|
:type namespace: PythonNamespace
|
||||||
|
|
||||||
|
:raises NamespaceExistsError: If a namespace with the name of the
|
||||||
|
namespace that is attempted to add
|
||||||
|
already exists.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
self.__getitem__(namespace.name)
|
||||||
|
raise NamespaceExistsError('namespace already exists', namespace_identifier = namespace.name)
|
||||||
|
except NamespaceNotFoundError:
|
||||||
|
self._sub_namespaces.append(namespace)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sub_namespaces(self) -> List[PythonNamespace]:
|
||||||
|
return self._sub_namespaces
|
||||||
|
|
||||||
|
class PythonModuleNamespace(PythonNamespace):
|
||||||
|
'''
|
||||||
|
A subclass of ``PythonNamespace`` for Python module namespaces.
|
||||||
|
|
||||||
|
:param name: The name of the namespace.
|
||||||
|
:type name: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
super().__init__(name)
|
||||||
|
|
||||||
|
def __eq__(self, value: PythonModuleNamespace) -> bool:
|
||||||
|
return self.name == value.name
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
class NamespaceError(ValueError):
|
||||||
|
'''
|
||||||
|
Base class for namespace errors.
|
||||||
|
'''
|
||||||
|
def __init__(self, *args: object, namespace_identifier: str = '') -> None:
|
||||||
|
super().__init__(*args)
|
||||||
|
self.namespace_identifier = namespace_identifier
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if not self.args:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return f'{self.args[0]}{": " if self.namespace_identifier and self.args[0] else ""}{self.namespace_identifier if self.args[0] else ""}'
|
||||||
|
|
||||||
|
class InvalidNamespaceError(NamespaceError): ...
|
||||||
|
class NamespaceNotFoundError(NamespaceError): ...
|
||||||
|
class NamespaceExistsError(NamespaceError): ...
|
||||||
|
|
||||||
|
class PythonIdentifierError(ValueError):
|
||||||
|
'''
|
||||||
|
Base class for Python identifier errors.
|
||||||
|
'''
|
||||||
|
def __init__(self, *args: object, identifier: str = '') -> None:
|
||||||
|
super().__init__(*args)
|
||||||
|
self.identifier = identifier
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if not self.args:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return f'{self.args[0]}{": " if self.identifier and self.args[0] else ""}{self.identifier if self.args[0] else ""}'
|
||||||
|
|
||||||
|
class InvalidPythonIdentifierError(PythonIdentifierError): ...
|
||||||
|
|
||||||
|
class PythonAnnotationError(ValueError):
|
||||||
|
'''
|
||||||
|
Base class for Python annotation errors.
|
||||||
|
'''
|
||||||
|
def __init__(self, *args: object, annotation: str = '') -> None:
|
||||||
|
super().__init__(*args)
|
||||||
|
self.annotation = annotation
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if not self.args:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return f'{self.args[0]}{": " if self.annotation and self.args[0] else ""}{self.annotation if self.args[0] else ""}'
|
||||||
|
|
||||||
|
class InvalidPythonAnnotationError(PythonAnnotationError): ...
|
||||||
|
|
||||||
|
class PythonDefinitionError(ValueError):
|
||||||
|
'''
|
||||||
|
Base class for Python definition errors.
|
||||||
|
'''
|
||||||
|
def __init__(self, *args: object, definition: str = '') -> None:
|
||||||
|
super().__init__(*args)
|
||||||
|
self.definition = definition
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if not self.args:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return f'{self.args[0]}{": " if self.definition and self.args[0] else ""}{self.definition if self.args[0] else ""}'
|
||||||
|
|
||||||
|
class PythonArgumentStructureError(PythonDefinitionError): ...
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
'''
|
||||||
|
The utilities for jcloud_docsgen
|
||||||
|
'''
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import os
|
||||||
|
import errno
|
||||||
|
from typing import Union
|
||||||
|
import types
|
||||||
|
|
||||||
|
class ExistingDirectory(pathlib.Path):
|
||||||
|
'''
|
||||||
|
pathlib.Path subclass that is a directory.
|
||||||
|
|
||||||
|
ExistingDirectory represents a filesystem path but it also checks
|
||||||
|
whether the path exists and is not a file but a directory. It
|
||||||
|
inherits from pathlib.Path.
|
||||||
|
'''
|
||||||
|
def __init__(self, path, *args, **kwargs) -> None:
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise FileNotFoundError(errno.ENOENT, 'no such file or directory', path)
|
||||||
|
if not os.path.isdir(path):
|
||||||
|
raise NotADirectoryError(errno.ENOTDIR, 'not a directory', path)
|
||||||
|
super().__init__(*(path, *args), **kwargs)
|
||||||
|
|
||||||
|
class ExistingFile(pathlib.Path):
|
||||||
|
'''
|
||||||
|
pathlib.Path subclass that is a file.
|
||||||
|
|
||||||
|
ExistingFile represents a filesystem path but it also checks whether
|
||||||
|
the path exists and is not a directory but a file. It inherits from
|
||||||
|
pathlib.Path.
|
||||||
|
'''
|
||||||
|
def __init__(self, path, *args, **kwargs) -> None:
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise FileNotFoundError(errno.ENOENT, 'no such file or directory', path)
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
raise IsADirectoryError(errno.EISDIR, 'is a directory', path)
|
||||||
|
super().__init__(*(path, *args), **kwargs)
|
||||||
|
|
||||||
|
def _quote(string: str, quotation_mark: str) -> str:
|
||||||
|
'''
|
||||||
|
Quotes a string.
|
||||||
|
|
||||||
|
:param string: The string to quote
|
||||||
|
:type string: str
|
||||||
|
:param quotation_mark: The quotation mark
|
||||||
|
:type quotation_mark: str
|
||||||
|
|
||||||
|
:return: The quoted string
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
return quotation_mark + string + quotation_mark
|
||||||
|
|
||||||
|
def human_readable_list(ls: list, final_separator: str = 'and', quotation_mark: str = '') -> str:
|
||||||
|
'''
|
||||||
|
Generates a human-readable list from a list.
|
||||||
|
|
||||||
|
:param ls: The list.
|
||||||
|
:type ls: list
|
||||||
|
:param final_separator: The seperator between the last and the
|
||||||
|
second-to-last element of the list. Default
|
||||||
|
is 'and'.
|
||||||
|
:type final_separator: str
|
||||||
|
:param quotation_mark: The characters that are inserted before and
|
||||||
|
after an element. Default is ''.
|
||||||
|
|
||||||
|
:return: The human-readable list.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
# empty list
|
||||||
|
if not ls:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# list with one element
|
||||||
|
if len(ls) == 1:
|
||||||
|
return _quote(str(ls[0]), quotation_mark)
|
||||||
|
|
||||||
|
return ', '.join(
|
||||||
|
[
|
||||||
|
_quote(str(obj), quotation_mark) # quote the string representation of the object
|
||||||
|
for obj in ls[:-1] # do not include the last element of the list
|
||||||
|
]
|
||||||
|
) + ' ' + final_separator + ' ' + _quote(str(ls[-1]), quotation_mark)
|
||||||
|
|
||||||
|
def _list_type_names(types: list[type]) -> list[str]:
|
||||||
|
'''
|
||||||
|
Converts a list of types into a list of their names (``__name__``
|
||||||
|
attribute)
|
||||||
|
|
||||||
|
:param types: The list of types
|
||||||
|
:type types: list[type]
|
||||||
|
|
||||||
|
:return: The list of the names of the types
|
||||||
|
:rtype: list[str]
|
||||||
|
'''
|
||||||
|
|
||||||
|
return [tp.__name__ for tp in types]
|
||||||
|
|
||||||
|
def assert_that_is_instance(obj: object, class_or_tuple: Union[type, types.UnionType, tuple[type, ...]]) -> None:
|
||||||
|
if not isinstance(class_or_tuple, Union[type, types.UnionType, tuple]):
|
||||||
|
raise TypeError(f'class_or_tuple: expected \'Union[type, types.UnionType, tuple[type, ...]]\', \'got {type(class_or_tuple).__name__}\'')
|
||||||
|
if not isinstance(obj, class_or_tuple):
|
||||||
|
if isinstance(class_or_tuple, (tuple, types.UnionType)):
|
||||||
|
if isinstance(class_or_tuple, types.UnionType):
|
||||||
|
class_or_tuple = class_or_tuple.__args__
|
||||||
|
if len(class_or_tuple) > 1:
|
||||||
|
exception_message_expected = 'either '
|
||||||
|
else:
|
||||||
|
exception_message_expected = ''
|
||||||
|
exception_message_expected += human_readable_list(_list_type_names(class_or_tuple), 'or', '\'')
|
||||||
|
else:
|
||||||
|
exception_message_expected = '\'' + class_or_tuple.__name__ + '\''
|
||||||
|
raise TypeError(f'expected {exception_message_expected}, got \'{type(obj).__name__}\'')
|
||||||
|
|
||||||
|
def non_empty_str(value: Union[str, None]) -> Union[str, None]:
|
||||||
|
'''
|
||||||
|
Returns the value or ``None``.
|
||||||
|
|
||||||
|
Returns the value if it is not empty and not `Ǹone``; otherwise,
|
||||||
|
returns ``None``.
|
||||||
|
Please note: values are stripped, i. e. if the value consists only
|
||||||
|
of spaces, it is also considered empty.
|
||||||
|
|
||||||
|
:param value: The value.
|
||||||
|
:type value: Union[str, None]
|
||||||
|
|
||||||
|
:return: The value or ``None``.
|
||||||
|
:rtype: Union[str, None]
|
||||||
|
'''
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if value.strip() == '':
|
||||||
|
return None
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_relative_path_segments(path: pathlib.Path, root: pathlib.Path) -> list[str]:
|
||||||
|
'''
|
||||||
|
Returns the path segments relative to ``root``.
|
||||||
|
|
||||||
|
:param path: The path.
|
||||||
|
:type path: pathlib.Path
|
||||||
|
:param root: The root path.
|
||||||
|
:type root: pathlib.Path
|
||||||
|
|
||||||
|
:return: The path segments relative to ``root``.
|
||||||
|
:rtype: list[str]
|
||||||
|
'''
|
||||||
|
|
||||||
|
return list(
|
||||||
|
path.resolve()
|
||||||
|
.relative_to(
|
||||||
|
root.resolve()
|
||||||
|
)
|
||||||
|
.parts
|
||||||
|
)
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python import PythonDocumentationGenerator
|
||||||
|
from src.jcloud_docsgen.core.python.namespaces import PythonModuleNamespace, PythonPackageNamespace
|
||||||
|
from src.jcloud_docsgen.utils import ExistingDirectory
|
||||||
|
import pytest
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('project_directory,docs_directory,expected_exception,expected_exception_msg', [
|
||||||
|
(1, 1, TypeError, 'expected \'ExistingDirectory\', got \'int\''),
|
||||||
|
(1, None, TypeError, 'expected \'ExistingDirectory\', got \'int\''),
|
||||||
|
(42, None, TypeError, 'expected \'ExistingDirectory\', got \'int\''),
|
||||||
|
(ExistingDirectory('tests'), 1, TypeError, 'expected \'ExistingDirectory\', got \'int\''),
|
||||||
|
(ExistingDirectory('tests/'), 1, TypeError, 'expected \'ExistingDirectory\', got \'int\''),
|
||||||
|
(1, ExistingDirectory('tests/'), TypeError, 'expected \'ExistingDirectory\', got \'int\''),
|
||||||
|
(1, ExistingDirectory('tests'), TypeError, 'expected \'ExistingDirectory\', got \'int\''),
|
||||||
|
(None, ExistingDirectory('tests'), TypeError, 'expected \'ExistingDirectory\', got \'NoneType\''),
|
||||||
|
])
|
||||||
|
def test_PythonDocumentationGenerator_exceptions(project_directory, docs_directory, expected_exception, expected_exception_msg):
|
||||||
|
with pytest.raises(expected_exception) as exc_info:
|
||||||
|
PythonDocumentationGenerator(project_directory, docs_directory)
|
||||||
|
assert str(exc_info.value) == expected_exception_msg
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('dir', [
|
||||||
|
d.name
|
||||||
|
for d in pathlib.Path('tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/').iterdir()
|
||||||
|
])
|
||||||
|
def test_generate_documentation(dir, expectdir):
|
||||||
|
with expectdir('test_PythonDocumentationGenerator/test_generate_documentation/' + dir) as d:
|
||||||
|
PythonDocumentationGenerator(
|
||||||
|
ExistingDirectory(
|
||||||
|
str(
|
||||||
|
pathlib.Path('tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/') /
|
||||||
|
dir /
|
||||||
|
'project'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
ExistingDirectory(str(d)),
|
||||||
|
allow_tables = True,
|
||||||
|
allow_html = False
|
||||||
|
).generate_documentation(
|
||||||
|
) # simplify the tests
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
# Module `pkg.module`
|
||||||
|
|
||||||
|
## Function `func`
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
func()
|
||||||
|
```
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
def func(): ...
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
# Module `user_management.exceptions`
|
||||||
|
|
||||||
|
## Class `UserNotFoundError`
|
||||||
|
|
||||||
|
User does not exist.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `Exception`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
User does not exist.
|
||||||
|
|
||||||
|
## Class `InvalidEmailError`
|
||||||
|
|
||||||
|
Email address is invalid.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `Exception`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Email address is invalid.
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# Module `user_management.models`
|
||||||
|
|
||||||
|
## Class `User`
|
||||||
|
|
||||||
|
Represents a user.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Decorators
|
||||||
|
|
||||||
|
```python
|
||||||
|
dataclass
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Represents a user.
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
# Module `user_management.service`
|
||||||
|
|
||||||
|
## Class `UserService`
|
||||||
|
|
||||||
|
Central class for the user management.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, storage: InMemoryUserStorage)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `storage` | `InMemoryUserStorage` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `create_user`
|
||||||
|
|
||||||
|
Creates and stores a new user.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
create_user(self, user_id: str, name: str, email: str, age: int | None = None) -> User
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `User`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `user_id` | `str` | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
| `email` | `str` | normal | -- | -- |
|
||||||
|
| `age` | `int | None` | normal | `None` | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Creates and stores a new user.
|
||||||
|
|
||||||
|
#### Method `get_user`
|
||||||
|
|
||||||
|
Returns a user.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
get_user(self, user_id: str) -> User
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `User`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `user_id` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns a user.
|
||||||
|
|
||||||
|
#### Method `delete_user`
|
||||||
|
|
||||||
|
Deletes a user.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
delete_user(self, user_id: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `user_id` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Deletes a user.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Central class for the user management.
|
||||||
+158
@@ -0,0 +1,158 @@
|
|||||||
|
# Module `user_management.storage`
|
||||||
|
|
||||||
|
## Class `InMemoryUserStorage`
|
||||||
|
|
||||||
|
A simple in-memory storage for users.
|
||||||
|
Intended for testing purposes only.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `add`
|
||||||
|
|
||||||
|
Adds a user.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
add(self, user: User) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `user` | `User` | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Adds a user.
|
||||||
|
|
||||||
|
#### Method `get`
|
||||||
|
|
||||||
|
Returns a user based on their ID.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
get(self, user_id: str) -> User
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `User`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `user_id` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns a user based on their ID.
|
||||||
|
|
||||||
|
#### Method `delete`
|
||||||
|
|
||||||
|
Deletes a user.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
delete(self, user_id: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `user_id` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Deletes a user.
|
||||||
|
|
||||||
|
#### Method `list_all`
|
||||||
|
|
||||||
|
Returns all users.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
list_all(self) -> List[User]
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `List[User]`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns all users.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
A simple in-memory storage for users.
|
||||||
|
Intended for testing purposes only.
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
# Module `user_management.utils`
|
||||||
|
|
||||||
|
## Function `is_valid_email`
|
||||||
|
|
||||||
|
Checks whether an email address is valid.
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
is_valid_email(email: str) -> bool
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
Return type: `bool`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `email` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Checks whether an email address is valid.
|
||||||
|
|
||||||
|
## Function `normalize_name`
|
||||||
|
|
||||||
|
Removes extra spaces and formats the name.
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
normalize_name(name: str) -> str
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Removes extra spaces and formats the name.
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
class UserNotFoundError(Exception):
|
||||||
|
"""User does not exist."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidEmailError(Exception):
|
||||||
|
"""Email address is invalid."""
|
||||||
|
pass
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class User:
|
||||||
|
"""
|
||||||
|
Represents a user.
|
||||||
|
"""
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
email: str
|
||||||
|
age: Optional[int] = None
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
from .models import User
|
||||||
|
from .storage import InMemoryUserStorage
|
||||||
|
from .exceptions import UserNotFoundError, InvalidEmailError
|
||||||
|
from .utils import is_valid_email, normalize_name
|
||||||
|
|
||||||
|
|
||||||
|
class UserService:
|
||||||
|
"""
|
||||||
|
Central class for the user management.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, storage: InMemoryUserStorage):
|
||||||
|
self.storage = storage
|
||||||
|
|
||||||
|
def create_user(self, user_id: str, name: str, email: str, age: int | None = None) -> User:
|
||||||
|
"""
|
||||||
|
Creates and stores a new user.
|
||||||
|
"""
|
||||||
|
if not is_valid_email(email):
|
||||||
|
raise InvalidEmailError(f"Invalid email: {email}")
|
||||||
|
|
||||||
|
user = User(
|
||||||
|
id=user_id,
|
||||||
|
name=normalize_name(name),
|
||||||
|
email=email,
|
||||||
|
age=age
|
||||||
|
)
|
||||||
|
|
||||||
|
self.storage.add(user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
def get_user(self, user_id: str) -> User:
|
||||||
|
"""
|
||||||
|
Returns a user.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.storage.get(user_id)
|
||||||
|
except KeyError:
|
||||||
|
raise UserNotFoundError(user_id)
|
||||||
|
|
||||||
|
def delete_user(self, user_id: str) -> None:
|
||||||
|
"""
|
||||||
|
Deletes a user.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.storage.delete(user_id)
|
||||||
|
except KeyError:
|
||||||
|
raise UserNotFoundError(user_id)
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
from typing import Dict, List
|
||||||
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
|
class InMemoryUserStorage:
|
||||||
|
"""
|
||||||
|
A simple in-memory storage for users.
|
||||||
|
Intended for testing purposes only.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._users: Dict[str, User] = {}
|
||||||
|
|
||||||
|
def add(self, user: User) -> None:
|
||||||
|
"""Adds a user."""
|
||||||
|
self._users[user.id] = user
|
||||||
|
|
||||||
|
def get(self, user_id: str) -> User:
|
||||||
|
"""Returns a user based on their ID."""
|
||||||
|
return self._users[user_id]
|
||||||
|
|
||||||
|
def delete(self, user_id: str) -> None:
|
||||||
|
"""Deletes a user."""
|
||||||
|
del self._users[user_id]
|
||||||
|
|
||||||
|
def list_all(self) -> List[User]:
|
||||||
|
"""Returns all users."""
|
||||||
|
return list(self._users.values())
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_email(email: str) -> bool:
|
||||||
|
"""
|
||||||
|
Checks whether an email address is valid.
|
||||||
|
"""
|
||||||
|
pattern = r"^[\w\.-]+@[\w\.-]+\.\w+$"
|
||||||
|
return re.match(pattern, email) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_name(name: str) -> str:
|
||||||
|
"""
|
||||||
|
Removes extra spaces and formats the name.
|
||||||
|
"""
|
||||||
|
return " ".join(name.strip().split()).title()
|
||||||
@@ -0,0 +1,964 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python import PythonModuleDocumentationGenerator
|
||||||
|
from src.jcloud_docsgen.exceptions import InvalidNamespaceError
|
||||||
|
import pytest
|
||||||
|
from src.jcloud_docsgen.utils import ExistingFile, ExistingDirectory
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('module_path,src_directory,expected_exception,expected_exception_msg', [
|
||||||
|
(1, 1, TypeError, 'expected \'ExistingFile\', got \'int\''),
|
||||||
|
(1, None, TypeError, 'expected \'ExistingFile\', got \'int\''),
|
||||||
|
(42, None, TypeError, 'expected \'ExistingFile\', got \'int\''),
|
||||||
|
(ExistingFile('tests/unit/utils/test_file'), 1, TypeError, 'expected either \'ExistingDirectory\' or \'NoneType\', got \'int\''),
|
||||||
|
(ExistingDirectory('tests/'), 1, TypeError, 'expected \'ExistingFile\', got \'ExistingDirectory\''),
|
||||||
|
(ExistingDirectory('tests'), 1, TypeError, 'expected \'ExistingFile\', got \'ExistingDirectory\''),
|
||||||
|
(1, ExistingDirectory('tests/'), TypeError, 'expected \'ExistingFile\', got \'int\''),
|
||||||
|
(1, ExistingDirectory('tests'), TypeError, 'expected \'ExistingFile\', got \'int\''),
|
||||||
|
(None, ExistingDirectory('tests'), TypeError, 'expected \'ExistingFile\', got \'NoneType\''),
|
||||||
|
(None, None, TypeError, 'expected \'ExistingFile\', got \'NoneType\''),
|
||||||
|
(1, None, TypeError, 'expected \'ExistingFile\', got \'int\''),
|
||||||
|
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f.g'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.h'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f.g.h'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.h.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f.g.h.py'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f.g.py'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f.py'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.py'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.py'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.py'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.py'),
|
||||||
|
(ExistingFile('tests/unit/core/python/_core/directory.with.more.than.one.dot/a.b.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.py'),
|
||||||
|
])
|
||||||
|
def test_PythonModuleDocumentationGenerator_exceptions(module_path, src_directory, expected_exception, expected_exception_msg):
|
||||||
|
with pytest.raises(expected_exception) as exc_info:
|
||||||
|
PythonModuleDocumentationGenerator(module_path, src_directory)
|
||||||
|
assert str(exc_info.value) == expected_exception_msg
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('module_documentation_generator,expected', [
|
||||||
|
(PythonModuleDocumentationGenerator(ExistingFile('tests/unit/core/python/_core/test_project_dirs/pdir_2/src/pkg/module.py'), ExistingDirectory('tests/unit/core/python/_core/test_project_dirs/pdir_2/src/'), allow_tables = True, allow_html = False), '''# Module `pkg.module`
|
||||||
|
|
||||||
|
A test module.
|
||||||
|
|
||||||
|
## Function `add`
|
||||||
|
|
||||||
|
Adds two numbers.
|
||||||
|
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
add(a: int | float, b: int | float) -> int | float
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The sum.
|
||||||
|
|
||||||
|
Return type: `int | float`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `a` | `int | float` | normal | -- | The first number. |
|
||||||
|
| `b` | `int | float` | normal | -- | The second number. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Adds two numbers.
|
||||||
|
|
||||||
|
:param a: The first number.
|
||||||
|
:type a: int | float
|
||||||
|
:param b: The second number.
|
||||||
|
:type b: int | float
|
||||||
|
|
||||||
|
:return: The sum.
|
||||||
|
:rtype: int | float
|
||||||
|
|
||||||
|
## Class `Animal`
|
||||||
|
|
||||||
|
A class representing an animal.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `greet`
|
||||||
|
|
||||||
|
Returns a greeting.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
greet(self)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The greeting.
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns a greeting.
|
||||||
|
|
||||||
|
:return: The greeting.
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
A class representing an animal.
|
||||||
|
|
||||||
|
:param name: Its name.
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
## Class `Dog`
|
||||||
|
|
||||||
|
A dog.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `Animal`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Method `bark`
|
||||||
|
|
||||||
|
Barks.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
bark(self) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Barks.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
A dog.'''),
|
||||||
|
|
||||||
|
(PythonModuleDocumentationGenerator(ExistingFile('tests/unit/core/python/_core/test_project_dirs/pdir_2/src/pkg/module.py'), None, allow_tables = True, allow_html = False), '''# Module `module`
|
||||||
|
|
||||||
|
A test module.
|
||||||
|
|
||||||
|
## Function `add`
|
||||||
|
|
||||||
|
Adds two numbers.
|
||||||
|
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
add(a: int | float, b: int | float) -> int | float
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The sum.
|
||||||
|
|
||||||
|
Return type: `int | float`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `a` | `int | float` | normal | -- | The first number. |
|
||||||
|
| `b` | `int | float` | normal | -- | The second number. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Adds two numbers.
|
||||||
|
|
||||||
|
:param a: The first number.
|
||||||
|
:type a: int | float
|
||||||
|
:param b: The second number.
|
||||||
|
:type b: int | float
|
||||||
|
|
||||||
|
:return: The sum.
|
||||||
|
:rtype: int | float
|
||||||
|
|
||||||
|
## Class `Animal`
|
||||||
|
|
||||||
|
A class representing an animal.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `greet`
|
||||||
|
|
||||||
|
Returns a greeting.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
greet(self)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The greeting.
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns a greeting.
|
||||||
|
|
||||||
|
:return: The greeting.
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
A class representing an animal.
|
||||||
|
|
||||||
|
:param name: Its name.
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
## Class `Dog`
|
||||||
|
|
||||||
|
A dog.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `Animal`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Method `bark`
|
||||||
|
|
||||||
|
Barks.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
bark(self) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Barks.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
A dog.'''),
|
||||||
|
|
||||||
|
(PythonModuleDocumentationGenerator(ExistingFile('tests/unit/core/python/_core/test_project_dirs/pdir_2/src/pkg/module.py'), None, allow_tables = True, allow_html = True), '''# Module `module`
|
||||||
|
|
||||||
|
A test module.
|
||||||
|
|
||||||
|
## Function `add`
|
||||||
|
|
||||||
|
Adds two numbers.
|
||||||
|
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
add(a: int | float, b: int | float) -> int | float
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The sum.
|
||||||
|
|
||||||
|
Return type: `int | float`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `a` | `int | float` | normal | -- | The first number. |
|
||||||
|
| `b` | `int | float` | normal | -- | The second number. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary><h3 style="display:inline">Docstring</h3></summary>Adds two numbers.
|
||||||
|
|
||||||
|
:param a: The first number.
|
||||||
|
:type a: int | float
|
||||||
|
:param b: The second number.
|
||||||
|
:type b: int | float
|
||||||
|
|
||||||
|
:return: The sum.
|
||||||
|
:rtype: int | float</details>
|
||||||
|
|
||||||
|
## Class `Animal`
|
||||||
|
|
||||||
|
A class representing an animal.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
| `name` | `str` | normal | -- | -- |
|
||||||
|
|
||||||
|
#### Method `greet`
|
||||||
|
|
||||||
|
Returns a greeting.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
greet(self)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The greeting.
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary><h5 style="display:inline">Docstring</h5></summary>Returns a greeting.
|
||||||
|
|
||||||
|
:return: The greeting.
|
||||||
|
:rtype: str</details>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary><h3 style="display:inline">Docstring</h3></summary>A class representing an animal.
|
||||||
|
|
||||||
|
:param name: Its name.
|
||||||
|
:type name: str</details>
|
||||||
|
|
||||||
|
## Class `Dog`
|
||||||
|
|
||||||
|
A dog.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `Animal`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Method `bark`
|
||||||
|
|
||||||
|
Barks.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
bark(self) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Kind | Default value | Description |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `self` | -- | normal | -- | -- |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary><h5 style="display:inline">Docstring</h5></summary>Barks.</details>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary><h3 style="display:inline">Docstring</h3></summary>A dog.</details>'''),
|
||||||
|
|
||||||
|
(PythonModuleDocumentationGenerator(ExistingFile('tests/unit/core/python/_core/test_project_dirs/pdir_2/src/pkg/module.py'), None, allow_tables = False, allow_html = True), '''# Module `module`
|
||||||
|
|
||||||
|
A test module.
|
||||||
|
|
||||||
|
## Function `add`
|
||||||
|
|
||||||
|
Adds two numbers.
|
||||||
|
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
add(a: int | float, b: int | float) -> int | float
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The sum.
|
||||||
|
|
||||||
|
Return type: `int | float`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
- Parameter: `a`
|
||||||
|
|
||||||
|
Type: `int | float`
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: The first number.
|
||||||
|
|
||||||
|
- Parameter: `b`
|
||||||
|
|
||||||
|
Type: `int | float`
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: The second number.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary><h3 style="display:inline">Docstring</h3></summary>Adds two numbers.
|
||||||
|
|
||||||
|
:param a: The first number.
|
||||||
|
:type a: int | float
|
||||||
|
:param b: The second number.
|
||||||
|
:type b: int | float
|
||||||
|
|
||||||
|
:return: The sum.
|
||||||
|
:rtype: int | float</details>
|
||||||
|
|
||||||
|
## Class `Animal`
|
||||||
|
|
||||||
|
A class representing an animal.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
- Parameter: `self`
|
||||||
|
|
||||||
|
Type: --
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: --
|
||||||
|
|
||||||
|
- Parameter: `name`
|
||||||
|
|
||||||
|
Type: `str`
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: --
|
||||||
|
|
||||||
|
#### Method `greet`
|
||||||
|
|
||||||
|
Returns a greeting.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
greet(self)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The greeting.
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
- Parameter: `self`
|
||||||
|
|
||||||
|
Type: --
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: --
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary><h5 style="display:inline">Docstring</h5></summary>Returns a greeting.
|
||||||
|
|
||||||
|
:return: The greeting.
|
||||||
|
:rtype: str</details>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary><h3 style="display:inline">Docstring</h3></summary>A class representing an animal.
|
||||||
|
|
||||||
|
:param name: Its name.
|
||||||
|
:type name: str</details>
|
||||||
|
|
||||||
|
## Class `Dog`
|
||||||
|
|
||||||
|
A dog.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `Animal`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Method `bark`
|
||||||
|
|
||||||
|
Barks.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
bark(self) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
- Parameter: `self`
|
||||||
|
|
||||||
|
Type: --
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: --
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary><h5 style="display:inline">Docstring</h5></summary>Barks.</details>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary><h3 style="display:inline">Docstring</h3></summary>A dog.</details>'''),
|
||||||
|
|
||||||
|
(PythonModuleDocumentationGenerator(ExistingFile('tests/unit/core/python/_core/test_project_dirs/pdir_2/src/pkg/module.py'), None, allow_tables = False, allow_html = False), '''# Module `module`
|
||||||
|
|
||||||
|
A test module.
|
||||||
|
|
||||||
|
## Function `add`
|
||||||
|
|
||||||
|
Adds two numbers.
|
||||||
|
|
||||||
|
|
||||||
|
### Function signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
add(a: int | float, b: int | float) -> int | float
|
||||||
|
```
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
The sum.
|
||||||
|
|
||||||
|
Return type: `int | float`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
- Parameter: `a`
|
||||||
|
|
||||||
|
Type: `int | float`
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: The first number.
|
||||||
|
|
||||||
|
- Parameter: `b`
|
||||||
|
|
||||||
|
Type: `int | float`
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: The second number.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
Adds two numbers.
|
||||||
|
|
||||||
|
:param a: The first number.
|
||||||
|
:type a: int | float
|
||||||
|
:param b: The second number.
|
||||||
|
:type b: int | float
|
||||||
|
|
||||||
|
:return: The sum.
|
||||||
|
:rtype: int | float
|
||||||
|
|
||||||
|
## Class `Animal`
|
||||||
|
|
||||||
|
A class representing an animal.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Constructor (method `__init__`)
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
__init__(self, name: str) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
- Parameter: `self`
|
||||||
|
|
||||||
|
Type: --
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: --
|
||||||
|
|
||||||
|
- Parameter: `name`
|
||||||
|
|
||||||
|
Type: `str`
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: --
|
||||||
|
|
||||||
|
#### Method `greet`
|
||||||
|
|
||||||
|
Returns a greeting.
|
||||||
|
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
greet(self)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
The greeting.
|
||||||
|
|
||||||
|
Return type: `str`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
- Parameter: `self`
|
||||||
|
|
||||||
|
Type: --
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: --
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Returns a greeting.
|
||||||
|
|
||||||
|
:return: The greeting.
|
||||||
|
:rtype: str
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
A class representing an animal.
|
||||||
|
|
||||||
|
:param name: Its name.
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
## Class `Dog`
|
||||||
|
|
||||||
|
A dog.
|
||||||
|
|
||||||
|
### Base classes
|
||||||
|
|
||||||
|
- `Animal`
|
||||||
|
|
||||||
|
### Body
|
||||||
|
|
||||||
|
#### Method `bark`
|
||||||
|
|
||||||
|
Barks.
|
||||||
|
|
||||||
|
##### Method signature
|
||||||
|
|
||||||
|
```python
|
||||||
|
bark(self) -> None
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Returns
|
||||||
|
|
||||||
|
Return type: `None`
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
|
||||||
|
- Parameter: `self`
|
||||||
|
|
||||||
|
Type: --
|
||||||
|
|
||||||
|
Kind: normal
|
||||||
|
|
||||||
|
Default value: --
|
||||||
|
|
||||||
|
Description: --
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Docstring
|
||||||
|
|
||||||
|
Barks.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Docstring
|
||||||
|
|
||||||
|
A dog.'''),
|
||||||
|
])
|
||||||
|
def test_PythonModuleDocumentationGenerator_exceptions(module_documentation_generator, expected):
|
||||||
|
assert module_documentation_generator.generate_documentation() == expected
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python._core import _argument_kind_human_readable
|
||||||
|
from src.jcloud_docsgen.core.python.arguments import PythonArgumentKind
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('argument_kind,expected', [
|
||||||
|
(PythonArgumentKind.NORMAL, 'normal'),
|
||||||
|
(PythonArgumentKind.POSITIONAL_ONLY, 'positional-only'),
|
||||||
|
(PythonArgumentKind.KEYWORD_ONLY, 'keyword-only'),
|
||||||
|
(PythonArgumentKind.VARARG, 'vararg'),
|
||||||
|
(PythonArgumentKind.KWARGS, 'kwargs'),
|
||||||
|
])
|
||||||
|
def test__argument_kind_human_readable(argument_kind, expected):
|
||||||
|
assert _argument_kind_human_readable(argument_kind) == expected
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
# 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 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
|
||||||
|
from src.jcloud_docsgen.core.python.docstrings import PythonDocstring
|
||||||
|
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', PythonDocstring('Adds two numbers.'), []),
|
||||||
|
PythonClassDefinition('Something', [], PythonDocstring('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', PythonDocstring('The value.'), ['property']),
|
||||||
|
PythonFunctionDefinition('get_value', [
|
||||||
|
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
], 'typing.Any', PythonDocstring('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
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python._core import _fit_row_length
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('expected_length,row,expected', [
|
||||||
|
(0, (), ()),
|
||||||
|
(1, (), (None,)),
|
||||||
|
(2, (), (None, None)),
|
||||||
|
(2, (1,), (1, None)),
|
||||||
|
|
||||||
|
(0, (1,), ()),
|
||||||
|
(1, (1, 2), (1,)),
|
||||||
|
(1, (1, 2), (1,)),
|
||||||
|
(0, (1, 2), ()),
|
||||||
|
(0, (1, 2, 3), ()),
|
||||||
|
(1, (1, 2, 3), (1,)),
|
||||||
|
(2, (1, 2, 3), (1, 2)),
|
||||||
|
|
||||||
|
(1, (1,), (1,)),
|
||||||
|
(2, (1, 1), (1, 1)),
|
||||||
|
(2, (1, 2), (1, 2)),
|
||||||
|
(3, (1, 2, 3), (1, 2, 3)),
|
||||||
|
])
|
||||||
|
def test__fit_row_length(expected_length, row, expected):
|
||||||
|
assert _fit_row_length(expected_length, row) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('row', [
|
||||||
|
(),
|
||||||
|
(1,),
|
||||||
|
(1, 2),
|
||||||
|
(1, 2, 3),
|
||||||
|
(1, 2, 3, 456),
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize('expected_length,expected_exception', [
|
||||||
|
(-1, ValueError),
|
||||||
|
(-2, ValueError),
|
||||||
|
(-3, ValueError),
|
||||||
|
(-42, ValueError),
|
||||||
|
])
|
||||||
|
def test__fit_row_length_exceptions(expected_length, row, expected_exception):
|
||||||
|
with pytest.raises(expected_exception) as exc_info:
|
||||||
|
_fit_row_length(expected_length, row)
|
||||||
|
assert str(exc_info.value) == 'expected must be larger than or equal 0.'
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python._core import _function_params_info, _FunctionParamInfo
|
||||||
|
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
|
||||||
|
import pytest
|
||||||
|
import docstring_parser
|
||||||
|
|
||||||
|
def docstring_args_list(docstring: str) -> list[docstring_parser.DocstringParam]:
|
||||||
|
'''
|
||||||
|
Returns the parameter list of a docstring.
|
||||||
|
|
||||||
|
:param docstring: The docstring
|
||||||
|
:type docstring: str
|
||||||
|
|
||||||
|
:return: The parameter list.
|
||||||
|
:rtype: list[docstring_parser.DocstringParam]
|
||||||
|
'''
|
||||||
|
|
||||||
|
return docstring_parser.parse(docstring).params
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('arg_list,docstring,expected', [
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'int'),
|
||||||
|
], ':param a: a\n:type a: str', {
|
||||||
|
'a': _FunctionParamInfo('int', 'a')
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
|
||||||
|
], ':param a: a\n:type a: str', {
|
||||||
|
'a': _FunctionParamInfo('str', 'a')
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, '', None),
|
||||||
|
], ':param a: a\n:type a: str', {
|
||||||
|
'a': _FunctionParamInfo('str', 'a'),
|
||||||
|
'b': _FunctionParamInfo(None, None)
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, '', 'int'),
|
||||||
|
], ':param a: a\n:type a: str', {
|
||||||
|
'a': _FunctionParamInfo('str', 'a'),
|
||||||
|
'b': _FunctionParamInfo('int', None)
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, '', 'int'),
|
||||||
|
], ':param a: a\n:type a: str\n:param b: b\n:type b: str', {
|
||||||
|
'a': _FunctionParamInfo('str', 'a'),
|
||||||
|
'b': _FunctionParamInfo('int', 'b')
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
|
||||||
|
], ':param a: a\n:type a: str\n:param b: b\n:type b: int', {
|
||||||
|
'a': _FunctionParamInfo('str', 'a')
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
|
||||||
|
], '', {
|
||||||
|
'a': _FunctionParamInfo(None, None)
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'str'),
|
||||||
|
], '', {
|
||||||
|
'a': _FunctionParamInfo('str', None)
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'str'),
|
||||||
|
], ':param a:\n:type a: int', {
|
||||||
|
'a': _FunctionParamInfo('str', '')
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
|
||||||
|
], ':param a:\n:type a: int', {
|
||||||
|
'a': _FunctionParamInfo('int', '')
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
|
||||||
|
], ':param a: a', {
|
||||||
|
'a': _FunctionParamInfo(None, 'a')
|
||||||
|
}),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'str'),
|
||||||
|
], ':param a: a', {
|
||||||
|
'a': _FunctionParamInfo('str', 'a')
|
||||||
|
}),
|
||||||
|
([], ':param a: a\n:type a: str', {}),
|
||||||
|
([], '', {}),
|
||||||
|
])
|
||||||
|
def test__function_params_types(arg_list, docstring, expected):
|
||||||
|
assert _function_params_info(arg_list, docstring_args_list(docstring)) == expected
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python._core import _markdown_table
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('header,body,allow_markdown_table,force_row_lengths,one_line_values,missing_value,expected', [
|
||||||
|
([], [], False, False, True, '--', ''),
|
||||||
|
([], [('a',)], False, False, True, '--', ''),
|
||||||
|
(['a'], [], False, False, True, '--', ''),
|
||||||
|
(['a'], [(1,)], True, False, True, '--', '''| a |
|
||||||
|
| --- |
|
||||||
|
| 1 |'''),
|
||||||
|
(['a'], [(1, 2)], True, False, True, '--', '''| a |
|
||||||
|
| --- |
|
||||||
|
| 1 |'''),
|
||||||
|
(['a', 'b'], [(1, 2)], True, False, True, '--', '''| a | b |
|
||||||
|
| --- | --- |
|
||||||
|
| 1 | 2 |'''),
|
||||||
|
(['a', 'b'], [(1,)], True, False, True, '--', '''| a | b |
|
||||||
|
| --- | --- |
|
||||||
|
| 1 | -- |'''),
|
||||||
|
(['a', 'b'], [()], True, False, True, '--', '''| a | b |
|
||||||
|
| --- | --- |
|
||||||
|
| -- | -- |'''),
|
||||||
|
(['a', 'b'], [(1, 2), (3, 4)], True, False, True, '--', '''| a | b |
|
||||||
|
| --- | --- |
|
||||||
|
| 1 | 2 |
|
||||||
|
| 3 | 4 |'''),
|
||||||
|
(['a'], [('line1\nline2',)], True, False, True, '--', '''| a |
|
||||||
|
| --- |
|
||||||
|
| line1 line2 |'''),
|
||||||
|
(['a'], [('line1\nline2',)], True, False, False, '--', '''| a |
|
||||||
|
| --- |
|
||||||
|
| line1\nline2 |'''),
|
||||||
|
|
||||||
|
([], [], False, False, True, '--', ''),
|
||||||
|
([], [('a',)], False, False, True, '--', ''),
|
||||||
|
(['a'], [], False, False, True, '--', ''),
|
||||||
|
(['a'], [(1,)], False, False, True, '--', '''- a: 1'''),
|
||||||
|
(['a'], [(1, 2)], False, False, True, '--', '''- a: 1'''),
|
||||||
|
(['a', 'b'], [(1, 2)], False, False, True, '--', '''- a: 1
|
||||||
|
|
||||||
|
b: 2'''),
|
||||||
|
(['a', 'b'], [(1,)], False, False, True, '--', '''- a: 1
|
||||||
|
|
||||||
|
b: --'''),
|
||||||
|
(['a', 'b'], [()], False, False, True, '--', '''- a: --
|
||||||
|
|
||||||
|
b: --'''),
|
||||||
|
(['a', 'b'], [(1, 2), (3, 4)], False, False, True, '--', '''- a: 1
|
||||||
|
|
||||||
|
b: 2
|
||||||
|
|
||||||
|
- a: 3
|
||||||
|
|
||||||
|
b: 4'''),
|
||||||
|
(['a'], [('line1\nline2',)], False, False, True, '--', '- a: line1 line2'),
|
||||||
|
(['a'], [('line1\nline2',)], False, False, False, '--', '- a: line1\nline2'),
|
||||||
|
])
|
||||||
|
def test__markdown_table(header, body, allow_markdown_table, force_row_lengths, one_line_values, missing_value, expected):
|
||||||
|
assert _markdown_table(header, body, allow_markdown_table = allow_markdown_table, force_row_lengths = force_row_lengths, one_line_values = one_line_values, missing_value = missing_value) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('header,body,allow_markdown_table,force_row_lengths,one_line_values,missing_value,expected_exception,expected_exception_msg', [
|
||||||
|
(['a'], [(1, 2)], True, True, True, '--', ValueError, 'expected 1 value, got 2'),
|
||||||
|
(['a'], [(1, 2, 3)], True, True, True, '--', ValueError, 'expected 1 value, got 3'),
|
||||||
|
(['a'], [()], True, True, True, '--', ValueError, 'expected 1 value, got 0'),
|
||||||
|
(['a', 'b'], [()], True, True, True, '--', ValueError, 'expected 2 values, got 0'),
|
||||||
|
(['a', 'b'], [(1,)], True, True, True, '--', ValueError, 'expected 2 values, got 1'),
|
||||||
|
(['a', 'b'], [(1, 2, 3)], True, True, True, '--', ValueError, 'expected 2 values, got 3'),
|
||||||
|
|
||||||
|
(['a'], [(1, 2)], False, True, True, '--', ValueError, 'expected 1 value, got 2'),
|
||||||
|
(['a'], [(1, 2, 3)], False, True, True, '--', ValueError, 'expected 1 value, got 3'),
|
||||||
|
(['a'], [()], False, True, True, '--', ValueError, 'expected 1 value, got 0'),
|
||||||
|
(['a', 'b'], [()], False, True, True, '--', ValueError, 'expected 2 values, got 0'),
|
||||||
|
(['a', 'b'], [(1,)], False, True, True, '--', ValueError, 'expected 2 values, got 1'),
|
||||||
|
(['a', 'b'], [(1, 2, 3)], False, True, True, '--', ValueError, 'expected 2 values, got 3'),
|
||||||
|
])
|
||||||
|
def test__markdown_table_exceptions(header, body, allow_markdown_table, force_row_lengths, one_line_values, missing_value, expected_exception, expected_exception_msg):
|
||||||
|
with pytest.raises(expected_exception) as exc_info:
|
||||||
|
_markdown_table(header, body, allow_markdown_table = allow_markdown_table, force_row_lengths = force_row_lengths, one_line_values = one_line_values, missing_value = missing_value)
|
||||||
|
assert str(exc_info.value) == expected_exception_msg
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python._core import _signature_argument_list
|
||||||
|
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('argument_list,expected', [
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None)
|
||||||
|
], ['a']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None)
|
||||||
|
], ['a', '/']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
], ['a', '/', 'b']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
], ['a', '/', 'b', '*', 'c']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
], ['b', '*', 'c']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
], ['*', 'c']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
], ['b', '/', '*', 'c']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
], ['*args']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
], ['a', '*args']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
], ['a', '/', '*args']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
], ['*args', 'a']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
], ['*args', '**kwargs']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
], ['a', '/', '*args', '**kwargs']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
], ['a', '*args', '**kwargs']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
], ['*args', 'a', '**kwargs']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
], ['*', 'a', '**kwargs']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
], ['a', '**kwargs']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
], ['a', '/', '**kwargs']),
|
||||||
|
([
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '1', 'int'),
|
||||||
|
], ['a: int = 1']),
|
||||||
|
])
|
||||||
|
def test__signature_argument_list(argument_list, expected):
|
||||||
|
assert _signature_argument_list(argument_list) == expected
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
'''
|
||||||
|
A test module.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def add(a: int | float, b: int | float) -> int | float:
|
||||||
|
'''
|
||||||
|
Adds two numbers.
|
||||||
|
|
||||||
|
:param a: The first number.
|
||||||
|
:type a: int | float
|
||||||
|
:param b: The second number.
|
||||||
|
:type b: int | float
|
||||||
|
|
||||||
|
:return: The sum.
|
||||||
|
:rtype: int | float
|
||||||
|
'''
|
||||||
|
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
class Animal:
|
||||||
|
'''
|
||||||
|
A class representing an animal.
|
||||||
|
|
||||||
|
:param name: Its name.
|
||||||
|
:type name: str
|
||||||
|
'''
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def greet(self):
|
||||||
|
'''
|
||||||
|
Returns a greeting.
|
||||||
|
|
||||||
|
:return: The greeting.
|
||||||
|
:rtype: str
|
||||||
|
'''
|
||||||
|
|
||||||
|
return f'Hi there! My name is {self.name}'
|
||||||
|
|
||||||
|
class Dog(Animal):
|
||||||
|
'''
|
||||||
|
A dog.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def bark(self) -> None:
|
||||||
|
'''
|
||||||
|
Barks.
|
||||||
|
'''
|
||||||
|
|
||||||
|
print('Woof!')
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python.arguments import PythonASTArgumentsListParser, PythonFunctionArgument, PythonArgumentKind
|
||||||
|
import pytest
|
||||||
|
import ast
|
||||||
|
|
||||||
|
def argument_list(argument_list: str) -> ast.arguments:
|
||||||
|
'''
|
||||||
|
Returns an AST argument list from the argument list source code
|
||||||
|
|
||||||
|
:param argument_list: The argument list.
|
||||||
|
:type argument_list: str
|
||||||
|
|
||||||
|
:return: The AST argument list.
|
||||||
|
:rtype: ast.arguments
|
||||||
|
'''
|
||||||
|
|
||||||
|
return ast.parse('def func(' + argument_list + '): ...').body[0].args
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('ast_arguments_list,expected', [
|
||||||
|
(argument_list('a'), [PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None)]),
|
||||||
|
(argument_list('a: str'), [PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'str')]),
|
||||||
|
(argument_list('a: str | int'), [PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'str | int')]),
|
||||||
|
(argument_list('a: str, b'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'str'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None)
|
||||||
|
]),
|
||||||
|
(argument_list('a: str, b: int'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'str'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int')
|
||||||
|
]),
|
||||||
|
(argument_list('a, b: int'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int')
|
||||||
|
]),
|
||||||
|
(argument_list('a, b, *args'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None)
|
||||||
|
]),
|
||||||
|
(argument_list('a: int, b, *args'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None)
|
||||||
|
]),
|
||||||
|
(argument_list('a, b: str, *args'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'str'),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None)
|
||||||
|
]),
|
||||||
|
(argument_list('a: int, b: str, *args'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'str'),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None)
|
||||||
|
]),
|
||||||
|
(argument_list('a: int, b: str, *args: float'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'str'),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, 'float')
|
||||||
|
]),
|
||||||
|
(argument_list('a: int, b, *args: float'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, 'float')
|
||||||
|
]),
|
||||||
|
(argument_list('a: int, *args: float'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, 'float')
|
||||||
|
]),
|
||||||
|
(argument_list('a: int, /, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None)
|
||||||
|
]),
|
||||||
|
(argument_list('a: int, **kwargs: str'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, 'str')
|
||||||
|
]),
|
||||||
|
(argument_list('a: int, /, b, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None)
|
||||||
|
]),
|
||||||
|
(argument_list('a: int, /, b'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a: int, /, b, *, c'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a, b, *, c'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a, b, *args, c'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a, b, *args, c, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a, b, /, *, c, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a, b, /, c, *, d, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.NORMAL, None, None),
|
||||||
|
PythonFunctionArgument('d', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a: str, b: int, /, c: float, d: list, *, e: tuple, f: dict, **kwargs: set'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'str'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.NORMAL, None, 'float'),
|
||||||
|
PythonFunctionArgument('d', PythonArgumentKind.NORMAL, None, 'list'),
|
||||||
|
PythonFunctionArgument('e', PythonArgumentKind.KEYWORD_ONLY, None, 'tuple'),
|
||||||
|
PythonFunctionArgument('f', PythonArgumentKind.KEYWORD_ONLY, None, 'dict'),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, 'set'),
|
||||||
|
]),
|
||||||
|
(argument_list('a: str, b: int, /, c: float, d: list, *args: bytes, e: tuple, f: dict, **kwargs: set'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'str'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.NORMAL, None, 'float'),
|
||||||
|
PythonFunctionArgument('d', PythonArgumentKind.NORMAL, None, 'list'),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, 'bytes'),
|
||||||
|
PythonFunctionArgument('e', PythonArgumentKind.KEYWORD_ONLY, None, 'tuple'),
|
||||||
|
PythonFunctionArgument('f', PythonArgumentKind.KEYWORD_ONLY, None, 'dict'),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, 'set'),
|
||||||
|
]),
|
||||||
|
(argument_list('a: str, b: int, /, *args: bytes, c: tuple, d: dict, **kwargs: set'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'str'),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, 'bytes'),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, 'tuple'),
|
||||||
|
PythonFunctionArgument('d', PythonArgumentKind.KEYWORD_ONLY, None, 'dict'),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, 'set'),
|
||||||
|
]),
|
||||||
|
(argument_list('a = 1, b = \'a\', /, *args, c = None, d = True, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, '1', None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, '\'a\'', None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, 'None', None),
|
||||||
|
PythonFunctionArgument('d', PythonArgumentKind.KEYWORD_ONLY, 'True', None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a, b = \'a\', /, *args, c = None, d = True, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, '\'a\'', None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, 'None', None),
|
||||||
|
PythonFunctionArgument('d', PythonArgumentKind.KEYWORD_ONLY, 'True', None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a, b , /, *args, c = None, d = True, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, 'None', None),
|
||||||
|
PythonFunctionArgument('d', PythonArgumentKind.KEYWORD_ONLY, 'True', None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a, b , /, *args, c, d = True, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('d', PythonArgumentKind.KEYWORD_ONLY, 'True', None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a, b , /, *args, c, d, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('d', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a, b , /, *args, c = None, d, **kwargs'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
|
||||||
|
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, 'None', None),
|
||||||
|
PythonFunctionArgument('d', PythonArgumentKind.KEYWORD_ONLY, None, None),
|
||||||
|
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
|
||||||
|
]),
|
||||||
|
(argument_list('a = 1'), [
|
||||||
|
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '1', None)
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
def test_PythonASTArgumentsListParser_to_argument_list(ast_arguments_list, expected):
|
||||||
|
assert PythonASTArgumentsListParser(ast_arguments_list).to_argument_list() == expected
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
|
||||||
|
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError, InvalidPythonAnnotationError, PythonArgumentStructureError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('name,kind,default,annotation,expected_exception,expected_exception_msg', [
|
||||||
|
(None, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'NoneType\''),
|
||||||
|
(1, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'int\''),
|
||||||
|
(42, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'int\''),
|
||||||
|
(4.2, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'float\''),
|
||||||
|
(4.2, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'float\''),
|
||||||
|
('a', PythonArgumentKind.NORMAL, 1, None, TypeError, 'expected either \'str\' or \'NoneType\', got \'int\''),
|
||||||
|
('a', PythonArgumentKind.NORMAL, None, 1, TypeError, 'expected either \'str\' or \'NoneType\', got \'int\''),
|
||||||
|
('a', 0, None, None, TypeError, 'expected \'PythonArgumentKind\', got \'int\''),
|
||||||
|
('a', PythonArgumentKind.NORMAL, None, '', InvalidPythonAnnotationError, 'invalid annotation'),
|
||||||
|
('', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier'),
|
||||||
|
('.', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: .'),
|
||||||
|
('#', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: #'),
|
||||||
|
('1a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: 1a'),
|
||||||
|
('1_', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: 1_'),
|
||||||
|
('1,', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: 1,'),
|
||||||
|
(',', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ,'),
|
||||||
|
(':', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: :'),
|
||||||
|
('-', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: -'),
|
||||||
|
(';', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ;'),
|
||||||
|
('|', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: |'),
|
||||||
|
('<', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: <'),
|
||||||
|
('>', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: >'),
|
||||||
|
('a,', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a,'),
|
||||||
|
(',a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ,a'),
|
||||||
|
('a:', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a:'),
|
||||||
|
(':a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: :a'),
|
||||||
|
('a-', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a-'),
|
||||||
|
('-a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: -a'),
|
||||||
|
('a;', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a;'),
|
||||||
|
(';a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ;a'),
|
||||||
|
('a|', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a|'),
|
||||||
|
('|a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: |a'),
|
||||||
|
('a<', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a<'),
|
||||||
|
('<a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: <a'),
|
||||||
|
('a>', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a>'),
|
||||||
|
('>a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: >a'),
|
||||||
|
('class', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: class'),
|
||||||
|
('def', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: def'),
|
||||||
|
('lambda', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: lambda'),
|
||||||
|
('None', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: None'),
|
||||||
|
('True', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: True'),
|
||||||
|
('a', PythonArgumentKind.VARARG, '1', None, PythonArgumentStructureError, '* or ** arguments cannot have default values: *a = 1'),
|
||||||
|
('a', PythonArgumentKind.KWARGS, '1', None, PythonArgumentStructureError, '* or ** arguments cannot have default values: **a = 1'),
|
||||||
|
])
|
||||||
|
def test_PythonFunctionArgument_exceptions(name, kind, default, annotation, expected_exception, expected_exception_msg):
|
||||||
|
with pytest.raises(expected_exception) as exc_info:
|
||||||
|
PythonFunctionArgument(name, kind, default, annotation)
|
||||||
|
assert str(exc_info.value) == expected_exception_msg
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('name', [
|
||||||
|
'a',
|
||||||
|
'_a',
|
||||||
|
'a1',
|
||||||
|
'a0',
|
||||||
|
'a_',
|
||||||
|
'_',
|
||||||
|
'_abc',
|
||||||
|
'abc',
|
||||||
|
'abc_',
|
||||||
|
'match',
|
||||||
|
'case',
|
||||||
|
'type',
|
||||||
|
'ä',
|
||||||
|
'none',
|
||||||
|
'true',
|
||||||
|
'false',
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize('kind', [
|
||||||
|
PythonArgumentKind.NORMAL,
|
||||||
|
PythonArgumentKind.POSITIONAL_ONLY,
|
||||||
|
PythonArgumentKind.KEYWORD_ONLY,
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize('default', [
|
||||||
|
None,
|
||||||
|
'x',
|
||||||
|
'1',
|
||||||
|
None,
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize('annotation', [
|
||||||
|
None,
|
||||||
|
'a',
|
||||||
|
'list[int]',
|
||||||
|
'_',
|
||||||
|
])
|
||||||
|
def test_PythonFunctionArgument(name, kind, default, annotation):
|
||||||
|
PythonFunctionArgument(name, kind, default, annotation)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('name', [
|
||||||
|
'a',
|
||||||
|
'_a',
|
||||||
|
'a1',
|
||||||
|
'a0',
|
||||||
|
'a_',
|
||||||
|
'_',
|
||||||
|
'_abc',
|
||||||
|
'abc',
|
||||||
|
'abc_',
|
||||||
|
'match',
|
||||||
|
'case',
|
||||||
|
'type',
|
||||||
|
'ä',
|
||||||
|
'none',
|
||||||
|
'true',
|
||||||
|
'false',
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize('kind', [
|
||||||
|
PythonArgumentKind.VARARG,
|
||||||
|
PythonArgumentKind.KWARGS,
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize('annotation', [
|
||||||
|
None,
|
||||||
|
'a',
|
||||||
|
'list[int]',
|
||||||
|
'_',
|
||||||
|
])
|
||||||
|
def test_PythonFunctionArgument_asterik(name, kind, annotation):
|
||||||
|
PythonFunctionArgument(name, kind, None, annotation)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('argument,expected', [
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None), 'a'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '1', None), 'a = 1'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'), 'a: int'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '1', 'int'), 'a: int = 1'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None), 'a'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, '1', None), 'a = 1'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'), 'a: int'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, '1', 'int'), 'a: int = 1'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None), 'a'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, '1', None), 'a = 1'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, 'int'), 'a: int'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, '1', 'int'), 'a: int = 1'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.VARARG, None, None), '*a'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.VARARG, None, 'int'), '*a: int'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.KWARGS, None, None), '**a'),
|
||||||
|
(PythonFunctionArgument('a', PythonArgumentKind.KWARGS, None, 'int'), '**a: int'),
|
||||||
|
])
|
||||||
|
def test_PythonFunctionArgument_signature_repr(argument, expected):
|
||||||
|
assert argument.signature_repr() == expected
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python.definitions import PythonFunctionDefinition, PythonClassDefinition
|
||||||
|
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
|
||||||
|
from src.jcloud_docsgen.core.python.docstrings import PythonDocstring
|
||||||
|
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, PythonDocstring('Hello, World!'), []), 'PythonClassDefinition(\'A\', [], None, PythonDocstring(\'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', [], PythonDocstring('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', [], PythonDocstring('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
|
||||||
|
|
||||||
|
# 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.core.python.docstrings import PythonDocstring
|
||||||
|
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('definition,expected', [
|
||||||
|
(PythonDefinition('a', None, []), 'PythonDefinition(\'a\', None, [])'),
|
||||||
|
(PythonDefinition('a', PythonDocstring('Hello, World!'), []), 'PythonDefinition(\'a\', PythonDocstring(\'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', PythonDocstring('doc'), []), PythonDefinition('a', PythonDocstring('doc'), []), True),
|
||||||
|
(PythonDefinition('a', PythonDocstring('doc'), ['a']), PythonDefinition('a', PythonDocstring('doc'), ['a']), True),
|
||||||
|
(PythonDefinition('a', PythonDocstring('doc'), ['a', 'b']), PythonDefinition('a', PythonDocstring('doc'), ['a', 'b']), True),
|
||||||
|
(PythonDefinition('a', None, []), PythonDefinition('b', None, []), False),
|
||||||
|
(PythonDefinition('a', PythonDocstring('doc'), []), PythonDefinition('a', None, []), False),
|
||||||
|
(PythonDefinition('a', PythonDocstring('doc'), []), PythonDefinition('a', PythonDocstring('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', PythonDocstring('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,87 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python.definitions import PythonFunctionDefinition
|
||||||
|
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
|
||||||
|
from src.jcloud_docsgen.core.python.docstrings import PythonDocstring
|
||||||
|
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, PythonDocstring('Hello, World!'), []), 'PythonFunctionDefinition(\'a\', [], None, PythonDocstring(\'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'])),
|
||||||
|
])
|
||||||
|
def test_PythonDefinition_from_node(node, expected):
|
||||||
|
assert PythonFunctionDefinition.from_node(node) == expected
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python.namespaces import PythonModuleNamespace
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class StrSubclass(str): ...
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace,expected_name', [
|
||||||
|
(PythonModuleNamespace('a'), 'a'),
|
||||||
|
(PythonModuleNamespace('1'), '1'),
|
||||||
|
(PythonModuleNamespace(StrSubclass('a')), StrSubclass('a')),
|
||||||
|
])
|
||||||
|
def test_PythonModuleNamespace_name_attribute(namespace, expected_name):
|
||||||
|
assert namespace.name == expected_name
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace1,namespace2,expected', [
|
||||||
|
(PythonModuleNamespace('a'), PythonModuleNamespace('a'), True),
|
||||||
|
(PythonModuleNamespace('b'), PythonModuleNamespace('b'), True),
|
||||||
|
(PythonModuleNamespace('1'), PythonModuleNamespace('1'), True),
|
||||||
|
(PythonModuleNamespace('42'), PythonModuleNamespace('42'), True),
|
||||||
|
(PythonModuleNamespace('a'), PythonModuleNamespace('b'), False),
|
||||||
|
(PythonModuleNamespace('0'), PythonModuleNamespace('-0'), False),
|
||||||
|
(PythonModuleNamespace('1'), PythonModuleNamespace('a'), False),
|
||||||
|
])
|
||||||
|
def test_PythonModuleNamespace___eq__(namespace1, namespace2, expected):
|
||||||
|
assert (namespace1 == namespace2) == expected
|
||||||
|
assert (namespace2 == namespace1) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace,expected', [
|
||||||
|
(PythonModuleNamespace('a'), 'PythonModuleNamespace(\'a\')'),
|
||||||
|
(PythonModuleNamespace('1'), 'PythonModuleNamespace(\'1\')'),
|
||||||
|
(PythonModuleNamespace(StrSubclass('a')), 'PythonModuleNamespace(\'a\')'),
|
||||||
|
])
|
||||||
|
def test_PythonModuleNamespace_string_representation(namespace, expected):
|
||||||
|
assert repr(namespace) == expected
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python.namespaces import PythonNamespace
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class StrSubclass(str): ...
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace,expected_name', [
|
||||||
|
(PythonNamespace('a'), 'a'),
|
||||||
|
(PythonNamespace('1'), '1'),
|
||||||
|
(PythonNamespace(StrSubclass('a')), StrSubclass('a')),
|
||||||
|
])
|
||||||
|
def test_PythonNamespace_name_attribute(namespace, expected_name):
|
||||||
|
assert namespace.name == expected_name
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace,expected', [
|
||||||
|
(PythonNamespace('a'), 'PythonNamespace(\'a\')'),
|
||||||
|
(PythonNamespace('1'), 'PythonNamespace(\'1\')'),
|
||||||
|
(PythonNamespace(StrSubclass('a')), 'PythonNamespace(\'a\')'),
|
||||||
|
])
|
||||||
|
def test_PythonNamespace_string_representation(namespace, expected):
|
||||||
|
assert repr(namespace) == expected
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# 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 src.jcloud_docsgen.core.python.namespaces import PythonPackageNamespace, PythonModuleNamespace
|
||||||
|
import pytest
|
||||||
|
from src.jcloud_docsgen.exceptions import NamespaceNotFoundError, InvalidNamespaceError
|
||||||
|
|
||||||
|
class StrSubclass(str): ...
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace,expected_name', [
|
||||||
|
(PythonPackageNamespace('a', []), 'a'),
|
||||||
|
(PythonPackageNamespace('1', []), '1'),
|
||||||
|
(PythonPackageNamespace(StrSubclass('a'), []), StrSubclass('a')),
|
||||||
|
])
|
||||||
|
def test_PythonPackageNamespace_name_attribute(namespace, expected_name):
|
||||||
|
assert namespace.name == expected_name
|
||||||
|
|
||||||
|
namespace_b = PythonModuleNamespace('b')
|
||||||
|
namespace_a = PythonPackageNamespace('a', [
|
||||||
|
namespace_b
|
||||||
|
])
|
||||||
|
namespace_c = PythonModuleNamespace('c')
|
||||||
|
namespace_k = PythonModuleNamespace('k')
|
||||||
|
namespace_j = PythonPackageNamespace('j', [namespace_k])
|
||||||
|
namespace_i = PythonPackageNamespace('i', [namespace_j])
|
||||||
|
namespace_h = PythonPackageNamespace('h', [namespace_i])
|
||||||
|
namespace_g = PythonPackageNamespace('g', [namespace_h])
|
||||||
|
namespace_f = PythonPackageNamespace('f', [namespace_g])
|
||||||
|
namespace_e = PythonPackageNamespace('e', [namespace_f])
|
||||||
|
namespace_d = PythonPackageNamespace('d', [namespace_e])
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace,sub_namespaces,expected', [
|
||||||
|
(PythonPackageNamespace('name', [
|
||||||
|
namespace_a,
|
||||||
|
namespace_c
|
||||||
|
]), ['c'], namespace_c),
|
||||||
|
(PythonPackageNamespace('name', [
|
||||||
|
namespace_a,
|
||||||
|
namespace_c
|
||||||
|
]), ['a'], namespace_a),
|
||||||
|
(PythonPackageNamespace('name', [
|
||||||
|
namespace_a,
|
||||||
|
namespace_c
|
||||||
|
]), ['a', 'b'], namespace_b),
|
||||||
|
(namespace_d, ['e', 'f', 'g', 'h', 'i'], namespace_i),
|
||||||
|
(namespace_d, ['e', 'f', 'g', 'h', 'i', 'j'], namespace_j),
|
||||||
|
(namespace_d, ['e', 'f', 'g', 'h', 'i', 'j', 'k'], namespace_k),
|
||||||
|
])
|
||||||
|
def test_PythonPackageNamespace_namespace(namespace: PythonPackageNamespace, sub_namespaces, expected):
|
||||||
|
assert namespace.namespace(sub_namespaces) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace,sub_namespaces,expected_exception,expected_exception_msg', [
|
||||||
|
(namespace_a, ['c'], NamespaceNotFoundError, 'no such namespace: c'),
|
||||||
|
(namespace_h, ['i', 'k'], NamespaceNotFoundError, 'no such namespace: k'),
|
||||||
|
(namespace_a, ['b', ''], InvalidNamespaceError, 'invalid namespace'),
|
||||||
|
(namespace_a, ['', 'b'], InvalidNamespaceError, 'invalid namespace'),
|
||||||
|
(namespace_a, ['b', '', 'b'], InvalidNamespaceError, 'invalid namespace'),
|
||||||
|
])
|
||||||
|
def test_PythonPackageNamespace_namespace_exceptions(namespace, sub_namespaces, expected_exception, expected_exception_msg):
|
||||||
|
with pytest.raises(expected_exception) as exc_info:
|
||||||
|
namespace.namespace(sub_namespaces)
|
||||||
|
assert str(exc_info.value) == expected_exception_msg
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace,expected', [
|
||||||
|
(PythonPackageNamespace('a', [PythonModuleNamespace('b')]), {'b': 'b'}),
|
||||||
|
(PythonPackageNamespace('a', [PythonModuleNamespace('b'), PythonPackageNamespace('c', [PythonModuleNamespace('d')])]), {'b': 'b', 'c': {'d': 'd'}}),
|
||||||
|
(PythonPackageNamespace('a', []), {})
|
||||||
|
])
|
||||||
|
def test_PythonPackageNamespace_sub_namespace_names(namespace, expected):
|
||||||
|
assert namespace.sub_namespace_names() == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace1,namespace2,expected', [
|
||||||
|
(PythonPackageNamespace('a', [PythonModuleNamespace('b')]), PythonPackageNamespace('a', [PythonModuleNamespace('b')]), True),
|
||||||
|
(PythonPackageNamespace('a', [PythonModuleNamespace('b'), PythonPackageNamespace('c', [PythonModuleNamespace('d')])]), PythonPackageNamespace('a', [PythonModuleNamespace('b'), PythonPackageNamespace('c', [PythonModuleNamespace('d')])]), True),
|
||||||
|
(PythonPackageNamespace('a', []), PythonPackageNamespace('a', []), True),
|
||||||
|
(PythonPackageNamespace('a', []), PythonPackageNamespace('b', []), False),
|
||||||
|
])
|
||||||
|
def test_PythonPackageNamespace___eq__(namespace1, namespace2, expected):
|
||||||
|
assert (namespace1 == namespace2) == expected
|
||||||
|
assert (namespace2 == namespace1) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace,expected', [
|
||||||
|
(PythonPackageNamespace('a', []), 'PythonPackageNamespace(\'a\', [])'),
|
||||||
|
(PythonPackageNamespace('1', []), 'PythonPackageNamespace(\'1\', [])'),
|
||||||
|
(PythonPackageNamespace(StrSubclass('a'), []), 'PythonPackageNamespace(\'a\', [])'),
|
||||||
|
(namespace_a, 'PythonPackageNamespace(\'a\', [PythonModuleNamespace(\'b\')])'),
|
||||||
|
(namespace_d, 'PythonPackageNamespace(\'d\', [PythonPackageNamespace(\'e\', [PythonPackageNamespace(\'f\', [PythonPackageNamespace(\'g\', [PythonPackageNamespace(\'h\', [PythonPackageNamespace(\'i\', [PythonPackageNamespace(\'j\', [PythonModuleNamespace(\'k\')])])])])])])])')
|
||||||
|
])
|
||||||
|
def test_PythonPackageNamespace_string_representation(namespace, expected):
|
||||||
|
assert repr(namespace) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('namespace,new_namespace,expected', [
|
||||||
|
(PythonPackageNamespace('a', []), PythonModuleNamespace('b'), PythonPackageNamespace('a', [PythonModuleNamespace('b')]))
|
||||||
|
])
|
||||||
|
def test_PythonPackageNamespace___getitem__(namespace, new_namespace, expected):
|
||||||
|
namespace.add(new_namespace)
|
||||||
|
assert namespace == expected
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# 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 src.jcloud_docsgen.utils import ExistingDirectory
|
||||||
|
import pytest
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('path,expected', [
|
||||||
|
('tests/unit/utils/test_directory/', 'tests/unit/utils/test_directory'),
|
||||||
|
('tests/unit/utils/test_directory', 'tests/unit/utils/test_directory'),
|
||||||
|
('tests/unit/utils/', 'tests/unit/utils'),
|
||||||
|
('tests/unit/utils', 'tests/unit/utils'),
|
||||||
|
('/', '/'),
|
||||||
|
('./', '.'),
|
||||||
|
('.', '.'),
|
||||||
|
('../', '..'),
|
||||||
|
('..', '..'),
|
||||||
|
])
|
||||||
|
def test_ExistingDirectory(path, expected):
|
||||||
|
assert ExistingDirectory(path).as_posix() == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('path,expected_exception,expected_exception_message', [
|
||||||
|
('tests/unit/utils/does_not_exist/', FileNotFoundError, '[Errno 2] no such file or directory: \'tests/unit/utils/does_not_exist/\''),
|
||||||
|
('tests/unit/utils/does_not_exist', FileNotFoundError, '[Errno 2] no such file or directory: \'tests/unit/utils/does_not_exist\''),
|
||||||
|
('', FileNotFoundError, '[Errno 2] no such file or directory: \'\''),
|
||||||
|
('README.md', NotADirectoryError, '[Errno 20] not a directory: \'README.md\''),
|
||||||
|
])
|
||||||
|
def test_ExistingDirectory_exceptions(path, expected_exception, expected_exception_message):
|
||||||
|
with pytest.raises(expected_exception) as exc_info:
|
||||||
|
ExistingDirectory(path)
|
||||||
|
assert str(exc_info.value) == expected_exception_message
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# 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 src.jcloud_docsgen.utils import ExistingFile
|
||||||
|
import pytest
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('path,expected', [
|
||||||
|
('tests/unit/utils/test_file', 'tests/unit/utils/test_file'),
|
||||||
|
])
|
||||||
|
def test_ExistingDirectory(path, expected):
|
||||||
|
assert ExistingFile(path).as_posix() == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('path,expected_exception,expected_exception_message', [
|
||||||
|
('tests/unit/utils/does_not_exist', FileNotFoundError, '[Errno 2] no such file or directory: \'tests/unit/utils/does_not_exist\''),
|
||||||
|
('', FileNotFoundError, '[Errno 2] no such file or directory: \'\''),
|
||||||
|
('tests/unit/utils/test_directory', IsADirectoryError, '[Errno 21] is a directory: \'tests/unit/utils/test_directory\''),
|
||||||
|
('.', IsADirectoryError, '[Errno 21] is a directory: \'.\''),
|
||||||
|
('./', IsADirectoryError, '[Errno 21] is a directory: \'./\''),
|
||||||
|
])
|
||||||
|
def test_ExistingDirectory_exceptions(path, expected_exception, expected_exception_message):
|
||||||
|
with pytest.raises(expected_exception) as exc_info:
|
||||||
|
ExistingFile(path)
|
||||||
|
assert str(exc_info.value) == expected_exception_message
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# 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 src.jcloud_docsgen.utils import assert_that_is_instance
|
||||||
|
import pytest
|
||||||
|
import types
|
||||||
|
|
||||||
|
class TestType: ...
|
||||||
|
TestType.__name__ = 'NOT TestType'
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('obj,class_or_tuple', [
|
||||||
|
(1, int),
|
||||||
|
(1, (int,)),
|
||||||
|
(1, str | int),
|
||||||
|
(1, int | str | list),
|
||||||
|
(TestType(), TestType),
|
||||||
|
(None, types.NoneType),
|
||||||
|
])
|
||||||
|
def test_assert_that_is_instance(obj, class_or_tuple):
|
||||||
|
assert_that_is_instance(obj, class_or_tuple)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('obj,class_or_tuple,expected_exception_msg', [
|
||||||
|
(1, str, 'expected \'str\', got \'int\''),
|
||||||
|
(1, (str,), 'expected \'str\', got \'int\''),
|
||||||
|
(1, str | float, 'expected either \'str\' or \'float\', got \'int\''),
|
||||||
|
(1, str | float | list, 'expected either \'str\', \'float\' or \'list\', got \'int\''),
|
||||||
|
(1, TestType, 'expected \'NOT TestType\', got \'int\''),
|
||||||
|
])
|
||||||
|
def test_assert_that_is_instance_exceptions(obj, class_or_tuple, expected_exception_msg):
|
||||||
|
with pytest.raises(TypeError) as exc_info:
|
||||||
|
assert_that_is_instance(obj, class_or_tuple)
|
||||||
|
assert str(exc_info.value) == expected_exception_msg
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# 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 src.jcloud_docsgen.utils import get_relative_path_segments
|
||||||
|
import pytest
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('path,root,expected', [
|
||||||
|
(pathlib.Path('/'), pathlib.Path('/'), []),
|
||||||
|
(pathlib.Path('/a'), pathlib.Path('/'), ['a']),
|
||||||
|
(pathlib.Path('/a/'), pathlib.Path('/'), ['a']),
|
||||||
|
(pathlib.Path('/a'), pathlib.Path('/a'), []),
|
||||||
|
(pathlib.Path('/a/'), pathlib.Path('/a'), []),
|
||||||
|
(pathlib.Path('/a'), pathlib.Path('/a/'), []),
|
||||||
|
(pathlib.Path('/a/'), pathlib.Path('/a/'), []),
|
||||||
|
(pathlib.Path('/a/b'), pathlib.Path('/'), ['a', 'b']),
|
||||||
|
(pathlib.Path('/a/b/'), pathlib.Path('/'), ['a', 'b']),
|
||||||
|
(pathlib.Path('/a/b'), pathlib.Path('/a'), ['b']),
|
||||||
|
(pathlib.Path('/a/b/'), pathlib.Path('/a'), ['b']),
|
||||||
|
(pathlib.Path('/a/b'), pathlib.Path('/a/'), ['b']),
|
||||||
|
(pathlib.Path('/a/b/'), pathlib.Path('/a/'), ['b']),
|
||||||
|
(pathlib.Path('/a/b'), pathlib.Path('/a/b'), []),
|
||||||
|
(pathlib.Path('/a/b/'), pathlib.Path('/a/b'), []),
|
||||||
|
(pathlib.Path('/a/b'), pathlib.Path('/a/b/'), []),
|
||||||
|
(pathlib.Path('/a/b/'), pathlib.Path('/a/b/'), []),
|
||||||
|
|
||||||
|
(pathlib.Path('./'), pathlib.Path('./'), []),
|
||||||
|
(pathlib.Path('./a'), pathlib.Path('./'), ['a']),
|
||||||
|
(pathlib.Path('./a/'), pathlib.Path('./'), ['a']),
|
||||||
|
(pathlib.Path('./a'), pathlib.Path('./a'), []),
|
||||||
|
(pathlib.Path('./a/'), pathlib.Path('./a'), []),
|
||||||
|
(pathlib.Path('./a'), pathlib.Path('./a/'), []),
|
||||||
|
(pathlib.Path('./a/'), pathlib.Path('./a/'), []),
|
||||||
|
(pathlib.Path('./a/b'), pathlib.Path('./'), ['a', 'b']),
|
||||||
|
(pathlib.Path('./a/b/'), pathlib.Path('./'), ['a', 'b']),
|
||||||
|
(pathlib.Path('./a/b'), pathlib.Path('./a'), ['b']),
|
||||||
|
(pathlib.Path('./a/b/'), pathlib.Path('./a'), ['b']),
|
||||||
|
(pathlib.Path('./a/b'), pathlib.Path('./a/'), ['b']),
|
||||||
|
(pathlib.Path('./a/b/'), pathlib.Path('./a/'), ['b']),
|
||||||
|
(pathlib.Path('./a/b'), pathlib.Path('./a/b'), []),
|
||||||
|
(pathlib.Path('./a/b/'), pathlib.Path('./a/b'), []),
|
||||||
|
(pathlib.Path('./a/b'), pathlib.Path('./a/b/'), []),
|
||||||
|
(pathlib.Path('./a/b/'), pathlib.Path('./a/b/'), []),
|
||||||
|
|
||||||
|
(pathlib.Path(''), pathlib.Path(''), []),
|
||||||
|
(pathlib.Path('a'), pathlib.Path(''), ['a']),
|
||||||
|
(pathlib.Path('a/'), pathlib.Path(''), ['a']),
|
||||||
|
(pathlib.Path('a'), pathlib.Path('a'), []),
|
||||||
|
(pathlib.Path('a/'), pathlib.Path('a'), []),
|
||||||
|
(pathlib.Path('a'), pathlib.Path('a/'), []),
|
||||||
|
(pathlib.Path('a/'), pathlib.Path('a/'), []),
|
||||||
|
(pathlib.Path('a/b'), pathlib.Path(''), ['a', 'b']),
|
||||||
|
(pathlib.Path('a/b/'), pathlib.Path(''), ['a', 'b']),
|
||||||
|
(pathlib.Path('a/b'), pathlib.Path('a'), ['b']),
|
||||||
|
(pathlib.Path('a/b/'), pathlib.Path('a'), ['b']),
|
||||||
|
(pathlib.Path('a/b'), pathlib.Path('a/'), ['b']),
|
||||||
|
(pathlib.Path('a/b/'), pathlib.Path('a/'), ['b']),
|
||||||
|
(pathlib.Path('a/b'), pathlib.Path('a/b'), []),
|
||||||
|
(pathlib.Path('a/b/'), pathlib.Path('a/b'), []),
|
||||||
|
(pathlib.Path('a/b'), pathlib.Path('a/b/'), []),
|
||||||
|
(pathlib.Path('a/b/'), pathlib.Path('a/b/'), []),
|
||||||
|
|
||||||
|
])
|
||||||
|
def test_get_relative_path_segments(path, root, expected):
|
||||||
|
assert get_relative_path_segments(path, root) == expected
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# 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 src.jcloud_docsgen.utils import human_readable_list
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('ls,final_separator,quotation_mark,expected', [
|
||||||
|
([], '', '', ''),
|
||||||
|
([], 'and', '', ''),
|
||||||
|
([], 'and', '\'', ''),
|
||||||
|
([1], '', '', '1'),
|
||||||
|
([-1], '', '', '-1'),
|
||||||
|
([0], '', '', '0'),
|
||||||
|
([0.0], '', '', '0.0'),
|
||||||
|
([-0.0], '', '', '-0.0'),
|
||||||
|
([4.2], '', '', '4.2'),
|
||||||
|
([-4.2], '', '', '-4.2'),
|
||||||
|
(['a'], '', '', 'a'),
|
||||||
|
([1], 'and', '', '1'),
|
||||||
|
(['a'], 'and', '', 'a'),
|
||||||
|
([1], 'and', '\'', '\'1\''),
|
||||||
|
(['a'], 'and', '\'', '\'a\''),
|
||||||
|
([1, 2], 'and', '', '1 and 2'),
|
||||||
|
([1, 2], 'and', '\'', '\'1\' and \'2\''),
|
||||||
|
([1, 'a'], 'and', '', '1 and a'),
|
||||||
|
([1, 'a'], 'and', '\'', '\'1\' and \'a\''),
|
||||||
|
([1, 'a'], 'and', '', '1 and a'),
|
||||||
|
([1, 'a'], 'and', '\'', '\'1\' and \'a\''),
|
||||||
|
([1, 'a'], 'and', '\'', '\'1\' and \'a\''),
|
||||||
|
([1, 'a'], '', '', '1 a'),
|
||||||
|
([1, 'a'], '', '\'', '\'1\' \'a\''),
|
||||||
|
([1, 'a'], '', '', '1 a'),
|
||||||
|
([1, 'a'], '', '\'', '\'1\' \'a\''),
|
||||||
|
([1, 'a'], '', '\'', '\'1\' \'a\''),
|
||||||
|
([1, 2, 3], 'and', '', '1, 2 and 3'),
|
||||||
|
([1, 2, 3], '', '', '1, 2 3'),
|
||||||
|
([1, 2, 3], 'and', '\'', '\'1\', \'2\' and \'3\''),
|
||||||
|
([1, 2, 3], '', '\'', '\'1\', \'2\' \'3\''),
|
||||||
|
([1, 2, 3, 42, 'a', 23], 'and', '', '1, 2, 3, 42, a and 23'),
|
||||||
|
([1, 2, 3, 42, 'a', 23], 'and', '\'', '\'1\', \'2\', \'3\', \'42\', \'a\' and \'23\''),
|
||||||
|
])
|
||||||
|
def test_human_readable_list(ls, final_separator, quotation_mark, expected):
|
||||||
|
assert human_readable_list(ls, final_separator, quotation_mark) == expected
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# 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 src.jcloud_docsgen.utils import non_empty_str
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('value,expected', [
|
||||||
|
(None, None),
|
||||||
|
('', None),
|
||||||
|
(' ', None),
|
||||||
|
(' ', None),
|
||||||
|
(' ', None),
|
||||||
|
(' a ', ' a '),
|
||||||
|
(' a', ' a'),
|
||||||
|
('a ', 'a '),
|
||||||
|
('a', 'a'),
|
||||||
|
])
|
||||||
|
def test_non_empty_str(value, expected):
|
||||||
|
assert non_empty_str(value) == expected
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# 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 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