style: format source code using Ruff

This commit is contained in:
jakob.scheid committed 2026-09-21 20:10:04 +02:00
1 parent d9887f34c9
commit aa4a139878
41 files changed
+6061 -5670

No files matched your search

+256 -227
View File
@@ -40,15 +40,16 @@ __all__ = [
'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):
@@ -58,6 +59,7 @@ def _collect_definitions(tree_or_node) -> Iterator[PythonDefinition]:
if isinstance(node, ast.ClassDef):
yield PythonClassDefinition.from_node(node)
_Arguments: TypeAlias = tuple[
list[PythonFunctionArgument],
list[PythonFunctionArgument],
@@ -66,10 +68,11 @@ _Arguments: TypeAlias = tuple[
Union[PythonFunctionArgument, None],
]
def _group_argument_list(
argument_list: list[PythonFunctionArgument],
) -> _Arguments:
'''
"""
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.
@@ -78,13 +81,12 @@ def _group_argument_list(
:type argument_list: list[PythonFunctionArgument]
:rtype: _Arguments
'''
"""
if argument_list:
if argument_list[-1].kind != PythonArgumentKind.KWARGS and any([
arg.kind == PythonArgumentKind.KWARGS
for arg in 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',
)
@@ -106,7 +108,7 @@ def _group_argument_list(
if vararg is not None:
raise PythonArgumentStructureError(
'* argument may only appear once',
definition = arg,
definition=arg,
)
vararg = arg
@@ -124,10 +126,11 @@ def _group_argument_list(
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.
@@ -135,7 +138,7 @@ def _signature_argument_list(
:return: The list of function signature argument strings.
:rtype: list[str]
'''
"""
(
positional_only_args,
@@ -170,8 +173,9 @@ def _signature_argument_list(
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.
@@ -181,7 +185,7 @@ def _fit_row_length(expected: int, row: tuple) -> tuple:
:type expected: int
:param row: The row.
:type row: tuple
'''
"""
if expected < 0:
raise ValueError('expected must be larger than or equal 0.')
@@ -191,6 +195,7 @@ def _fit_row_length(expected: int, row: tuple) -> tuple:
else:
return row + (None,) * (expected - len(row))
def _markdown_table(
header: tuple,
body: list[tuple],
@@ -200,7 +205,7 @@ def _markdown_table(
one_line_values: bool = True,
missing_value: str = '--',
) -> str:
'''
"""
Generates a table.
If ``allow_markdown_table`` is ``False``, an alternative
@@ -222,7 +227,7 @@ def _markdown_table(
: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 ''
@@ -231,37 +236,34 @@ def _markdown_table(
if allow_markdown_table:
table = (
'| ' \
+ ' | '.join(header) \
+ ' |\n|' \
+ ' --- |' * len(header) \
+ '\n'
'| ' + ' | '.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 ""
"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'
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 = ''
@@ -270,9 +272,7 @@ def _markdown_table(
if force_row_lengths:
raise ValueError(
f'expected {header_len} value{
"s"
if header_len > 1
else ""
"s" if header_len > 1 else ""
}, got {len(row)}',
)
else:
@@ -281,29 +281,25 @@ def _markdown_table(
sep = '- '
for i, col in enumerate(row):
table += (
sep \
+ header[i] \
+ ': ' \
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
str(col if col is not None else missing_value).replace(
'\n', ' '
)
) + '\n\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.
@@ -311,7 +307,7 @@ def _is_docstring(docstring: Union[str, PythonDocstring, None]) -> bool:
:return: Whether there is a docstring.
:rtype: bool
'''
"""
if docstring is None:
return False
@@ -321,11 +317,13 @@ def _is_docstring(docstring: Union[str, PythonDocstring, None]) -> bool:
return docstring.strip() != ''
@dataclass
class _FunctionParamInfo:
annotation: Union[str, None]
description: Union[str, None]
@dataclass
class _NoDocstringTemplate:
description = None
@@ -340,10 +338,12 @@ class _NoDocstringTemplate:
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``.
@@ -352,15 +352,16 @@ def _argument_kind_human_readable(argument_kind: PythonArgumentKind) -> str:
: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
@@ -377,11 +378,10 @@ def _function_params_info(
:return: The dictionary with the parameter types.
:rtype: dict[str, _FunctionParamInfo]
'''
"""
arg_annotations = {
arg.name: _FunctionParamInfo(arg.annotation, None)
for arg in arg_list
arg.name: _FunctionParamInfo(arg.annotation, None) for arg in arg_list
}
for arg in docstring_arg_list:
@@ -393,8 +393,9 @@ def _function_params_info(
return arg_annotations
class PythonDefinitionDocumentationIncludeSections:
'''
"""
A class representing the included sections in a Python definition
documentation.
@@ -414,50 +415,55 @@ class PythonDefinitionDocumentationIncludeSections:
: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,
*,
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,
}
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()
],
) + ')'
return (
type(self).__name__
+ '('
+ ', '.join(
[
key + ' = ' + repr(value)
for key, value in self._args.items()
],
)
+ ')'
)
def __eq__(self, value) -> str:
if not isinstance(
@@ -467,8 +473,9 @@ class PythonDefinitionDocumentationIncludeSections:
return False
return self._args == value._args
class PythonDefinitionDocumentationGenerator:
'''
"""
A documentation generator for a Python definition.
:param definition: The Python definition.
@@ -492,21 +499,21 @@ class PythonDefinitionDocumentationGenerator:
: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: 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
@@ -521,13 +528,15 @@ class PythonDefinitionDocumentationGenerator:
@property
def _docstring(self):
if self._docstring_cache is None:
self._docstring_cache = self.definition.doc.parse() \
if _is_docstring(self.definition.doc) \
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.
@@ -535,18 +544,23 @@ class PythonDefinitionDocumentationGenerator:
: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,
),
) + ')'
signature = (
self.definition.name
+ '('
+ ', '.join(
_signature_argument_list(
self.definition.args,
),
)
+ ')'
)
if self.definition.returns is not None:
signature += ' -> ' + self.definition.returns
@@ -554,38 +568,38 @@ class PythonDefinitionDocumentationGenerator:
return signature.strip()
def _generate_signature_section(self) -> str:
'''
"""
Generates the signature section.
:return: The signature section.
:rtype: str
'''
"""
definition_type = 'Function' if not self.is_method else 'Method'
heading = self.level * '#' + '# ' + (definition_type) +' signature'
return f'''{heading}
heading = self.level * '#' + '# ' + (definition_type) + ' signature'
return f"""{heading}
```python
{self.generate_signature()}
```'''
```"""
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 += f'<details><summary><h{str(
self.level + 1
)} style="display:inline">Docstring</h{str(
self.level + 1
)}></summary>'
md += f'<details><summary><h{
str(self.level + 1)
} style="display:inline">Docstring</h{
str(self.level + 1)
}></summary>'
else:
md += self.level * '#' + '# Docstring\n\n'
@@ -602,12 +616,12 @@ class PythonDefinitionDocumentationGenerator:
return md.strip()
def _generate_params_section(self) -> str:
'''
"""
Generates the parameters section.
:return: The parameters section.
:rtype: str
'''
"""
md = ''
@@ -636,7 +650,7 @@ class PythonDefinitionDocumentationGenerator:
)
for arg in self.definition.args
],
allow_markdown_table = self.allow_tables,
allow_markdown_table=self.allow_tables,
)
md += '\n\n'
@@ -647,12 +661,12 @@ class PythonDefinitionDocumentationGenerator:
return md.strip()
def _generate_returns_section(self) -> str:
'''
"""
Generates the returns section.
:return: The returns section.
:rtype: str
'''
"""
md = ''
@@ -686,12 +700,12 @@ class PythonDefinitionDocumentationGenerator:
return md.strip()
def _generate_exceptions_section(self) -> str:
'''
"""
Generates the exceptions section.
:return: The exceptions section.
:rtype: str
'''
"""
md = ''
@@ -699,26 +713,31 @@ class PythonDefinitionDocumentationGenerator:
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)
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 = ''
@@ -727,9 +746,11 @@ class PythonDefinitionDocumentationGenerator:
examples = []
for example in self._docstring.examples:
examples.append('```python\n' +\
(example.snippet or example.description) + \
'\n```')
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:
@@ -738,12 +759,12 @@ class PythonDefinitionDocumentationGenerator:
return md.strip()
def _generate_decorators_section(self) -> str:
'''
"""
Generates the decorators section.
:return: The decorators section.
:rtype: str
'''
"""
md = ''
@@ -759,12 +780,12 @@ class PythonDefinitionDocumentationGenerator:
return md.strip()
def _generate_bases_section(self) -> str:
'''
"""
Generates the bases section.
:return: The bases section.
:rtype: str
'''
"""
md = ''
@@ -780,12 +801,12 @@ class PythonDefinitionDocumentationGenerator:
return md.strip()
def _generate_body_section(self) -> str:
'''
"""
Generates the body section.
:return: The body section.
:rtype: str
'''
"""
md = ''
@@ -793,15 +814,18 @@ class PythonDefinitionDocumentationGenerator:
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'
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.'
@@ -809,12 +833,12 @@ class PythonDefinitionDocumentationGenerator:
return md.strip()
def generate_documentation(self) -> str:
'''
"""
Generates the documentation in the markdown format.
:return: The documentation.
:rtype: str
'''
"""
md = self.level * '#' + ' '
@@ -848,13 +872,13 @@ class PythonDefinitionDocumentationGenerator:
md += self._generate_signature_section() + '\n\n'
if self.include_sections.returns:
md += self._generate_returns_section() + '\n\n'
md += self._generate_returns_section() + '\n\n'
if self.include_sections.params:
md += self._generate_params_section() + '\n\n'
md += self._generate_params_section() + '\n\n'
if self.include_sections.exceptions:
md += self._generate_exceptions_section() + '\n\n'
md += self._generate_exceptions_section() + '\n\n'
if isinstance(self.definition, PythonClassDefinition):
if self.include_sections.bases:
@@ -874,8 +898,9 @@ class PythonDefinitionDocumentationGenerator:
return md.strip()
class PythonModuleDocumentationGenerator:
'''
"""
A documentation generator for a Python module.
:param module_path: The path of the module file.
@@ -887,21 +912,21 @@ class PythonModuleDocumentationGenerator:
:param include_sections: The sections that will be included.
:type include_sections: PythonDefinitionDocumentationIncludeSections
:param kwargs: The options for ``PythonDefinitionDocumentationGenerator``.
'''
"""
def __init__(
self,
module_path: pathlib.Path,
src_directory: Union[pathlib.Path, None] = None,
include_sections: PythonDefinitionDocumentationIncludeSections = (
PythonDefinitionDocumentationIncludeSections()
),
**kwargs,
) -> None:
self,
module_path: pathlib.Path,
src_directory: Union[pathlib.Path, None] = None,
include_sections: PythonDefinitionDocumentationIncludeSections = (
PythonDefinitionDocumentationIncludeSections()
),
**kwargs,
) -> None:
if module_path.name.count('.') > 1:
raise InvalidNamespaceError(
'namespace identifier cannot contain more than one dot',
namespace_identifier = module_path.name,
namespace_identifier=module_path.name,
)
self.module_path = pathlib.Path(str(module_path))
@@ -930,25 +955,24 @@ class PythonModuleDocumentationGenerator:
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 `'
@@ -973,26 +997,30 @@ class PythonModuleDocumentationGenerator:
md += docstring + '\n\n'
for definition in self.collect_definitions():
md += PythonDefinitionDocumentationGenerator(
definition,
self.include_sections,
2,
**self.kwargs,
).generate_documentation() + '\n\n'
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:
'''
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.
@@ -1007,19 +1035,20 @@ def _generate_package_namespace_documentation(
: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)
(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,
root_directory,
dir / sub_namespace.name,
include_sections,
kwargs,
)
@@ -1035,8 +1064,9 @@ def _generate_package_namespace_documentation(
).generate_documentation()
)
class PythonDocumentationGenerator:
'''
"""
The class for the documentation generator.
:param project_directory: The directory of the project.
@@ -1046,17 +1076,17 @@ class PythonDocumentationGenerator:
: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: pathlib.Path,
docs_directory: pathlib.Path,
include_sections: PythonDefinitionDocumentationIncludeSections = (
PythonDefinitionDocumentationIncludeSections()
),
**kwargs,
) -> None:
self,
project_directory: pathlib.Path,
docs_directory: pathlib.Path,
include_sections: PythonDefinitionDocumentationIncludeSections = (
PythonDefinitionDocumentationIncludeSections()
),
**kwargs,
) -> None:
self.project_directory = pathlib.Path(str(project_directory))
self.docs_directory = pathlib.Path(str(docs_directory))
self.include_sections = include_sections
@@ -1074,25 +1104,24 @@ class PythonDocumentationGenerator:
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()
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()),
+42 -38
View File
@@ -31,6 +31,7 @@ __all__ = [
'PythonASTArgumentsListParser',
]
class PythonArgumentKind(Enum):
NORMAL = 0
POSITIONAL_ONLY = 1
@@ -38,8 +39,9 @@ class PythonArgumentKind(Enum):
VARARG = 3
KWARGS = 4
class PythonFunctionArgument:
'''
"""
Represents an argument of a Python function.
:param name: The name of the argument.
@@ -50,7 +52,7 @@ class PythonFunctionArgument:
:type default: Union[str, None]
:param annotation: The type annotation of the argument.
:type annotation: Union[str, None]
'''
"""
def __init__(
self,
@@ -68,7 +70,7 @@ class PythonFunctionArgument:
if not name or not name.isidentifier() or keyword.iskeyword(name):
raise InvalidPythonIdentifierError(
'invalid identifier',
identifier = name,
identifier=name,
)
# check whether the annotation is a valid annotation
@@ -77,7 +79,7 @@ class PythonFunctionArgument:
if annotation is not None and not annotation:
raise InvalidPythonAnnotationError(
'invalid annotation',
annotation = annotation,
annotation=annotation,
)
self.name = name
@@ -85,13 +87,17 @@ class PythonFunctionArgument:
self.default = default
self.annotation = annotation
if self.kind in (
PythonArgumentKind.VARARG,
PythonArgumentKind.KWARGS,
) and self.default is not None:
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(),
definition=self.signature_repr(),
)
def __eq__(self, value: PythonFunctionArgument) -> bool:
@@ -105,20 +111,22 @@ class PythonFunctionArgument:
)
def __repr__(self) -> str:
return type(self).__name__ + repr((
self.name,
self.kind,
self.default,
self.annotation,
))
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 = '**'
@@ -137,48 +145,44 @@ class PythonFunctionArgument:
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:
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
))
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)
for d in self.ast_arguments_list.defaults
]
+ [
ast.unparse(d)
if d is not None
else None
ast.unparse(d) if d is not None else None
for d in self.ast_arguments_list.kw_defaults
]
)
+23 -22
View File
@@ -29,8 +29,9 @@ __all__ = [
'PythonClassDefinition',
]
def _python_docstring(doc: Union[str, None]) -> Union[PythonDocstring, None]:
'''
"""
Returns the PythonDocstring object if the docstring is a string and
not ``None``.
@@ -39,12 +40,13 @@ def _python_docstring(doc: Union[str, None]) -> Union[PythonDocstring, 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.
@@ -57,7 +59,7 @@ class PythonDefinition:
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
'''
"""
def __init__(
self,
@@ -68,7 +70,7 @@ class PythonDefinition:
if keyword.iskeyword(name) or not name.isidentifier() or not name:
raise InvalidPythonIdentifierError(
'invalid identifier',
identifier = name,
identifier=name,
)
assert_that_is_instance(doc, (PythonDocstring, types.NoneType))
@@ -89,12 +91,12 @@ class PythonDefinition:
@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,
@@ -107,8 +109,9 @@ class PythonDefinition:
return False
return self._arg_list() == value._arg_list()
class PythonFunctionDefinition(PythonDefinition):
'''
"""
A PythonDefinition subclass representing a Python function
definition.
@@ -125,7 +128,7 @@ class PythonFunctionDefinition(PythonDefinition):
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
'''
"""
def __init__(
self,
@@ -150,12 +153,12 @@ class PythonFunctionDefinition(PythonDefinition):
@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,
@@ -165,8 +168,9 @@ class PythonFunctionDefinition(PythonDefinition):
[ast.unparse(d) for d in node.decorator_list],
)
class PythonAsyncFunctionDefinition(PythonFunctionDefinition):
'''
"""
A PythonDefinition subclass representing an asynchronous Python function
definition.
@@ -183,10 +187,11 @@ class PythonAsyncFunctionDefinition(PythonFunctionDefinition):
: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.
@@ -202,7 +207,7 @@ class PythonClassDefinition(PythonDefinition):
:raises InvalidPythonIdentifierError: If the name is not a valid
Python identifier.
'''
"""
def __init__(
self,
@@ -227,12 +232,12 @@ class PythonClassDefinition(PythonDefinition):
@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,
@@ -242,16 +247,12 @@ class PythonClassDefinition(PythonDefinition):
[
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)
expr, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)
)
],
)
+6 -7
View File
@@ -15,17 +15,16 @@
from enum import Enum
import docstring_parser
__all__ = [
'PythonDocstring'
]
__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
@@ -39,11 +38,11 @@ class PythonDocstring:
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)
+24 -19
View File
@@ -27,20 +27,22 @@ __all__ = [
'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,
namespace_identifier=name,
)
self.name = name
@@ -48,6 +50,7 @@ class PythonNamespace:
def __repr__(self) -> str:
return f'{type(self).__name__}({self.name!r})'
PythonPackageNamespaceDictType: TypeAlias = Dict[
str,
Union[
@@ -56,15 +59,16 @@ PythonPackageNamespaceDictType: TypeAlias = Dict[
],
]
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,
@@ -78,7 +82,7 @@ class PythonPackageNamespace(PythonNamespace):
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']``.
@@ -91,7 +95,7 @@ class PythonPackageNamespace(PythonNamespace):
:return: The namespace.
:rtype: PythonNamespace
'''
"""
if not sub_namespaces:
raise InvalidNamespaceError('invalid namespace')
@@ -115,7 +119,7 @@ class PythonPackageNamespace(PythonNamespace):
self,
package: PythonPackageNamespace,
) -> PythonPackageNamespaceDictType:
'''
"""
Returns the names of all sub-namespaces of the specified package
namespace and keeps the package structure.
@@ -124,7 +128,7 @@ class PythonPackageNamespace(PythonNamespace):
:return: The names if all sub-namespaces.
:rtype: PythonPackageNamespaceDictType
'''
"""
return {
sn.name: (
@@ -136,13 +140,13 @@ class PythonPackageNamespace(PythonNamespace):
}
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)
@@ -159,7 +163,7 @@ class PythonPackageNamespace(PythonNamespace):
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
@@ -169,7 +173,7 @@ class PythonPackageNamespace(PythonNamespace):
:return: The namespace
:rtype: PythonNamespace
'''
"""
for sub in self._sub_namespaces:
if sub.name == name:
@@ -177,11 +181,11 @@ class PythonPackageNamespace(PythonNamespace):
raise NamespaceNotFoundError(
'no such namespace',
namespace_identifier = name,
namespace_identifier=name,
)
def add(self, namespace: PythonNamespace) -> None:
'''
"""
Adds a namespace.
:param namespace: The namespace to add.
@@ -190,12 +194,12 @@ class PythonPackageNamespace(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,
namespace_identifier=namespace.name,
)
except NamespaceNotFoundError:
self._sub_namespaces.append(namespace)
@@ -204,13 +208,14 @@ class PythonPackageNamespace(PythonNamespace):
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)