generated from jCloud/repository-template
style: format source code using Ruff
This commit is contained in:
41 files changed
+6061
-5670
No files matched your search
@@ -12,9 +12,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''
|
||||
"""
|
||||
The command line interface
|
||||
'''
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import pathlib
|
||||
@@ -31,80 +31,81 @@ __all__ = [
|
||||
_LANG_PYTHON = 'python'
|
||||
_PROGRAM_NAME = 'jcloud-docsgen'
|
||||
|
||||
|
||||
def existing_directory(path: str) -> pathlib.Path:
|
||||
_path = path
|
||||
path = pathlib.Path(_path)
|
||||
if not path.exists():
|
||||
raise argparse.ArgumentTypeError(
|
||||
f'{_path}: no such file or directory'
|
||||
)
|
||||
raise argparse.ArgumentTypeError(f'{_path}: no such file or directory')
|
||||
if not path.is_dir():
|
||||
raise argparse.ArgumentTypeError(f'{_path}: not a directory')
|
||||
return path
|
||||
|
||||
|
||||
def error(message: str, exit_code: int = 1):
|
||||
'''
|
||||
"""
|
||||
Shows an error message and returns the exit code.
|
||||
|
||||
:param message: The error message to show.
|
||||
:type message: str
|
||||
:param exit_code: The exit code.
|
||||
:type exit_code: int
|
||||
'''
|
||||
"""
|
||||
|
||||
print(f'{_PROGRAM_NAME}: error: {message}', file = sys.stderr)
|
||||
print(f'{_PROGRAM_NAME}: error: {message}', file=sys.stderr)
|
||||
return exit_code
|
||||
|
||||
|
||||
def create_argument_parser() -> argparse.ArgumentParser:
|
||||
'''
|
||||
"""
|
||||
Prepares a parser to parse the command-line arguments using
|
||||
argparse.
|
||||
|
||||
:return: The parser.
|
||||
:rtype: argparse.ArgumentParser
|
||||
'''
|
||||
"""
|
||||
|
||||
common_parser = argparse.ArgumentParser(add_help = False)
|
||||
common_parser = argparse.ArgumentParser(add_help=False)
|
||||
common_parser.add_argument(
|
||||
'project_directory',
|
||||
type = existing_directory,
|
||||
nargs = '?',
|
||||
default = pathlib.Path('.'),
|
||||
help = 'The directory of the project',
|
||||
type=existing_directory,
|
||||
nargs='?',
|
||||
default=pathlib.Path('.'),
|
||||
help='The directory of the project',
|
||||
)
|
||||
common_parser.add_argument(
|
||||
'-o',
|
||||
'--output-dir',
|
||||
type = pathlib.Path,
|
||||
help = 'the output directory the documentation will be written to',
|
||||
type=pathlib.Path,
|
||||
help='the output directory the documentation will be written to',
|
||||
)
|
||||
|
||||
argument_parser = argparse.ArgumentParser()
|
||||
|
||||
subparsers = argument_parser.add_subparsers(
|
||||
dest = 'language',
|
||||
required = True,
|
||||
dest='language',
|
||||
required=True,
|
||||
)
|
||||
|
||||
python_parser = subparsers.add_parser(
|
||||
_LANG_PYTHON,
|
||||
parents = [common_parser],
|
||||
help = 'generate documentation for Python project',
|
||||
parents=[common_parser],
|
||||
help='generate documentation for Python project',
|
||||
)
|
||||
python_parser.add_argument(
|
||||
'-s',
|
||||
'--sections',
|
||||
nargs = '+',
|
||||
choices = VALID_PYTHON_SECTIONS,
|
||||
help = 'the sections to be included in the documentation',
|
||||
nargs='+',
|
||||
choices=VALID_PYTHON_SECTIONS,
|
||||
help='the sections to be included in the documentation',
|
||||
)
|
||||
python_parser.add_argument(
|
||||
'-S',
|
||||
'--sections-exclude',
|
||||
nargs = '+',
|
||||
default = [],
|
||||
choices = VALID_PYTHON_SECTIONS,
|
||||
help = (
|
||||
nargs='+',
|
||||
default=[],
|
||||
choices=VALID_PYTHON_SECTIONS,
|
||||
help=(
|
||||
'the sections to be excluded in the documentation. '
|
||||
'If a section shall be included AND excluded, it is excluded.'
|
||||
),
|
||||
@@ -112,8 +113,8 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
||||
python_parser.add_argument(
|
||||
'-H',
|
||||
'--allow-html',
|
||||
action = 'store_true',
|
||||
help = (
|
||||
action='store_true',
|
||||
help=(
|
||||
'indicates whether HTML is allowed in the documentation. '
|
||||
'By default, HTML is disabled.'
|
||||
),
|
||||
@@ -121,8 +122,8 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
||||
python_parser.add_argument(
|
||||
'-T',
|
||||
'--allow-tables',
|
||||
action = 'store_true',
|
||||
help = (
|
||||
action='store_true',
|
||||
help=(
|
||||
'indicates whether Markdown tables are allowed in the '
|
||||
'documentation. By default, Markdown tables are disabled. '
|
||||
'Warning: Markdown tables are not CommonMark.'
|
||||
@@ -131,7 +132,8 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
||||
|
||||
return argument_parser
|
||||
|
||||
def main(argv = None) -> int:
|
||||
|
||||
def main(argv=None) -> int:
|
||||
argument_parser = create_argument_parser()
|
||||
args = argument_parser.parse_args(argv)
|
||||
|
||||
@@ -141,13 +143,14 @@ def main(argv = None) -> int:
|
||||
try:
|
||||
if args.language == _LANG_PYTHON:
|
||||
from .languages.python import generate
|
||||
|
||||
generate(
|
||||
args.project_directory,
|
||||
args.output_dir,
|
||||
include_sections = args.sections,
|
||||
exclude_sections = args.sections_exclude,
|
||||
allow_html = args.allow_html,
|
||||
allow_tables = args.allow_tables,
|
||||
include_sections=args.sections,
|
||||
exclude_sections=args.sections_exclude,
|
||||
allow_html=args.allow_html,
|
||||
allow_tables=args.allow_tables,
|
||||
)
|
||||
except FileNotFoundError as exc:
|
||||
return error(f'{exc.filename}: no such file or directory')
|
||||
|
||||
@@ -29,11 +29,12 @@ VALID_SECTIONS = list(
|
||||
PythonDefinitionDocumentationIncludeSections()._args.keys()
|
||||
)
|
||||
|
||||
|
||||
def parse_sections_list(
|
||||
include_sections: Optional[list[str]] = None,
|
||||
exclude_sections: list[str] = [],
|
||||
) -> PythonDefinitionDocumentationIncludeSections:
|
||||
'''
|
||||
"""
|
||||
Returns a Python definition documentation sections list from the
|
||||
sections list to be included and the sections list to be excluded.
|
||||
These lists both consist of the sections as strings which contain
|
||||
@@ -46,7 +47,7 @@ def parse_sections_list(
|
||||
|
||||
:return: The Python definition documentation sections object.
|
||||
:rtype: PythonDefinitionDocumentationIncludeSections
|
||||
'''
|
||||
"""
|
||||
|
||||
if include_sections is None:
|
||||
include_sections = VALID_SECTIONS[:]
|
||||
@@ -56,17 +57,18 @@ def parse_sections_list(
|
||||
del include_sections[include_sections.index(section)]
|
||||
|
||||
return PythonDefinitionDocumentationIncludeSections(
|
||||
signature = 'signature' in include_sections,
|
||||
returns = 'returns' in include_sections,
|
||||
params = 'params' in include_sections,
|
||||
exceptions = 'exceptions' in include_sections,
|
||||
examples = 'examples' in include_sections,
|
||||
docstring = 'docstring' in include_sections,
|
||||
bases = 'bases' in include_sections,
|
||||
decorators = 'decorators' in include_sections,
|
||||
class_body = 'class_body' in include_sections,
|
||||
signature='signature' in include_sections,
|
||||
returns='returns' in include_sections,
|
||||
params='params' in include_sections,
|
||||
exceptions='exceptions' in include_sections,
|
||||
examples='examples' in include_sections,
|
||||
docstring='docstring' in include_sections,
|
||||
bases='bases' in include_sections,
|
||||
decorators='decorators' in include_sections,
|
||||
class_body='class_body' in include_sections,
|
||||
)
|
||||
|
||||
|
||||
def _generate(
|
||||
project_directory: pathlib.Path,
|
||||
output_directory: pathlib.Path,
|
||||
@@ -77,7 +79,7 @@ def _generate(
|
||||
allow_html: bool = False,
|
||||
allow_tables: bool = False,
|
||||
) -> None:
|
||||
'''
|
||||
"""
|
||||
Generates the documentation for a Python project.
|
||||
|
||||
:param project_directory: The project directory.
|
||||
@@ -92,17 +94,18 @@ def _generate(
|
||||
:type allow_html: bool
|
||||
:param allow_tables: Indicates whether Markdown tables are allowed.
|
||||
Warning: Markdown tables are not CommonMark.
|
||||
'''
|
||||
"""
|
||||
|
||||
docs_generator = PythonDocumentationGenerator(
|
||||
project_directory,
|
||||
output_directory,
|
||||
include_sections = include_sections,
|
||||
allow_html = allow_html,
|
||||
allow_tables = allow_tables,
|
||||
include_sections=include_sections,
|
||||
allow_html=allow_html,
|
||||
allow_tables=allow_tables,
|
||||
)
|
||||
docs_generator.generate_documentation()
|
||||
|
||||
|
||||
def generate(
|
||||
project_directory: pathlib.Path,
|
||||
output_directory: pathlib.Path,
|
||||
@@ -112,7 +115,7 @@ def generate(
|
||||
allow_html: bool = False,
|
||||
allow_tables: bool = False,
|
||||
) -> None:
|
||||
'''
|
||||
"""
|
||||
Generates the documentation for a Python project.
|
||||
|
||||
:param project_directory: The project directory.
|
||||
@@ -130,14 +133,14 @@ def generate(
|
||||
:type allow_html: bool
|
||||
:param allow_tables: Indicates whether Markdown tables are allowed.
|
||||
Warning: Markdown tables are not CommonMark.
|
||||
'''
|
||||
"""
|
||||
|
||||
sections = parse_sections_list(include_sections, exclude_sections)
|
||||
|
||||
_generate(
|
||||
project_directory,
|
||||
output_directory,
|
||||
include_sections = sections,
|
||||
allow_html = allow_html,
|
||||
allow_tables = allow_tables,
|
||||
include_sections=sections,
|
||||
allow_html=allow_html,
|
||||
allow_tables=allow_tables,
|
||||
)
|
||||
@@ -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()),
|
||||
|
||||
@@ -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
|
||||
]
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
# 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
|
||||
@@ -25,23 +27,24 @@ class NamespaceError(ValueError):
|
||||
return ''
|
||||
else:
|
||||
return f'{self.args[0]}{
|
||||
": "
|
||||
if self.namespace_identifier and self.args[0]
|
||||
else ""
|
||||
}{
|
||||
self.namespace_identifier
|
||||
if self.args[0]
|
||||
else ""
|
||||
}'
|
||||
": " 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
|
||||
@@ -51,21 +54,18 @@ class PythonIdentifierError(ValueError):
|
||||
return ''
|
||||
else:
|
||||
return f'{self.args[0]}{
|
||||
": "
|
||||
if self.identifier and self.args[0]
|
||||
else ""
|
||||
}{
|
||||
self.identifier
|
||||
if self.args[0]
|
||||
else ""
|
||||
}'
|
||||
": " 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
|
||||
@@ -75,21 +75,18 @@ class PythonAnnotationError(ValueError):
|
||||
return ''
|
||||
else:
|
||||
return f'{self.args[0]}{
|
||||
": "
|
||||
if self.annotation and self.args[0]
|
||||
else ""
|
||||
}{
|
||||
self.annotation
|
||||
if self.args[0]
|
||||
else ""
|
||||
}'
|
||||
": " 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
|
||||
@@ -99,13 +96,8 @@ class PythonDefinitionError(ValueError):
|
||||
return ''
|
||||
else:
|
||||
return f'{self.args[0]}{
|
||||
": "
|
||||
if self.definition and self.args[0]
|
||||
else ""
|
||||
}{
|
||||
self.definition
|
||||
if self.args[0]
|
||||
else ""
|
||||
}'
|
||||
": " if self.definition and self.args[0] else ""
|
||||
}{self.definition if self.args[0] else ""}'
|
||||
|
||||
|
||||
class PythonArgumentStructureError(PythonDefinitionError): ...
|
||||
+51
-32
@@ -12,9 +12,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
'''
|
||||
"""
|
||||
The utilities for jcloud_docsgen
|
||||
'''
|
||||
"""
|
||||
|
||||
import pathlib
|
||||
import os
|
||||
@@ -27,15 +27,17 @@ try:
|
||||
except ImportError:
|
||||
from typing_extensions import deprecated
|
||||
|
||||
|
||||
@deprecated('Use pathlib.Path instead.')
|
||||
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(
|
||||
@@ -47,15 +49,17 @@ class ExistingDirectory(pathlib.Path):
|
||||
raise NotADirectoryError(errno.ENOTDIR, 'not a directory', path)
|
||||
super().__init__(*(path, *args), **kwargs)
|
||||
|
||||
|
||||
@deprecated('Use pathlib.Path instead.')
|
||||
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(
|
||||
@@ -67,8 +71,9 @@ class ExistingFile(pathlib.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
|
||||
@@ -78,16 +83,17 @@ def _quote(string: str, quotation_mark: str) -> 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.
|
||||
@@ -101,7 +107,7 @@ def human_readable_list(
|
||||
|
||||
:return: The human-readable list.
|
||||
:rtype: str
|
||||
'''
|
||||
"""
|
||||
|
||||
# empty list
|
||||
if not ls:
|
||||
@@ -111,18 +117,26 @@ def human_readable_list(
|
||||
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)
|
||||
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)
|
||||
|
||||
@@ -131,10 +145,11 @@ def _list_type_names(types: list[type]) -> list[str]:
|
||||
|
||||
: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[
|
||||
@@ -145,8 +160,8 @@ def assert_that_is_instance(
|
||||
) -> None:
|
||||
if not isinstance(class_or_tuple, Union[type, types.UnionType, tuple]):
|
||||
raise TypeError(
|
||||
f'class_or_tuple: expected \'Union[type, types.UnionType, '
|
||||
f'tuple[type, ...]]\', \'got {type(class_or_tuple).__name__}\'',
|
||||
f"class_or_tuple: expected 'Union[type, types.UnionType, "
|
||||
f"tuple[type, ...]]', 'got {type(class_or_tuple).__name__}'",
|
||||
)
|
||||
if not isinstance(obj, class_or_tuple):
|
||||
if isinstance(class_or_tuple, (tuple, types.UnionType)):
|
||||
@@ -157,18 +172,21 @@ def assert_that_is_instance(
|
||||
else:
|
||||
exception_message_expected = ''
|
||||
exception_message_expected += human_readable_list(
|
||||
_list_type_names(class_or_tuple), 'or', '\'',
|
||||
_list_type_names(class_or_tuple),
|
||||
'or',
|
||||
"'",
|
||||
)
|
||||
else:
|
||||
exception_message_expected = '\'' + class_or_tuple.__name__ + '\''
|
||||
exception_message_expected = "'" + class_or_tuple.__name__ + "'"
|
||||
raise TypeError(
|
||||
f'expected {exception_message_expected}, got \'{type(
|
||||
obj
|
||||
).__name__}\'',
|
||||
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,
|
||||
@@ -181,7 +199,7 @@ def non_empty_str(value: Union[str, None]) -> Union[str, None]:
|
||||
|
||||
:return: The value or ``None``.
|
||||
:rtype: Union[str, None]
|
||||
'''
|
||||
"""
|
||||
|
||||
if value is None:
|
||||
return None
|
||||
@@ -191,11 +209,12 @@ def non_empty_str(value: Union[str, None]) -> Union[str, 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.
|
||||
@@ -205,7 +224,7 @@ def get_relative_path_segments(
|
||||
|
||||
:return: The path segments relative to ``root``.
|
||||
:rtype: list[str]
|
||||
'''
|
||||
"""
|
||||
|
||||
return list(
|
||||
path.resolve()
|
||||
|
||||
Reference in new issue
Block a user