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

+40 -37
View File
@@ -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')
+24 -21
View File
@@ -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,
)
+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)
+32 -40
View File
@@ -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
View File
@@ -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()
+9 -7
View File
@@ -15,6 +15,7 @@
import subprocess
import sys
def test_cli():
result = subprocess.run(
[
@@ -23,12 +24,13 @@ def test_cli():
'jcloud_docsgen',
'--help',
],
capture_output = True,
text = True,
capture_output=True,
text=True,
)
stdout = result.stdout.lower()
assert \
'usage:' in stdout and \
'positional arguments' in stdout and \
'options' in stdout and \
'--help' in stdout
assert (
'usage:' in stdout
and 'positional arguments' in stdout
and 'options' in stdout
and '--help' in stdout
)
@@ -1,5 +1,5 @@
def f(a: int = 42) -> str:
'''
"""
Does something.
:param a: A parameter.
@@ -7,5 +7,5 @@ def f(a: int = 42) -> str:
:return: A string.
:rtype: str
'''
"""
...
+16 -8
View File
@@ -15,20 +15,28 @@
import pytest
from jcloud_docsgen.cli.cli import create_argument_parser
@pytest.mark.parametrize('lang', [
'javascript',
'Python',
'PYTHON',
])
@pytest.mark.parametrize(
'lang',
[
'javascript',
'Python',
'PYTHON',
],
)
def test_language_error(lang):
parser = create_argument_parser()
with pytest.raises(SystemExit):
parser.parse_args([lang])
@pytest.mark.parametrize('lang', [
'python',
])
@pytest.mark.parametrize(
'lang',
[
'python',
],
)
def test_language_success(lang):
parser = create_argument_parser()
parser.parse_args([lang])
+140 -123
View File
@@ -17,152 +17,169 @@ import pytest
from jcloud_docsgen.cli.cli import create_argument_parser, main as cli_main
from jcloud_docsgen.cli.languages.python import parse_sections_list
from jcloud_docsgen.core.python import (
PythonDefinitionDocumentationIncludeSections
PythonDefinitionDocumentationIncludeSections,
)
@pytest.mark.parametrize('include,exclude,expected', [
(
[],
[],
PythonDefinitionDocumentationIncludeSections(
signature = False,
returns = False,
params = False,
exceptions = False,
examples = False,
docstring = False,
bases = False,
decorators = False,
class_body = False,
@pytest.mark.parametrize(
'include,exclude,expected',
[
(
[],
[],
PythonDefinitionDocumentationIncludeSections(
signature=False,
returns=False,
params=False,
exceptions=False,
examples=False,
docstring=False,
bases=False,
decorators=False,
class_body=False,
),
),
),
(
[],
['signature'],
PythonDefinitionDocumentationIncludeSections(
signature = False,
returns = False,
params = False,
exceptions = False,
examples = False,
docstring = False,
bases = False,
decorators = False,
class_body = False,
(
[],
['signature'],
PythonDefinitionDocumentationIncludeSections(
signature=False,
returns=False,
params=False,
exceptions=False,
examples=False,
docstring=False,
bases=False,
decorators=False,
class_body=False,
),
),
),
(
['signature'],
['signature'],
PythonDefinitionDocumentationIncludeSections(
signature = False,
returns = False,
params = False,
exceptions = False,
examples = False,
docstring = False,
bases = False,
decorators = False,
class_body = False,
(
['signature'],
['signature'],
PythonDefinitionDocumentationIncludeSections(
signature=False,
returns=False,
params=False,
exceptions=False,
examples=False,
docstring=False,
bases=False,
decorators=False,
class_body=False,
),
),
),
(
['signature'],
[],
PythonDefinitionDocumentationIncludeSections(
signature = True,
returns = False,
params = False,
exceptions = False,
examples = False,
docstring = False,
bases = False,
decorators = False,
class_body = False,
(
['signature'],
[],
PythonDefinitionDocumentationIncludeSections(
signature=True,
returns=False,
params=False,
exceptions=False,
examples=False,
docstring=False,
bases=False,
decorators=False,
class_body=False,
),
),
),
(
None,
[],
PythonDefinitionDocumentationIncludeSections(),
),
(
['signature', 'exceptions', 'bases', 'class_body'],
['signature', 'bases'],
PythonDefinitionDocumentationIncludeSections(
signature = False,
returns = False,
params = False,
exceptions = True,
examples = False,
docstring = False,
bases = False,
decorators = False,
class_body = True,
(
None,
[],
PythonDefinitionDocumentationIncludeSections(),
),
),
(
None,
['params', 'docstring'],
PythonDefinitionDocumentationIncludeSections(
signature = True,
returns = True,
params = False,
exceptions = True,
examples = True,
docstring = False,
bases = True,
decorators = True,
class_body = True,
(
['signature', 'exceptions', 'bases', 'class_body'],
['signature', 'bases'],
PythonDefinitionDocumentationIncludeSections(
signature=False,
returns=False,
params=False,
exceptions=True,
examples=False,
docstring=False,
bases=False,
decorators=False,
class_body=True,
),
),
)
])
(
None,
['params', 'docstring'],
PythonDefinitionDocumentationIncludeSections(
signature=True,
returns=True,
params=False,
exceptions=True,
examples=True,
docstring=False,
bases=True,
decorators=True,
class_body=True,
),
),
],
)
def test_parse_sections_list(include, exclude, expected):
assert parse_sections_list(include, exclude) == expected
def test_sections_error():
parser = create_argument_parser()
with pytest.raises(SystemExit):
parser.parse_args([
'python',
'-s',
'param', # correct: `params`
])
parser.parse_args(
[
'python',
'-s',
'param', # correct: `params`
]
)
with pytest.raises(SystemExit):
parser.parse_args([
'python',
'-s',
'params',
'docstrng', # correct: `docstring`
])
parser.parse_args(
[
'python',
'-s',
'params',
'docstrng', # correct: `docstring`
]
)
def test_sections_success():
parser = create_argument_parser()
parser.parse_args([
'python',
'-s',
'params',
])
parser.parse_args([
'python',
'-s',
'params',
'docstring',
])
parser.parse_args(
[
'python',
'-s',
'params',
]
)
parser.parse_args(
[
'python',
'-s',
'params',
'docstring',
]
)
def test_docs_generation(expectdir):
with expectdir('python_project') as output_dir:
output_dir: pathlib.Path
input_dir = pathlib.Path(__file__).parent / 'python_project/project'
cli_main([
'python',
'-s',
'params',
'docstring',
'-HTo',
str(output_dir),
str(input_dir),
])
cli_main(
[
'python',
'-s',
'params',
'docstring',
'-HTo',
str(output_dir),
str(input_dir),
]
)
File diff suppressed because it is too large. Load diff
@@ -20,78 +20,82 @@ from src.jcloud_docsgen.core.python.namespaces import (
import pytest
import pathlib
@pytest.mark.parametrize('python_documentation_generator,expected', [
(
PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_1'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_1/docs'
@pytest.mark.parametrize(
'python_documentation_generator,expected',
[
(
PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_1'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_1/docs'
),
),
[],
),
[],
),
(
PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_2'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_2/docs'
(
PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_2'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_2/docs'
),
),
[
PythonPackageNamespace(
'pkg',
[PythonModuleNamespace('module')],
),
],
),
[
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace('module')
],
),
],
),
(
PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_3'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_3/docs'
(
PythonDocumentationGenerator(
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_3'
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_3/docs'
),
),
[
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace('module'),
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace('module2'),
],
),
],
),
],
),
[
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace('module'),
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace(
'module2'
),
],
),
],
),
],
),
])
],
)
def test_PythonDocumentationGenerator_collect_modules(
python_documentation_generator: PythonDocumentationGenerator,
expected,
):
assert python_documentation_generator.namespace() == expected
@pytest.mark.parametrize('dir', [
d.name
for d in pathlib.Path(
(
'tests/unit/core/python/_core/test_PythonDocume'
'ntationGenerator/test_generate_documentation/'
),
).iterdir()
])
@pytest.mark.parametrize(
'dir',
[
d.name
for d in pathlib.Path(
(
'tests/unit/core/python/_core/test_PythonDocume'
'ntationGenerator/test_generate_documentation/'
),
).iterdir()
],
)
def test_generate_documentation(dir, expectdir):
with expectdir(
'test_PythonDocumentationGenerator/test_generate_documentation/' + dir
@@ -104,12 +108,12 @@ def test_generate_documentation(dir, expectdir):
'tests/unit/core/python/_core/test_PythonDocume'
'ntationGenerator/test_generate_documentation/'
),
) /
dir /
'project',
)
/ dir
/ 'project',
),
),
pathlib.Path(str(d)),
allow_tables = True,
allow_html = False,
).generate_documentation() # simplify the tests
allow_tables=True,
allow_html=False,
).generate_documentation() # simplify the tests
@@ -1,8 +1,10 @@
class UserNotFoundError(Exception):
"""User does not exist."""
pass
class InvalidEmailError(Exception):
"""Email address is invalid."""
pass
pass
@@ -7,7 +7,8 @@ class User:
"""
Represents a user.
"""
id: str
name: str
email: str
age: Optional[int] = None
age: Optional[int] = None
@@ -13,23 +13,16 @@ class UserService:
self.storage = storage
def create_user(
self,
user_id: str,
name: str,
email: str,
age: int | None = None
self, user_id: str, name: str, email: str, age: int | None = None
) -> User:
"""
Creates and stores a new user.
"""
if not is_valid_email(email):
raise InvalidEmailError(f"Invalid email: {email}")
raise InvalidEmailError(f'Invalid email: {email}')
user = User(
id=user_id,
name=normalize_name(name),
email=email,
age=age
id=user_id, name=normalize_name(name), email=email, age=age
)
self.storage.add(user)
@@ -51,4 +44,4 @@ class UserService:
try:
self.storage.delete(user_id)
except KeyError:
raise UserNotFoundError(user_id)
raise UserNotFoundError(user_id)
@@ -25,4 +25,4 @@ class InMemoryUserStorage:
def list_all(self) -> List[User]:
"""Returns all users."""
return list(self._users.values())
return list(self._users.values())
@@ -5,7 +5,7 @@ def is_valid_email(email: str) -> bool:
"""
Checks whether an email address is valid.
"""
pattern = r"^[\w\.-]+@[\w\.-]+\.\w+$"
pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
return re.match(pattern, email) is not None
@@ -13,4 +13,4 @@ def normalize_name(name: str) -> str:
"""
Removes extra spaces and formats the name.
"""
return " ".join(name.strip().split()).title()
return ' '.join(name.strip().split()).title()
@@ -17,6 +17,7 @@ from src.jcloud_docsgen.exceptions import InvalidNamespaceError
import pathlib
import pytest
@pytest.mark.parametrize(
'module_path,src_directory,expected_exception,expected_exception_msg',
[
@@ -216,22 +217,25 @@ def test_PythonModuleDocumentationGenerator_exceptions(
PythonModuleDocumentationGenerator(module_path, src_directory)
assert str(exc_info.value) == expected_exception_msg
@pytest.mark.parametrize('module_documentation_generator,expected', [
(
PythonModuleDocumentationGenerator(
pathlib.Path(
(
'tests/unit/core/python/_core/test_pr'
'oject_dirs/pdir_2/src/pkg/module.py'
@pytest.mark.parametrize(
'module_documentation_generator,expected',
[
(
PythonModuleDocumentationGenerator(
pathlib.Path(
(
'tests/unit/core/python/_core/test_pr'
'oject_dirs/pdir_2/src/pkg/module.py'
),
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_2/src/'
),
allow_tables=True,
allow_html=False,
),
pathlib.Path(
'tests/unit/core/python/_core/test_project_dirs/pdir_2/src/'
),
allow_tables = True,
allow_html = False,
),
'''# Module `pkg.module`
"""# Module `pkg.module`
A test module.
@@ -398,205 +402,21 @@ Barks.
### Docstring
A dog.'''
),
(
PythonModuleDocumentationGenerator(
pathlib.Path(
(
'tests/unit/core/python/_core/test_pr'
'oject_dirs/pdir_2/src/pkg/module.py'
),
),
None,
allow_tables = True,
allow_html = False,
),
'''# Module `module`
A test module.
## Function `add`
Adds two numbers.
### Function signature
```python
add(a: int | float, b: int | float) -> int | float
```
### Returns
The sum.
Return type: `int | float`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `a` | `int | float` | normal | -- | The first number. |
| `b` | `int | float` | normal | -- | The second number. |
### Docstring
Adds two numbers.
:param a: The first number.
:type a: int | float
:param b: The second number.
:type b: int | float
:return: The sum.
:rtype: int | float
## Class `Animal`
A class representing an animal.
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, name: str) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `name` | `str` | normal | -- | -- |
#### Method `greet`
Returns a greeting.
##### Method signature
```python
greet(self)
```
##### Returns
The greeting.
Return type: `str`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
##### Docstring
Returns a greeting.
:return: The greeting.
:rtype: str
### Docstring
A class representing an animal.
:param name: Its name.
:type name: str
## Class `Dog`
A dog.
### Base classes
- `Animal`
### Body
#### Method `bark`
Barks.
##### Method signature
```python
bark(self) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
##### Docstring
Barks.
### Docstring
A dog.'''
),
(
PythonModuleDocumentationGenerator(
pathlib.Path(
(
'tests/unit/core/python/_core/test_pr'
'oject_dirs/pdir_2/src/pkg/module.py'
),
),
None,
allow_tables = True,
allow_html = True,
A dog.""",
),
(
'''# Module `module`
PythonModuleDocumentationGenerator(
pathlib.Path(
(
'tests/unit/core/python/_core/test_pr'
'oject_dirs/pdir_2/src/pkg/module.py'
),
),
None,
allow_tables=True,
allow_html=False,
),
"""# Module `module`
A test module.
@@ -630,8 +450,190 @@ Return type: `int | float`
<details><summary><h3 style="display:inline">Docstring</h3></summary>Adds '''
'''two numbers.
### Docstring
Adds two numbers.
:param a: The first number.
:type a: int | float
:param b: The second number.
:type b: int | float
:return: The sum.
:rtype: int | float
## Class `Animal`
A class representing an animal.
### Body
#### Constructor (method `__init__`)
##### Method signature
```python
__init__(self, name: str) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
| `name` | `str` | normal | -- | -- |
#### Method `greet`
Returns a greeting.
##### Method signature
```python
greet(self)
```
##### Returns
The greeting.
Return type: `str`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
##### Docstring
Returns a greeting.
:return: The greeting.
:rtype: str
### Docstring
A class representing an animal.
:param name: Its name.
:type name: str
## Class `Dog`
A dog.
### Base classes
- `Animal`
### Body
#### Method `bark`
Barks.
##### Method signature
```python
bark(self) -> None
```
##### Returns
Return type: `None`
##### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `self` | -- | normal | -- | -- |
##### Docstring
Barks.
### Docstring
A dog.""",
),
(
PythonModuleDocumentationGenerator(
pathlib.Path(
(
'tests/unit/core/python/_core/test_pr'
'oject_dirs/pdir_2/src/pkg/module.py'
),
),
None,
allow_tables=True,
allow_html=True,
),
(
"""# Module `module`
A test module.
## Function `add`
Adds two numbers.
### Function signature
```python
add(a: int | float, b: int | float) -> int | float
```
### Returns
The sum.
Return type: `int | float`
### Parameters
| Parameter | Type | Kind | Default value | Description |
| --- | --- | --- | --- | --- |
| `a` | `int | float` | normal | -- | The first number. |
| `b` | `int | float` | normal | -- | The second number. |
<details><summary><h3 style="display:inline">Docstring</h3></summary>Adds """
"""two numbers.
:param a: The first number.
:type a: int | float
@@ -698,8 +700,8 @@ Return type: `str`
<details><summary><h5 style="display:inline">Docstring</h5></summary>'''
'''Returns a greeting.
<details><summary><h5 style="display:inline">Docstring</h5></summary>"""
"""Returns a greeting.
:return: The greeting.
:rtype: str</details>
@@ -708,8 +710,8 @@ Return type: `str`
<details><summary><h3 style="display:inline">Docstring</h3></summary>A '''
'''class representing an animal.
<details><summary><h3 style="display:inline">Docstring</h3></summary>A """
"""class representing an animal.
:param name: Its name.
:type name: str</details>
@@ -750,32 +752,31 @@ Return type: `None`
<details><summary><h5 style="display:inline">Docstring</h5></summary>Barks.'''
'''</details>
<details><summary><h5 style="display:inline">Docstring</h5></summary>Barks."""
"""</details>
<details><summary><h3 style="display:inline">Docstring</h3></summary>A dog.'''
'</details>'
),
),
(
PythonModuleDocumentationGenerator(
pathlib.Path(
(
'tests/unit/core/python/_core/test_pr'
'oject_dirs/pdir_2/src/pkg/module.py'
),
<details><summary><h3 style="display:inline">Docstring</h3></summary>A dog."""
'</details>'
),
None,
allow_tables = False,
allow_html = True,
),
(
'''# Module `module`
PythonModuleDocumentationGenerator(
pathlib.Path(
(
'tests/unit/core/python/_core/test_pr'
'oject_dirs/pdir_2/src/pkg/module.py'
),
),
None,
allow_tables=False,
allow_html=True,
),
(
"""# Module `module`
A test module.
@@ -824,8 +825,8 @@ Return type: `int | float`
<details><summary><h3 style="display:inline">Docstring</h3></summary>Adds '''
'''two numbers.
<details><summary><h3 style="display:inline">Docstring</h3></summary>Adds """
"""two numbers.
:param a: The first number.
:type a: int | float
@@ -913,8 +914,8 @@ Return type: `str`
<details><summary><h5 style="display:inline">Docstring</h5></summary>'''
'''Returns a greeting.
<details><summary><h5 style="display:inline">Docstring</h5></summary>"""
"""Returns a greeting.
:return: The greeting.
:rtype: str</details>
@@ -923,8 +924,8 @@ Return type: `str`
<details><summary><h3 style="display:inline">Docstring</h3></summary>A '''
'''class representing an animal.
<details><summary><h3 style="display:inline">Docstring</h3></summary>A """
"""class representing an animal.
:param name: Its name.
:type name: str</details>
@@ -971,31 +972,30 @@ Return type: `None`
<details><summary><h5 style="display:inline">Docstring</h5></summary>Barks.'''
'''</details>
<details><summary><h5 style="display:inline">Docstring</h5></summary>Barks."""
"""</details>
<details><summary><h3 style="display:inline">Docstring</h3></summary>A '''
'dog.</details>'
),
),
(
PythonModuleDocumentationGenerator(
pathlib.Path(
(
'tests/unit/core/python/_core/test_pr'
'oject_dirs/pdir_2/src/pkg/module.py'
),
<details><summary><h3 style="display:inline">Docstring</h3></summary>A """
'dog.</details>'
),
None,
allow_tables = False,
allow_html = False,
),
'''# Module `module`
(
PythonModuleDocumentationGenerator(
pathlib.Path(
(
'tests/unit/core/python/_core/test_pr'
'oject_dirs/pdir_2/src/pkg/module.py'
),
),
None,
allow_tables=False,
allow_html=False,
),
"""# Module `module`
A test module.
@@ -1204,9 +1204,10 @@ Barks.
### Docstring
A dog.'''
),
])
A dog.""",
),
],
)
def test_PythonModuleDocumentationGenerator_exceptions(
module_documentation_generator,
expected,
@@ -16,12 +16,16 @@ from src.jcloud_docsgen.core.python._core import _argument_kind_human_readable
from src.jcloud_docsgen.core.python.arguments import PythonArgumentKind
import pytest
@pytest.mark.parametrize('argument_kind,expected', [
(PythonArgumentKind.NORMAL, 'normal'),
(PythonArgumentKind.POSITIONAL_ONLY, 'positional-only'),
(PythonArgumentKind.KEYWORD_ONLY, 'keyword-only'),
(PythonArgumentKind.VARARG, 'vararg'),
(PythonArgumentKind.KWARGS, 'kwargs'),
])
@pytest.mark.parametrize(
'argument_kind,expected',
[
(PythonArgumentKind.NORMAL, 'normal'),
(PythonArgumentKind.POSITIONAL_ONLY, 'positional-only'),
(PythonArgumentKind.KEYWORD_ONLY, 'keyword-only'),
(PythonArgumentKind.VARARG, 'vararg'),
(PythonArgumentKind.KWARGS, 'kwargs'),
],
)
def test__argument_kind_human_readable(argument_kind, expected):
assert _argument_kind_human_readable(argument_kind) == expected
@@ -25,8 +25,9 @@ from src.jcloud_docsgen.core.python.docstrings import PythonDocstring
import pytest
import ast
def module(source_code: str) -> ast.Module:
'''
"""
Returns an AST module of the source code.
:param source_code: The module source code.
@@ -34,38 +35,41 @@ def module(source_code: str) -> ast.Module:
:return: The AST module.
:rtype: ast.Module
'''
"""
return ast.parse(source_code)
@pytest.mark.parametrize('module,expected', [
(
module('def add(a, b):\n\treturn a + b'),
[
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
],
None,
None,
[],
),
],
),
(
module('''def add(a: int | float, b: int | float) -> int | float:
@pytest.mark.parametrize(
'module,expected',
[
(
module('def add(a, b):\n\treturn a + b'),
[
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
],
None,
None,
[],
),
],
),
(
module('''def add(a: int | float, b: int | float) -> int | float:
"""Adds two numbers."""
return a + b
@@ -89,87 +93,87 @@ class Something:
"""
return self._value
'''),
[
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int | float',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int | float',
),
],
'int | float',
PythonDocstring('Adds two numbers.'),
[],
),
PythonClassDefinition(
'Something',
[],
PythonDocstring('A class.'),
[],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'value',
PythonArgumentKind.NORMAL,
'42',
'typing.Any',
),
],
'None',
None,
[],
),
PythonFunctionDefinition(
'value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
],
'typing.Any',
PythonDocstring('The value.'),
['property'],
),
PythonFunctionDefinition(
'get_value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
],
'typing.Any',
PythonDocstring('Returns the value.'),
[],
),
],
),
],
),
(
module('''from src.jcloud_docsgen.core.python import (
[
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int | float',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int | float',
),
],
'int | float',
PythonDocstring('Adds two numbers.'),
[],
),
PythonClassDefinition(
'Something',
[],
PythonDocstring('A class.'),
[],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'value',
PythonArgumentKind.NORMAL,
'42',
'typing.Any',
),
],
'None',
None,
[],
),
PythonFunctionDefinition(
'value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
],
'typing.Any',
PythonDocstring('The value.'),
['property'],
),
PythonFunctionDefinition(
'get_value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
],
'typing.Any',
PythonDocstring('Returns the value.'),
[],
),
],
),
],
),
(
module("""from src.jcloud_docsgen.core.python import (
PythonDocumentationGenerator
)
from src.jcloud_docsgen.core.python.namespaces import (
@@ -235,139 +239,142 @@ def test_PythonDocumentationGenerator_collect_modules(
python_documentation_generator: PythonDocumentationGenerator,
expected
):
assert python_documentation_generator.namespace() == expected'''),
[
PythonFunctionDefinition(
'test_PythonDocumentationGenerator_type_exceptions',
[
PythonFunctionArgument(
'project_directory',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'docs_directory',
PythonArgumentKind.NORMAL,
None,
None,
)
],
None,
None,
[
(
'''pytest.mark.parametrize('project_directo'''
'''ry,docs_directory', [(1, 1), (1, None), '''
'''(42, None), (pathlib.Path('tests'), 1), '''
'''(pathlib.Path('tests/'), 1), (1, pathlib'''
'''.Path('tests/')), (1, pathlib.Path('test'''
'''s')), (None, pathlib.Path('tests'))])'''
),
],
),
PythonFunctionDefinition(
'test_PythonDocumentationGenerator_collect_modules',
[
PythonFunctionArgument(
'python_documentation_generator',
PythonArgumentKind.NORMAL,
None,
'PythonDocumentationGenerator',
),
PythonFunctionArgument(
'expected',
PythonArgumentKind.NORMAL,
None,
None,
)
],
None,
None,
[(
'''pytest.mark.parametrize('python_documentation_gener'''
'''ator,expected', [(PythonDocumentationGenerator(path'''
'''lib.Path('tests/unit/core/python/_core/test_project'''
'''_dirs/pdir_1'), pathlib.Path('tests/unit/core/pytho'''
'''n/_core/test_project_dirs/pdir_1/docs')), []), (Pyt'''
'''honDocumentationGenerator(pathlib.Path('tests/unit/'''
'''core/python/_core/test_project_dirs/pdir_2'), pathl'''
'''ib.Path('tests/unit/core/python/_core/test_project_'''
'''dirs/pdir_2/docs')), [PythonPackageNamespace('pkg','''
''' [PythonModuleNamespace('module')])]), (PythonDocum'''
'''entationGenerator(pathlib.Path('tests/unit/core/pyt'''
'''hon/_core/test_project_dirs/pdir_3'), pathlib.Path('''
''''tests/unit/core/python/_core/test_project_dirs/pdi'''
'''r_3/docs')), [PythonPackageNamespace('pkg', [Python'''
'''ModuleNamespace(\'module'), PythonPackageNamespace('''
'''\'pkg\', [PythonModuleNamespace(\'module2')])])])])'''
)],
),
],
),
(
module(
'''class C1:
assert python_documentation_generator.namespace() == expected"""),
[
PythonFunctionDefinition(
'test_PythonDocumentationGenerator_type_exceptions',
[
PythonFunctionArgument(
'project_directory',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'docs_directory',
PythonArgumentKind.NORMAL,
None,
None,
),
],
None,
None,
[
(
"""pytest.mark.parametrize('project_directo"""
"""ry,docs_directory', [(1, 1), (1, None), """
"""(42, None), (pathlib.Path('tests'), 1), """
"""(pathlib.Path('tests/'), 1), (1, pathlib"""
""".Path('tests/')), (1, pathlib.Path('test"""
"""s')), (None, pathlib.Path('tests'))])"""
),
],
),
PythonFunctionDefinition(
'test_PythonDocumentationGenerator_collect_modules',
[
PythonFunctionArgument(
'python_documentation_generator',
PythonArgumentKind.NORMAL,
None,
'PythonDocumentationGenerator',
),
PythonFunctionArgument(
'expected',
PythonArgumentKind.NORMAL,
None,
None,
),
],
None,
None,
[
(
"""pytest.mark.parametrize('python_documentation_gener"""
"""ator,expected', [(PythonDocumentationGenerator(path"""
"""lib.Path('tests/unit/core/python/_core/test_project"""
"""_dirs/pdir_1'), pathlib.Path('tests/unit/core/pytho"""
"""n/_core/test_project_dirs/pdir_1/docs')), []), (Pyt"""
"""honDocumentationGenerator(pathlib.Path('tests/unit/"""
"""core/python/_core/test_project_dirs/pdir_2'), pathl"""
"""ib.Path('tests/unit/core/python/_core/test_project_"""
"""dirs/pdir_2/docs')), [PythonPackageNamespace('pkg',"""
""" [PythonModuleNamespace('module')])]), (PythonDocum"""
"""entationGenerator(pathlib.Path('tests/unit/core/pyt"""
"""hon/_core/test_project_dirs/pdir_3'), pathlib.Path("""
"""'tests/unit/core/python/_core/test_project_dirs/pdi"""
"""r_3/docs')), [PythonPackageNamespace('pkg', [Python"""
"""ModuleNamespace(\'module'), PythonPackageNamespace("""
"""\'pkg\', [PythonModuleNamespace(\'module2')])])])])"""
)
],
),
],
),
(
module(
"""class C1:
class C2:
class C3:
def function(self, value: bool, /, arg) -> bool:
def should_be_not_in_the_result(arg: int) -> object:
pass
return not value'''
),
[
PythonClassDefinition(
'C1',
[],
None,
[],
[
PythonClassDefinition(
'C2',
[],
None,
[],
[
PythonClassDefinition(
'C3',
[],
None,
[],
[
PythonFunctionDefinition(
'function',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'value',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'bool',
),
PythonFunctionArgument(
'arg',
PythonArgumentKind.NORMAL,
None,
None,
)
],
'bool',
None,
[],
),
],
),
],
),
],
return not value"""
),
],
),
])
[
PythonClassDefinition(
'C1',
[],
None,
[],
[
PythonClassDefinition(
'C2',
[],
None,
[],
[
PythonClassDefinition(
'C3',
[],
None,
[],
[
PythonFunctionDefinition(
'function',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'value',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'bool',
),
PythonFunctionArgument(
'arg',
PythonArgumentKind.NORMAL,
None,
None,
),
],
'bool',
None,
[],
),
],
),
],
),
],
),
],
),
],
)
def test___collect_definitions(module, expected):
assert list(_collect_definitions(module)) == expected
@@ -15,41 +15,50 @@
from src.jcloud_docsgen.core.python._core import _fit_row_length
import pytest
@pytest.mark.parametrize('expected_length,row,expected', [
(0, (), ()),
(1, (), (None,)),
(2, (), (None, None)),
(2, (1,), (1, None)),
(0, (1,), ()),
(1, (1, 2), (1,)),
(1, (1, 2), (1,)),
(0, (1, 2), ()),
(0, (1, 2, 3), ()),
(1, (1, 2, 3), (1,)),
(2, (1, 2, 3), (1, 2)),
(1, (1,), (1,)),
(2, (1, 1), (1, 1)),
(2, (1, 2), (1, 2)),
(3, (1, 2, 3), (1, 2, 3)),
])
@pytest.mark.parametrize(
'expected_length,row,expected',
[
(0, (), ()),
(1, (), (None,)),
(2, (), (None, None)),
(2, (1,), (1, None)),
(0, (1,), ()),
(1, (1, 2), (1,)),
(1, (1, 2), (1,)),
(0, (1, 2), ()),
(0, (1, 2, 3), ()),
(1, (1, 2, 3), (1,)),
(2, (1, 2, 3), (1, 2)),
(1, (1,), (1,)),
(2, (1, 1), (1, 1)),
(2, (1, 2), (1, 2)),
(3, (1, 2, 3), (1, 2, 3)),
],
)
def test__fit_row_length(expected_length, row, expected):
assert _fit_row_length(expected_length, row) == expected
@pytest.mark.parametrize('row', [
(),
(1,),
(1, 2),
(1, 2, 3),
(1, 2, 3, 456),
])
@pytest.mark.parametrize('expected_length,expected_exception', [
(-1, ValueError),
(-2, ValueError),
(-3, ValueError),
(-42, ValueError),
])
@pytest.mark.parametrize(
'row',
[
(),
(1,),
(1, 2),
(1, 2, 3),
(1, 2, 3, 456),
],
)
@pytest.mark.parametrize(
'expected_length,expected_exception',
[
(-1, ValueError),
(-2, ValueError),
(-3, ValueError),
(-42, ValueError),
],
)
def test__fit_row_length_exceptions(expected_length, row, expected_exception):
with pytest.raises(expected_exception) as exc_info:
_fit_row_length(expected_length, row)
@@ -23,10 +23,11 @@ from src.jcloud_docsgen.core.python.arguments import (
import pytest
import docstring_parser
def docstring_args_list(
docstring: str,
) -> list[docstring_parser.DocstringParam]:
'''
"""
Returns the parameter list of a docstring.
:param docstring: The docstring
@@ -34,82 +35,164 @@ def docstring_args_list(
:return: The parameter list.
:rtype: list[docstring_parser.DocstringParam]
'''
"""
return docstring_parser.parse(docstring).params
@pytest.mark.parametrize('arg_list,docstring,expected', [
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'int'),
], ':param a: a\n:type a: str', {
'a': _FunctionParamInfo('int', 'a'),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
], ':param a: a\n:type a: str', {
'a': _FunctionParamInfo('str', 'a'),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, '', None),
], ':param a: a\n:type a: str', {
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo(None, None),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, '', 'int'),
], ':param a: a\n:type a: str', {
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo('int', None),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, '', 'int'),
], ':param a: a\n:type a: str\n:param b: b\n:type b: str', {
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo('int', 'b'),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
], ':param a: a\n:type a: str\n:param b: b\n:type b: int', {
'a': _FunctionParamInfo('str', 'a'),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
], '', {
'a': _FunctionParamInfo(None, None),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'str'),
], '', {
'a': _FunctionParamInfo('str', None),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'str'),
], ':param a:\n:type a: int', {
'a': _FunctionParamInfo('str', ''),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
], ':param a:\n:type a: int', {
'a': _FunctionParamInfo('int', ''),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
], ':param a: a', {
'a': _FunctionParamInfo(None, 'a'),
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'str'),
], ':param a: a', {
'a': _FunctionParamInfo('str', 'a'),
}),
([], ':param a: a\n:type a: str', {}),
([], '', {}),
])
@pytest.mark.parametrize(
'arg_list,docstring,expected',
[
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', 'int'
),
],
':param a: a\n:type a: str',
{
'a': _FunctionParamInfo('int', 'a'),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
],
':param a: a\n:type a: str',
{
'a': _FunctionParamInfo('str', 'a'),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
PythonFunctionArgument(
'b', PythonArgumentKind.NORMAL, '', None
),
],
':param a: a\n:type a: str',
{
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo(None, None),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
PythonFunctionArgument(
'b', PythonArgumentKind.NORMAL, '', 'int'
),
],
':param a: a\n:type a: str',
{
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo('int', None),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
PythonFunctionArgument(
'b', PythonArgumentKind.NORMAL, '', 'int'
),
],
':param a: a\n:type a: str\n:param b: b\n:type b: str',
{
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo('int', 'b'),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
],
':param a: a\n:type a: str\n:param b: b\n:type b: int',
{
'a': _FunctionParamInfo('str', 'a'),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
],
'',
{
'a': _FunctionParamInfo(None, None),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', 'str'
),
],
'',
{
'a': _FunctionParamInfo('str', None),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', 'str'
),
],
':param a:\n:type a: int',
{
'a': _FunctionParamInfo('str', ''),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
],
':param a:\n:type a: int',
{
'a': _FunctionParamInfo('int', ''),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', None
),
],
':param a: a',
{
'a': _FunctionParamInfo(None, 'a'),
},
),
(
[
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, '', 'str'
),
],
':param a: a',
{
'a': _FunctionParamInfo('str', 'a'),
},
),
([], ':param a: a\n:type a: str', {}),
([], '', {}),
],
)
def test__function_params_types(arg_list, docstring, expected):
assert _function_params_info(
arg_list,
docstring_args_list(docstring)
) == expected
assert (
_function_params_info(arg_list, docstring_args_list(docstring))
== expected
)
@@ -15,6 +15,7 @@
from src.jcloud_docsgen.core.python._core import _markdown_table
import pytest
@pytest.mark.parametrize(
(
'header,body,allow_markdown_table,force_row_le'
@@ -55,9 +56,9 @@ import pytest
False,
True,
'--',
'''| a |
"""| a |
| --- |
| 1 |''',
| 1 |""",
),
(
['a'],
@@ -66,9 +67,9 @@ import pytest
False,
True,
'--',
'''| a |
"""| a |
| --- |
| 1 |''',
| 1 |""",
),
(
['a', 'b'],
@@ -77,9 +78,9 @@ import pytest
False,
True,
'--',
'''| a | b |
"""| a | b |
| --- | --- |
| 1 | 2 |''',
| 1 | 2 |""",
),
(
['a', 'b'],
@@ -88,9 +89,9 @@ import pytest
False,
True,
'--',
'''| a | b |
"""| a | b |
| --- | --- |
| 1 | -- |''',
| 1 | -- |""",
),
(
['a', 'b'],
@@ -99,9 +100,9 @@ import pytest
False,
True,
'--',
'''| a | b |
"""| a | b |
| --- | --- |
| -- | -- |''',
| -- | -- |""",
),
(
['a', 'b'],
@@ -110,10 +111,10 @@ import pytest
False,
True,
'--',
'''| a | b |
"""| a | b |
| --- | --- |
| 1 | 2 |
| 3 | 4 |''',
| 3 | 4 |""",
),
(
['a'],
@@ -122,9 +123,9 @@ import pytest
False,
True,
'--',
'''| a |
"""| a |
| --- |
| line1 line2 |''',
| line1 line2 |""",
),
(
['a'],
@@ -133,11 +134,10 @@ import pytest
False,
False,
'--',
'''| a |
"""| a |
| --- |
| line1\nline2 |''',
| line1\nline2 |""",
),
(
[],
[],
@@ -190,9 +190,9 @@ import pytest
False,
True,
'--',
'''- a: 1
"""- a: 1
b: 2''',
b: 2""",
),
(
['a', 'b'],
@@ -201,9 +201,9 @@ import pytest
False,
True,
'--',
'''- a: 1
"""- a: 1
b: --''',
b: --""",
),
(
['a', 'b'],
@@ -212,9 +212,9 @@ import pytest
False,
True,
'--',
'''- a: --
"""- a: --
b: --''',
b: --""",
),
(
['a', 'b'],
@@ -223,13 +223,13 @@ import pytest
False,
True,
'--',
'''- a: 1
"""- a: 1
b: 2
- a: 3
b: 4''',
b: 4""",
),
(
['a'],
@@ -260,14 +260,18 @@ def test__markdown_table(
missing_value,
expected,
):
assert _markdown_table(
header,
body,
allow_markdown_table = allow_markdown_table,
force_row_lengths = force_row_lengths,
one_line_values = one_line_values,
missing_value = missing_value,
) == expected
assert (
_markdown_table(
header,
body,
allow_markdown_table=allow_markdown_table,
force_row_lengths=force_row_lengths,
one_line_values=one_line_values,
missing_value=missing_value,
)
== expected
)
@pytest.mark.parametrize(
(
@@ -335,7 +339,6 @@ def test__markdown_table(
ValueError,
'expected 2 values, got 3',
),
(
['a'],
[(1, 2)],
@@ -412,9 +415,9 @@ def test__markdown_table_exceptions(
_markdown_table(
header,
body,
allow_markdown_table = allow_markdown_table,
force_row_lengths = force_row_lengths,
one_line_values = one_line_values,
missing_value = missing_value,
allow_markdown_table=allow_markdown_table,
force_row_lengths=force_row_lengths,
one_line_values=one_line_values,
missing_value=missing_value,
)
assert str(exc_info.value) == expected_exception_msg
@@ -19,266 +19,328 @@ from src.jcloud_docsgen.core.python.arguments import (
)
import pytest
@pytest.mark.parametrize('argument_list,expected', [
([
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
@pytest.mark.parametrize(
'argument_list,expected',
[
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
],
['a'],
),
], ['a']),
([
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
],
['a', '/'],
),
], ['a', '/']),
([
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
],
['a', '/', 'b'],
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
['a', '/', 'b', '*', 'c'],
),
], ['a', '/', 'b']),
([
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
(
[
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
['b', '*', 'c'],
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
(
[
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
['*', 'c'],
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
(
[
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
['b', '/', '*', 'c'],
),
], ['a', '/', 'b', '*', 'c']),
([
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None,
(
[
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
)
],
['*args'],
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
],
['a', '*args'],
),
], ['b', '*', 'c']),
([
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
],
['a', '/', '*args'],
),
], ['*', 'c']),
([
PythonFunctionArgument(
'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
(
[
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
],
['*args', 'a'],
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
(
[
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['*args', '**kwargs'],
),
], ['b', '/', '*', 'c']),
([
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
)
], ['*args']),
([
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['a', '/', '*args', '**kwargs'],
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['a', '*args', '**kwargs'],
),
], ['a', '*args']),
([
PythonFunctionArgument(
'a', PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
(
[
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['*args', 'a', '**kwargs'],
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['*', 'a', '**kwargs'],
),
], ['a', '/', '*args']),
([
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['a', '**kwargs'],
),
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
],
['a', '/', '**kwargs'],
),
], ['*args', 'a']),
([
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
(
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
'int',
),
],
['a: int = 1'],
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
], ['*args', '**kwargs']),
([
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
], ['a', '/', '*args', '**kwargs']),
([
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
], ['a', '*args', '**kwargs']),
([
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None,
),
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
], ['*args', 'a', '**kwargs']),
([
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
], ['*', 'a', '**kwargs']),
([
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
], ['a', '**kwargs']),
([
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None,
),
], ['a', '/', '**kwargs']),
([
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
'int',
),
], ['a: int = 1']),
])
],
)
def test__signature_argument_list(argument_list, expected):
assert _signature_argument_list(argument_list) == expected
@@ -1,9 +1,10 @@
'''
"""
A test module.
'''
"""
def add(a: int | float, b: int | float) -> int | float:
'''
"""
Adds two numbers.
:param a: The first number.
@@ -13,38 +14,41 @@ def add(a: int | float, b: int | float) -> int | float:
:return: The sum.
:rtype: int | float
'''
"""
return a + b
class Animal:
'''
"""
A class representing an animal.
:param name: Its name.
:type name: str
'''
"""
def __init__(self, name: str) -> None:
self.name = name
def greet(self):
'''
"""
Returns a greeting.
:return: The greeting.
:rtype: str
'''
"""
return f'Hi there! My name is {self.name}'
class Dog(Animal):
'''
"""
A dog.
'''
"""
def bark(self) -> None:
'''
"""
Barks.
'''
"""
print('Woof!')
File diff suppressed because it is too large. Load diff
@@ -23,6 +23,7 @@ from src.jcloud_docsgen.exceptions import (
)
import pytest
@pytest.mark.parametrize(
'name,kind,default,annotation,expected_exception,expected_exception_msg',
[
@@ -32,7 +33,7 @@ import pytest
None,
None,
TypeError,
'expected \'str\', got \'NoneType\'',
"expected 'str', got 'NoneType'",
),
(
1,
@@ -40,7 +41,7 @@ import pytest
None,
None,
TypeError,
'expected \'str\', got \'int\'',
"expected 'str', got 'int'",
),
(
42,
@@ -48,7 +49,7 @@ import pytest
None,
None,
TypeError,
'expected \'str\', got \'int\'',
"expected 'str', got 'int'",
),
(
4.2,
@@ -56,7 +57,7 @@ import pytest
None,
None,
TypeError,
'expected \'str\', got \'float\'',
"expected 'str', got 'float'",
),
(
4.2,
@@ -64,7 +65,7 @@ import pytest
None,
None,
TypeError,
'expected \'str\', got \'float\'',
"expected 'str', got 'float'",
),
(
'a',
@@ -72,7 +73,7 @@ import pytest
1,
None,
TypeError,
'expected either \'str\' or \'NoneType\', got \'int\'',
"expected either 'str' or 'NoneType', got 'int'",
),
(
'a',
@@ -80,7 +81,7 @@ import pytest
None,
1,
TypeError,
'expected either \'str\' or \'NoneType\', got \'int\'',
"expected either 'str' or 'NoneType', got 'int'",
),
(
'a',
@@ -88,7 +89,7 @@ import pytest
None,
None,
TypeError,
'expected \'PythonArgumentKind\', got \'int\'',
"expected 'PythonArgumentKind', got 'int'",
),
(
'a',
@@ -384,220 +385,244 @@ def test_PythonFunctionArgument_exceptions(
PythonFunctionArgument(name, kind, default, annotation)
assert str(exc_info.value) == expected_exception_msg
@pytest.mark.parametrize('name', [
'a',
'_a',
'a1',
'a0',
'a_',
'_',
'_abc',
'abc',
'abc_',
'match',
'case',
'type',
'ä',
'none',
'true',
'false',
])
@pytest.mark.parametrize('kind', [
PythonArgumentKind.NORMAL,
PythonArgumentKind.POSITIONAL_ONLY,
PythonArgumentKind.KEYWORD_ONLY,
])
@pytest.mark.parametrize('default', [
None,
'x',
'1',
None,
])
@pytest.mark.parametrize('annotation', [
None,
'a',
'list[int]',
'_',
])
@pytest.mark.parametrize(
'name',
[
'a',
'_a',
'a1',
'a0',
'a_',
'_',
'_abc',
'abc',
'abc_',
'match',
'case',
'type',
'ä',
'none',
'true',
'false',
],
)
@pytest.mark.parametrize(
'kind',
[
PythonArgumentKind.NORMAL,
PythonArgumentKind.POSITIONAL_ONLY,
PythonArgumentKind.KEYWORD_ONLY,
],
)
@pytest.mark.parametrize(
'default',
[
None,
'x',
'1',
None,
],
)
@pytest.mark.parametrize(
'annotation',
[
None,
'a',
'list[int]',
'_',
],
)
def test_PythonFunctionArgument(name, kind, default, annotation):
PythonFunctionArgument(name, kind, default, annotation)
@pytest.mark.parametrize('name', [
'a',
'_a',
'a1',
'a0',
'a_',
'_',
'_abc',
'abc',
'abc_',
'match',
'case',
'type',
'ä',
'none',
'true',
'false',
])
@pytest.mark.parametrize('kind', [
PythonArgumentKind.VARARG,
PythonArgumentKind.KWARGS,
])
@pytest.mark.parametrize('annotation', [
None,
'a',
'list[int]',
'_',
])
@pytest.mark.parametrize(
'name',
[
'a',
'_a',
'a1',
'a0',
'a_',
'_',
'_abc',
'abc',
'abc_',
'match',
'case',
'type',
'ä',
'none',
'true',
'false',
],
)
@pytest.mark.parametrize(
'kind',
[
PythonArgumentKind.VARARG,
PythonArgumentKind.KWARGS,
],
)
@pytest.mark.parametrize(
'annotation',
[
None,
'a',
'list[int]',
'_',
],
)
def test_PythonFunctionArgument_asterik(name, kind, annotation):
PythonFunctionArgument(name, kind, None, annotation)
@pytest.mark.parametrize('argument,expected', [
(
PythonFunctionArgument(
@pytest.mark.parametrize(
'argument,expected',
[
(
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
'a',
PythonArgumentKind.NORMAL,
None,
None,
),
'a',
),
(
PythonFunctionArgument(
(
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
None,
),
'a = 1',
),
(
PythonFunctionArgument(
'a', PythonArgumentKind.NORMAL, None, 'int'
),
'a: int',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
'int',
),
'a: int = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
),
'a',
PythonArgumentKind.NORMAL,
'1',
None,
),
'a = 1',
),
(
PythonFunctionArgument(
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
'1',
None,
),
'a = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int',
),
'a: int',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
'1',
'int',
),
'a: int = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
),
'a',
PythonArgumentKind.NORMAL,
None,
'int'
),
'a: int',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
'int',
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
'1',
None,
),
'a = 1',
),
'a: int = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None,
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
'int',
),
'a: int',
),
'a',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
'1',
None,
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
'1',
'int',
),
'a: int = 1',
),
'a = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int',
(
PythonFunctionArgument(
'a',
PythonArgumentKind.VARARG,
None,
None,
),
'*a',
),
'a: int',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
'1',
'int',
(
PythonFunctionArgument(
'a',
PythonArgumentKind.VARARG,
None,
'int',
),
'*a: int',
),
'a: int = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None,
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KWARGS,
None,
None,
),
'**a',
),
'a',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
'1',
None,
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KWARGS,
None,
'int',
),
'**a: int',
),
'a = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
'int',
),
'a: int',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
'1',
'int',
),
'a: int = 1',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.VARARG,
None,
None,
),
'*a',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.VARARG,
None,
'int',
),
'*a: int',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KWARGS,
None,
None,
),
'**a',
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KWARGS,
None,
'int',
),
'**a: int',
),
])
],
)
def test_PythonFunctionArgument_signature_repr(argument, expected):
assert argument.signature_repr() == expected
@@ -25,31 +25,35 @@ from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError
from tests.utils.ast_node import ast_node
import pytest
@pytest.mark.parametrize('definition,expected', [
(
PythonClassDefinition(
'A',
[],
None,
None,
[],
),
'PythonClassDefinition(\'A\', [], None, None, [])',
),
(
PythonClassDefinition(
'A',
[],
None,
PythonDocstring('Hello, World!'),
[],
@pytest.mark.parametrize(
'definition,expected',
[
(
PythonClassDefinition(
'A',
[],
None,
None,
[],
),
"PythonClassDefinition('A', [], None, None, [])",
),
(
'PythonClassDefinition(\'A\', [], None, '
'PythonDocstring(\'Hello, World!\'), [])'
PythonClassDefinition(
'A',
[],
None,
PythonDocstring('Hello, World!'),
[],
),
(
"PythonClassDefinition('A', [], None, "
"PythonDocstring('Hello, World!'), [])"
),
),
),
])
],
)
def test_PythonDefinition_string_representation(definition, expected):
assert repr(definition) == expected
@@ -364,129 +368,132 @@ def test_PythonFunctionDefinition_exceptions(
assert str(exc_info.value) == expected_exception_message
@pytest.mark.parametrize('node,expected', [
(
ast_node(
(
'@decorator\nclass Class(BaseClass1, BaseClass2)'
':\n\tdef __init__(self, value: object) -> None:'
'\n\t\tself._value = value\n\n\t@property\n\tdef'
' value(self) -> object:\n\t\treturn self._value'
@pytest.mark.parametrize(
'node,expected',
[
(
ast_node(
(
'@decorator\nclass Class(BaseClass1, BaseClass2)'
':\n\tdef __init__(self, value: object) -> None:'
'\n\t\tself._value = value\n\n\t@property\n\tdef'
' value(self) -> object:\n\t\treturn self._value'
),
),
PythonClassDefinition(
'Class',
['BaseClass1', 'BaseClass2'],
None,
['decorator'],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'value',
PythonArgumentKind.NORMAL,
None,
'object',
),
],
'None',
None,
[],
),
PythonFunctionDefinition(
'value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
],
'object',
None,
['property'],
),
],
),
),
PythonClassDefinition(
'Class',
['BaseClass1', 'BaseClass2'],
None,
['decorator'],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
PythonFunctionArgument(
'value',
PythonArgumentKind.NORMAL,
None,
'object',
),
],
'None',
None,
[],
),
PythonFunctionDefinition(
'value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
],
'object',
None,
['property'],
),
],
(
ast_node('class Class: ...'),
PythonClassDefinition(
'Class',
[],
None,
[],
[],
),
),
),
(
ast_node('class Class: ...'),
PythonClassDefinition(
'Class',
[],
None,
[],
[],
(
ast_node("class Class: '''docstring'''"),
PythonClassDefinition(
'Class',
[],
PythonDocstring('docstring'),
[],
[],
),
),
),
(
ast_node('class Class: \'\'\'docstring\'\'\''),
PythonClassDefinition(
'Class',
[],
PythonDocstring('docstring'),
[],
[],
(
ast_node('class Class:\n\tdef __init__(self): ...'),
PythonClassDefinition(
'Class',
[],
None,
[],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
)
],
None,
None,
[],
),
],
),
),
),
(
ast_node('class Class:\n\tdef __init__(self): ...'),
PythonClassDefinition(
'Class',
[],
None,
[],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
)
],
None,
None,
[],
),
],
(
ast_node("class Class:\n\t'''doc'''\n\tdef __init__(self): ..."),
PythonClassDefinition(
'Class',
[],
PythonDocstring('doc'),
[],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
],
None,
None,
[],
),
],
),
),
),
(
ast_node('class Class:\n\t\'\'\'doc\'\'\'\n\tdef __init__(self): ...'),
PythonClassDefinition(
'Class',
[],
PythonDocstring('doc'),
[],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None,
),
],
None,
None,
[],
),
],
),
),
])
],
)
def test_PythonDefinition_from_node(node, expected):
assert PythonClassDefinition.from_node(node) == expected
@@ -17,24 +17,28 @@ from src.jcloud_docsgen.core.python.docstrings import PythonDocstring
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError
import pytest
@pytest.mark.parametrize('definition,expected', [
(
PythonDefinition(
'a',
None,
[],
@pytest.mark.parametrize(
'definition,expected',
[
(
PythonDefinition(
'a',
None,
[],
),
"PythonDefinition('a', None, [])",
),
'PythonDefinition(\'a\', None, [])',
),
(
PythonDefinition(
'a',
PythonDocstring('Hello, World!'),
[],
(
PythonDefinition(
'a',
PythonDocstring('Hello, World!'),
[],
),
"PythonDefinition('a', PythonDocstring('Hello, World!'), [])",
),
'PythonDefinition(\'a\', PythonDocstring(\'Hello, World!\'), [])',
),
])
],
)
def test_PythonDefinition_string_representation(definition, expected):
assert repr(definition) == expected
@@ -280,164 +284,167 @@ def test_PythonDefinition_exceptions(
assert str(exc_info.value) == expected_exception_message
@pytest.mark.parametrize('definition1,definition2,expected', [
(
PythonDefinition(
'a',
None,
[],
@pytest.mark.parametrize(
'definition1,definition2,expected',
[
(
PythonDefinition(
'a',
None,
[],
),
PythonDefinition(
'a',
None,
[],
),
True,
),
PythonDefinition(
'a',
None,
[],
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[],
),
PythonDefinition(
'a',
PythonDocstring('doc'),
[],
),
True,
),
True,
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[],
(
PythonDefinition(
'a',
PythonDocstring('doc'),
['a'],
),
PythonDefinition(
'a',
PythonDocstring('doc'),
['a'],
),
True,
),
PythonDefinition(
'a',
PythonDocstring('doc'),
[],
(
PythonDefinition(
'a',
PythonDocstring('doc'),
['a', 'b'],
),
PythonDefinition(
'a',
PythonDocstring('doc'),
['a', 'b'],
),
True,
),
True,
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
['a'],
(
PythonDefinition(
'a',
None,
[],
),
PythonDefinition(
'b',
None,
[],
),
False,
),
PythonDefinition(
'a',
PythonDocstring('doc'),
['a'],
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[],
),
PythonDefinition(
'a',
None,
[],
),
False,
),
True,
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
['a', 'b'],
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[],
),
PythonDefinition(
'a',
PythonDocstring('docs'),
[],
),
False,
),
PythonDefinition(
'a',
PythonDocstring('doc'),
['a', 'b'],
(
PythonDefinition(
'a',
None,
['a'],
),
PythonDefinition(
'a',
None,
['b'],
),
False,
),
True,
),
(
PythonDefinition(
'a',
None,
[],
(
PythonDefinition(
'a',
None,
['a'],
),
PythonDefinition(
'a',
None,
[],
),
False,
),
PythonDefinition(
'b',
None,
[],
(
PythonDefinition(
'a',
None,
['a'],
),
PythonDefinition(
'a',
None,
['a', 'b'],
),
False,
),
False,
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[],
(
PythonDefinition(
'a',
None,
['b', 'a'],
),
PythonDefinition(
'a',
None,
['a', 'b'],
),
False,
),
PythonDefinition(
'a',
None,
[],
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[],
),
PythonDefinition(
'b',
None,
['a', 'b'],
),
False,
),
False,
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[],
),
PythonDefinition(
'a',
PythonDocstring('docs'),
[],
),
False,
),
(
PythonDefinition(
'a',
None,
['a'],
),
PythonDefinition(
'a',
None,
['b'],
),
False,
),
(
PythonDefinition(
'a',
None,
['a'],
),
PythonDefinition(
'a',
None,
[],
),
False,
),
(
PythonDefinition(
'a',
None,
['a'],
),
PythonDefinition(
'a',
None,
['a', 'b'],
),
False,
),
(
PythonDefinition(
'a',
None,
['b', 'a'],
),
PythonDefinition(
'a',
None,
['a', 'b'],
),
False,
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[],
),
PythonDefinition(
'b',
None,
['a', 'b'],
),
False,
),
])
],
)
def test_PythonDefinition___eq__(definition1, definition2, expected):
assert (definition1 == definition2) == expected
assert (definition2 == definition1) == expected
@@ -22,31 +22,35 @@ from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError
from tests.utils.ast_node import ast_node
import pytest
@pytest.mark.parametrize('definition,expected', [
(
PythonFunctionDefinition(
'a',
[],
None,
None,
[],
),
'PythonFunctionDefinition(\'a\', [], None, None, [])',
),
(
PythonFunctionDefinition(
'a',
[],
None,
PythonDocstring('Hello, World!'),
[],
@pytest.mark.parametrize(
'definition,expected',
[
(
PythonFunctionDefinition(
'a',
[],
None,
None,
[],
),
"PythonFunctionDefinition('a', [], None, None, [])",
),
(
'PythonFunctionDefinition(\'a\', [], None,'
' PythonDocstring(\'Hello, World!\'), [])'
PythonFunctionDefinition(
'a',
[],
None,
PythonDocstring('Hello, World!'),
[],
),
(
"PythonFunctionDefinition('a', [], None,"
" PythonDocstring('Hello, World!'), [])"
),
),
),
])
],
)
def test_PythonDefinition_string_representation(definition, expected):
assert repr(definition) == expected
@@ -57,295 +61,296 @@ def test_PythonDefinition_string_representation(definition, expected):
'ed_exception,expected_exception_message'
),
[
(
'',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier',
),
(
'.',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: .',
),
(
'#',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: #',
),
(
'1a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1a',
),
(
'1_',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1_',
),
(
'1,',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1,',
),
(
',',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ,',
),
(
':',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: :',
),
(
'-',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: -',
),
(
';',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ;',
),
(
'|',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: |',
),
(
'<',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: <',
),
(
'>',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: >',
),
(
'a,',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a,',
),
(
',a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ,a',
),
(
'a:',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a:',
),
(
':a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: :a',
),
(
'a-',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a-',
),
(
'-a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: -a',
),
(
'a;',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a;',
),
(
';a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ;a',
),
(
'a|',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a|',
),
(
'|a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: |a',
),
(
'a<',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a<',
),
(
'<a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: <a',
),
(
'a>',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a>',
),
(
'>a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: >a',
),
(
'class',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: class',
),
(
'def',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: def',
),
(
'lambda',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: lambda',
),
(
'None',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: None',
),
(
'True',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: True',
),
])
(
'',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier',
),
(
'.',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: .',
),
(
'#',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: #',
),
(
'1a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1a',
),
(
'1_',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1_',
),
(
'1,',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1,',
),
(
',',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ,',
),
(
':',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: :',
),
(
'-',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: -',
),
(
';',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ;',
),
(
'|',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: |',
),
(
'<',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: <',
),
(
'>',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: >',
),
(
'a,',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a,',
),
(
',a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ,a',
),
(
'a:',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a:',
),
(
':a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: :a',
),
(
'a-',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a-',
),
(
'-a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: -a',
),
(
'a;',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a;',
),
(
';a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ;a',
),
(
'a|',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a|',
),
(
'|a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: |a',
),
(
'a<',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a<',
),
(
'<a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: <a',
),
(
'a>',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a>',
),
(
'>a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: >a',
),
(
'class',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: class',
),
(
'def',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: def',
),
(
'lambda',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: lambda',
),
(
'None',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: None',
),
(
'True',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: True',
),
],
)
def test_PythonFunctionDefinition_exceptions(
name,
args,
@@ -360,100 +365,106 @@ def test_PythonFunctionDefinition_exceptions(
assert str(exc_info.value) == expected_exception_message
@pytest.mark.parametrize('node,expected', [
(
ast_node('def add(a: int, b: int) -> int: return a + b'),
PythonFunctionDefinition(
'add', [
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int',
),
],
'int',
None,
[],
),
),
(
ast_node('@decorator\ndef add(a: int, b: int) -> int: return a + b'),
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int',
),
],
'int',
None,
['decorator'],
),
),
(
ast_node(
(
'@decorator1\n@decorator2\ndef add(a'
': int, b: int) -> int: return a + b'
@pytest.mark.parametrize(
'node,expected',
[
(
ast_node('def add(a: int, b: int) -> int: return a + b'),
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int',
),
],
'int',
None,
[],
),
),
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
(
ast_node(
'@decorator\ndef add(a: int, b: int) -> int: return a + b'
),
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int',
),
],
'int',
None,
['decorator'],
),
),
(
ast_node(
(
'@decorator1\n@decorator2\ndef add(a'
': int, b: int) -> int: return a + b'
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int',
),
],
'int',
None,
['decorator1', 'decorator2'],
),
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int',
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int',
),
],
'int',
None,
['decorator1', 'decorator2'],
),
),
),
(
ast_node('def func(): ...'),
PythonFunctionDefinition(
'func',
[],
None,
None,
[],
(
ast_node('def func(): ...'),
PythonFunctionDefinition(
'func',
[],
None,
None,
[],
),
),
),
(
ast_node('@decorator\ndef func(): ...'),
PythonFunctionDefinition(
'func',
[],
None,
None,
['decorator'],
(
ast_node('@decorator\ndef func(): ...'),
PythonFunctionDefinition(
'func',
[],
None,
None,
['decorator'],
),
),
),
])
],
)
def test_PythonDefinition_from_node(node, expected):
assert PythonFunctionDefinition.from_node(node) == expected
@@ -15,33 +15,49 @@
from src.jcloud_docsgen.core.python.namespaces import PythonModuleNamespace
import pytest
class StrSubclass(str): ...
@pytest.mark.parametrize('namespace,expected_name', [
(PythonModuleNamespace('a'), 'a'),
(PythonModuleNamespace('1'), '1'),
(PythonModuleNamespace(StrSubclass('a')), StrSubclass('a')),
])
@pytest.mark.parametrize(
'namespace,expected_name',
[
(PythonModuleNamespace('a'), 'a'),
(PythonModuleNamespace('1'), '1'),
(PythonModuleNamespace(StrSubclass('a')), StrSubclass('a')),
],
)
def test_PythonModuleNamespace_name_attribute(namespace, expected_name):
assert namespace.name == expected_name
@pytest.mark.parametrize('namespace1,namespace2,expected', [
(PythonModuleNamespace('a'), PythonModuleNamespace('a'), True),
(PythonModuleNamespace('b'), PythonModuleNamespace('b'), True),
(PythonModuleNamespace('1'), PythonModuleNamespace('1'), True),
(PythonModuleNamespace('42'), PythonModuleNamespace('42'), True),
(PythonModuleNamespace('a'), PythonModuleNamespace('b'), False),
(PythonModuleNamespace('0'), PythonModuleNamespace('-0'), False),
(PythonModuleNamespace('1'), PythonModuleNamespace('a'), False),
])
@pytest.mark.parametrize(
'namespace1,namespace2,expected',
[
(PythonModuleNamespace('a'), PythonModuleNamespace('a'), True),
(PythonModuleNamespace('b'), PythonModuleNamespace('b'), True),
(PythonModuleNamespace('1'), PythonModuleNamespace('1'), True),
(PythonModuleNamespace('42'), PythonModuleNamespace('42'), True),
(PythonModuleNamespace('a'), PythonModuleNamespace('b'), False),
(PythonModuleNamespace('0'), PythonModuleNamespace('-0'), False),
(PythonModuleNamespace('1'), PythonModuleNamespace('a'), False),
],
)
def test_PythonModuleNamespace___eq__(namespace1, namespace2, expected):
assert (namespace1 == namespace2) == expected
assert (namespace2 == namespace1) == expected
@pytest.mark.parametrize('namespace,expected', [
(PythonModuleNamespace('a'), 'PythonModuleNamespace(\'a\')'),
(PythonModuleNamespace('1'), 'PythonModuleNamespace(\'1\')'),
(PythonModuleNamespace(StrSubclass('a')), 'PythonModuleNamespace(\'a\')'),
])
@pytest.mark.parametrize(
'namespace,expected',
[
(PythonModuleNamespace('a'), "PythonModuleNamespace('a')"),
(PythonModuleNamespace('1'), "PythonModuleNamespace('1')"),
(
PythonModuleNamespace(StrSubclass('a')),
"PythonModuleNamespace('a')",
),
],
)
def test_PythonModuleNamespace_string_representation(namespace, expected):
assert repr(namespace) == expected
@@ -15,20 +15,29 @@
from src.jcloud_docsgen.core.python.namespaces import PythonNamespace
import pytest
class StrSubclass(str): ...
@pytest.mark.parametrize('namespace,expected_name', [
(PythonNamespace('a'), 'a'),
(PythonNamespace('1'), '1'),
(PythonNamespace(StrSubclass('a')), StrSubclass('a')),
])
@pytest.mark.parametrize(
'namespace,expected_name',
[
(PythonNamespace('a'), 'a'),
(PythonNamespace('1'), '1'),
(PythonNamespace(StrSubclass('a')), StrSubclass('a')),
],
)
def test_PythonNamespace_name_attribute(namespace, expected_name):
assert namespace.name == expected_name
@pytest.mark.parametrize('namespace,expected', [
(PythonNamespace('a'), 'PythonNamespace(\'a\')'),
(PythonNamespace('1'), 'PythonNamespace(\'1\')'),
(PythonNamespace(StrSubclass('a')), 'PythonNamespace(\'a\')'),
])
@pytest.mark.parametrize(
'namespace,expected',
[
(PythonNamespace('a'), "PythonNamespace('a')"),
(PythonNamespace('1'), "PythonNamespace('1')"),
(PythonNamespace(StrSubclass('a')), "PythonNamespace('a')"),
],
)
def test_PythonNamespace_string_representation(namespace, expected):
assert repr(namespace) == expected
@@ -22,38 +22,47 @@ from src.jcloud_docsgen.exceptions import (
InvalidNamespaceError,
)
class StrSubclass(str): ...
@pytest.mark.parametrize('namespace,expected_name', [
(
PythonPackageNamespace(
@pytest.mark.parametrize(
'namespace,expected_name',
[
(
PythonPackageNamespace(
'a',
[],
),
'a',
[],
),
'a',
),
(
PythonPackageNamespace(
(
PythonPackageNamespace(
'1',
[],
),
'1',
[],
),
'1',
),
(
PythonPackageNamespace(
(
PythonPackageNamespace(
StrSubclass('a'),
[],
),
StrSubclass('a'),
[],
),
StrSubclass('a'),
),
])
],
)
def test_PythonPackageNamespace_name_attribute(namespace, expected_name):
assert namespace.name == expected_name
namespace_b = PythonModuleNamespace('b')
namespace_a = PythonPackageNamespace('a', [
namespace_b,
])
namespace_a = PythonPackageNamespace(
'a',
[
namespace_b,
],
)
namespace_c = PythonModuleNamespace('c')
namespace_k = PythonModuleNamespace('k')
namespace_j = PythonPackageNamespace('j', [namespace_k])
@@ -64,47 +73,51 @@ namespace_f = PythonPackageNamespace('f', [namespace_g])
namespace_e = PythonPackageNamespace('e', [namespace_f])
namespace_d = PythonPackageNamespace('d', [namespace_e])
@pytest.mark.parametrize('namespace,sub_namespaces,expected', [
(
PythonPackageNamespace(
'name',
[namespace_a, namespace_c],
@pytest.mark.parametrize(
'namespace,sub_namespaces,expected',
[
(
PythonPackageNamespace(
'name',
[namespace_a, namespace_c],
),
['c'],
namespace_c,
),
['c'],
namespace_c,
),
(
PythonPackageNamespace(
'name',
[namespace_a, namespace_c],
(
PythonPackageNamespace(
'name',
[namespace_a, namespace_c],
),
['a'],
namespace_a,
),
['a'],
namespace_a,
),
(
PythonPackageNamespace(
'name',
[namespace_a, namespace_c],
(
PythonPackageNamespace(
'name',
[namespace_a, namespace_c],
),
['a', 'b'],
namespace_b,
),
['a', 'b'],
namespace_b,
),
(
namespace_d,
['e', 'f', 'g', 'h', 'i'],
namespace_i,
),
(
namespace_d,
['e', 'f', 'g', 'h', 'i', 'j'],
namespace_j,
),
(
namespace_d,
['e', 'f', 'g', 'h', 'i', 'j', 'k'],
namespace_k,
),
])
(
namespace_d,
['e', 'f', 'g', 'h', 'i'],
namespace_i,
),
(
namespace_d,
['e', 'f', 'g', 'h', 'i', 'j'],
namespace_j,
),
(
namespace_d,
['e', 'f', 'g', 'h', 'i', 'j', 'k'],
namespace_k,
),
],
)
def test_PythonPackageNamespace_namespace(
namespace: PythonPackageNamespace,
sub_namespaces,
@@ -112,6 +125,7 @@ def test_PythonPackageNamespace_namespace(
):
assert namespace.namespace(sub_namespaces) == expected
@pytest.mark.parametrize(
'namespace,sub_namespaces,expected_exception,expected_exception_msg',
[
@@ -157,129 +171,145 @@ def test_PythonPackageNamespace_namespace_exceptions(
namespace.namespace(sub_namespaces)
assert str(exc_info.value) == expected_exception_msg
@pytest.mark.parametrize('namespace,expected', [
(
PythonPackageNamespace(
'a',
[PythonModuleNamespace('b')],
@pytest.mark.parametrize(
'namespace,expected',
[
(
PythonPackageNamespace(
'a',
[PythonModuleNamespace('b')],
),
{'b': 'b'},
),
{'b': 'b'},
),
(
PythonPackageNamespace(
'a',
[
PythonModuleNamespace('b'),
PythonPackageNamespace(
'c',
[PythonModuleNamespace('d')],
),
],
(
PythonPackageNamespace(
'a',
[
PythonModuleNamespace('b'),
PythonPackageNamespace(
'c',
[PythonModuleNamespace('d')],
),
],
),
{
'b': 'b',
'c': {'d': 'd'},
},
),
{
'b': 'b',
'c': {'d': 'd'},
},
),
(
PythonPackageNamespace('a', []),
{},
),
])
(
PythonPackageNamespace('a', []),
{},
),
],
)
def test_PythonPackageNamespace_sub_namespace_names(namespace, expected):
assert namespace.sub_namespace_names() == expected
@pytest.mark.parametrize('namespace1,namespace2,expected', [
(
PythonPackageNamespace(
'a',
[PythonModuleNamespace('b')],
@pytest.mark.parametrize(
'namespace1,namespace2,expected',
[
(
PythonPackageNamespace(
'a',
[PythonModuleNamespace('b')],
),
PythonPackageNamespace(
'a',
[PythonModuleNamespace('b')],
),
True,
),
PythonPackageNamespace(
'a',
[PythonModuleNamespace('b')],
(
PythonPackageNamespace(
'a',
[
PythonModuleNamespace('b'),
PythonPackageNamespace(
'c',
[PythonModuleNamespace('d')],
),
],
),
PythonPackageNamespace(
'a',
[
PythonModuleNamespace('b'),
PythonPackageNamespace(
'c',
[PythonModuleNamespace('d')],
),
],
),
True,
),
True,
),
(
PythonPackageNamespace(
'a',
[
PythonModuleNamespace('b'),
PythonPackageNamespace(
'c',
[PythonModuleNamespace('d')],
),
],
(
PythonPackageNamespace('a', []),
PythonPackageNamespace('a', []),
True,
),
PythonPackageNamespace(
'a',
[
PythonModuleNamespace('b'),
PythonPackageNamespace(
'c',
[PythonModuleNamespace('d')],
),
],
(
PythonPackageNamespace('a', []),
PythonPackageNamespace('b', []),
False,
),
True,
),
(
PythonPackageNamespace('a', []),
PythonPackageNamespace('a', []),
True,
),
(
PythonPackageNamespace('a', []),
PythonPackageNamespace('b', []),
False,
),
])
],
)
def test_PythonPackageNamespace___eq__(namespace1, namespace2, expected):
assert (namespace1 == namespace2) == expected
assert (namespace2 == namespace1) == expected
@pytest.mark.parametrize('namespace,expected', [
(
PythonPackageNamespace('a', []),
'PythonPackageNamespace(\'a\', [])',
),
(
PythonPackageNamespace('1', []),
'PythonPackageNamespace(\'1\', [])',
),
(
PythonPackageNamespace(StrSubclass('a'), []),
'PythonPackageNamespace(\'a\', [])',
),
(
namespace_a,
'PythonPackageNamespace(\'a\', [PythonModuleNamespace(\'b\')])',
),
(
namespace_d,
@pytest.mark.parametrize(
'namespace,expected',
[
(
'PythonPackageNamespace(\'d\', [PythonPackageNamespac'
'e(\'e\', [PythonPackageNamespace(\'f\', [PythonPacka'
'geNamespace(\'g\', [PythonPackageNamespace(\'h\', [P'
'ythonPackageNamespace(\'i\', [PythonPackageNamespace'
'(\'j\', [PythonModuleNamespace(\'k\')])])])])])])])'
PythonPackageNamespace('a', []),
"PythonPackageNamespace('a', [])",
),
),
])
(
PythonPackageNamespace('1', []),
"PythonPackageNamespace('1', [])",
),
(
PythonPackageNamespace(StrSubclass('a'), []),
"PythonPackageNamespace('a', [])",
),
(
namespace_a,
"PythonPackageNamespace('a', [PythonModuleNamespace('b')])",
),
(
namespace_d,
(
"PythonPackageNamespace('d', [PythonPackageNamespac"
"e('e', [PythonPackageNamespace('f', [PythonPacka"
"geNamespace('g', [PythonPackageNamespace('h', [P"
"ythonPackageNamespace('i', [PythonPackageNamespace"
"('j', [PythonModuleNamespace('k')])])])])])])])"
),
),
],
)
def test_PythonPackageNamespace_string_representation(namespace, expected):
assert repr(namespace) == expected
@pytest.mark.parametrize('namespace,new_namespace,expected', [
(
PythonPackageNamespace('a', []),
PythonModuleNamespace('b'),
PythonPackageNamespace(
'a',
[PythonModuleNamespace('b')],
@pytest.mark.parametrize(
'namespace,new_namespace,expected',
[
(
PythonPackageNamespace('a', []),
PythonModuleNamespace('b'),
PythonPackageNamespace(
'a',
[PythonModuleNamespace('b')],
),
),
),
])
],
)
def test_PythonPackageNamespace___getitem__(
namespace,
new_namespace,
@@ -16,47 +16,58 @@ from src.jcloud_docsgen.utils import assert_that_is_instance
import pytest
import types
class TestType: ...
TestType.__name__ = 'NOT TestType'
@pytest.mark.parametrize('obj,class_or_tuple', [
(1, int),
(1, (int,)),
(1, str | int),
(1, int | str | list),
(TestType(), TestType),
(None, types.NoneType),
])
@pytest.mark.parametrize(
'obj,class_or_tuple',
[
(1, int),
(1, (int,)),
(1, str | int),
(1, int | str | list),
(TestType(), TestType),
(None, types.NoneType),
],
)
def test_assert_that_is_instance(obj, class_or_tuple):
assert_that_is_instance(obj, class_or_tuple)
@pytest.mark.parametrize('obj,class_or_tuple,expected_exception_msg', [
(
1,
str,
'expected \'str\', got \'int\'',
),
(
1,
(str,),
'expected \'str\', got \'int\'',
),
(
1,
str | float,
'expected either \'str\' or \'float\', got \'int\'',
),
(
1,
str | float | list,
'expected either \'str\', \'float\' or \'list\', got \'int\'',
),
(
1,
TestType,
'expected \'NOT TestType\', got \'int\'',
),
])
@pytest.mark.parametrize(
'obj,class_or_tuple,expected_exception_msg',
[
(
1,
str,
"expected 'str', got 'int'",
),
(
1,
(str,),
"expected 'str', got 'int'",
),
(
1,
str | float,
"expected either 'str' or 'float', got 'int'",
),
(
1,
str | float | list,
"expected either 'str', 'float' or 'list', got 'int'",
),
(
1,
TestType,
"expected 'NOT TestType', got 'int'",
),
],
)
def test_assert_that_is_instance_exceptions(
obj,
class_or_tuple,
@@ -16,61 +16,62 @@ from src.jcloud_docsgen.utils import get_relative_path_segments
import pytest
import pathlib
@pytest.mark.parametrize('path,root,expected', [
(pathlib.Path('/'), pathlib.Path('/'), []),
(pathlib.Path('/a'), pathlib.Path('/'), ['a']),
(pathlib.Path('/a/'), pathlib.Path('/'), ['a']),
(pathlib.Path('/a'), pathlib.Path('/a'), []),
(pathlib.Path('/a/'), pathlib.Path('/a'), []),
(pathlib.Path('/a'), pathlib.Path('/a/'), []),
(pathlib.Path('/a/'), pathlib.Path('/a/'), []),
(pathlib.Path('/a/b'), pathlib.Path('/'), ['a', 'b']),
(pathlib.Path('/a/b/'), pathlib.Path('/'), ['a', 'b']),
(pathlib.Path('/a/b'), pathlib.Path('/a'), ['b']),
(pathlib.Path('/a/b/'), pathlib.Path('/a'), ['b']),
(pathlib.Path('/a/b'), pathlib.Path('/a/'), ['b']),
(pathlib.Path('/a/b/'), pathlib.Path('/a/'), ['b']),
(pathlib.Path('/a/b'), pathlib.Path('/a/b'), []),
(pathlib.Path('/a/b/'), pathlib.Path('/a/b'), []),
(pathlib.Path('/a/b'), pathlib.Path('/a/b/'), []),
(pathlib.Path('/a/b/'), pathlib.Path('/a/b/'), []),
(pathlib.Path('./'), pathlib.Path('./'), []),
(pathlib.Path('./a'), pathlib.Path('./'), ['a']),
(pathlib.Path('./a/'), pathlib.Path('./'), ['a']),
(pathlib.Path('./a'), pathlib.Path('./a'), []),
(pathlib.Path('./a/'), pathlib.Path('./a'), []),
(pathlib.Path('./a'), pathlib.Path('./a/'), []),
(pathlib.Path('./a/'), pathlib.Path('./a/'), []),
(pathlib.Path('./a/b'), pathlib.Path('./'), ['a', 'b']),
(pathlib.Path('./a/b/'), pathlib.Path('./'), ['a', 'b']),
(pathlib.Path('./a/b'), pathlib.Path('./a'), ['b']),
(pathlib.Path('./a/b/'), pathlib.Path('./a'), ['b']),
(pathlib.Path('./a/b'), pathlib.Path('./a/'), ['b']),
(pathlib.Path('./a/b/'), pathlib.Path('./a/'), ['b']),
(pathlib.Path('./a/b'), pathlib.Path('./a/b'), []),
(pathlib.Path('./a/b/'), pathlib.Path('./a/b'), []),
(pathlib.Path('./a/b'), pathlib.Path('./a/b/'), []),
(pathlib.Path('./a/b/'), pathlib.Path('./a/b/'), []),
(pathlib.Path(''), pathlib.Path(''), []),
(pathlib.Path('a'), pathlib.Path(''), ['a']),
(pathlib.Path('a/'), pathlib.Path(''), ['a']),
(pathlib.Path('a'), pathlib.Path('a'), []),
(pathlib.Path('a/'), pathlib.Path('a'), []),
(pathlib.Path('a'), pathlib.Path('a/'), []),
(pathlib.Path('a/'), pathlib.Path('a/'), []),
(pathlib.Path('a/b'), pathlib.Path(''), ['a', 'b']),
(pathlib.Path('a/b/'), pathlib.Path(''), ['a', 'b']),
(pathlib.Path('a/b'), pathlib.Path('a'), ['b']),
(pathlib.Path('a/b/'), pathlib.Path('a'), ['b']),
(pathlib.Path('a/b'), pathlib.Path('a/'), ['b']),
(pathlib.Path('a/b/'), pathlib.Path('a/'), ['b']),
(pathlib.Path('a/b'), pathlib.Path('a/b'), []),
(pathlib.Path('a/b/'), pathlib.Path('a/b'), []),
(pathlib.Path('a/b'), pathlib.Path('a/b/'), []),
(pathlib.Path('a/b/'), pathlib.Path('a/b/'), []),
])
@pytest.mark.parametrize(
'path,root,expected',
[
(pathlib.Path('/'), pathlib.Path('/'), []),
(pathlib.Path('/a'), pathlib.Path('/'), ['a']),
(pathlib.Path('/a/'), pathlib.Path('/'), ['a']),
(pathlib.Path('/a'), pathlib.Path('/a'), []),
(pathlib.Path('/a/'), pathlib.Path('/a'), []),
(pathlib.Path('/a'), pathlib.Path('/a/'), []),
(pathlib.Path('/a/'), pathlib.Path('/a/'), []),
(pathlib.Path('/a/b'), pathlib.Path('/'), ['a', 'b']),
(pathlib.Path('/a/b/'), pathlib.Path('/'), ['a', 'b']),
(pathlib.Path('/a/b'), pathlib.Path('/a'), ['b']),
(pathlib.Path('/a/b/'), pathlib.Path('/a'), ['b']),
(pathlib.Path('/a/b'), pathlib.Path('/a/'), ['b']),
(pathlib.Path('/a/b/'), pathlib.Path('/a/'), ['b']),
(pathlib.Path('/a/b'), pathlib.Path('/a/b'), []),
(pathlib.Path('/a/b/'), pathlib.Path('/a/b'), []),
(pathlib.Path('/a/b'), pathlib.Path('/a/b/'), []),
(pathlib.Path('/a/b/'), pathlib.Path('/a/b/'), []),
(pathlib.Path('./'), pathlib.Path('./'), []),
(pathlib.Path('./a'), pathlib.Path('./'), ['a']),
(pathlib.Path('./a/'), pathlib.Path('./'), ['a']),
(pathlib.Path('./a'), pathlib.Path('./a'), []),
(pathlib.Path('./a/'), pathlib.Path('./a'), []),
(pathlib.Path('./a'), pathlib.Path('./a/'), []),
(pathlib.Path('./a/'), pathlib.Path('./a/'), []),
(pathlib.Path('./a/b'), pathlib.Path('./'), ['a', 'b']),
(pathlib.Path('./a/b/'), pathlib.Path('./'), ['a', 'b']),
(pathlib.Path('./a/b'), pathlib.Path('./a'), ['b']),
(pathlib.Path('./a/b/'), pathlib.Path('./a'), ['b']),
(pathlib.Path('./a/b'), pathlib.Path('./a/'), ['b']),
(pathlib.Path('./a/b/'), pathlib.Path('./a/'), ['b']),
(pathlib.Path('./a/b'), pathlib.Path('./a/b'), []),
(pathlib.Path('./a/b/'), pathlib.Path('./a/b'), []),
(pathlib.Path('./a/b'), pathlib.Path('./a/b/'), []),
(pathlib.Path('./a/b/'), pathlib.Path('./a/b/'), []),
(pathlib.Path(''), pathlib.Path(''), []),
(pathlib.Path('a'), pathlib.Path(''), ['a']),
(pathlib.Path('a/'), pathlib.Path(''), ['a']),
(pathlib.Path('a'), pathlib.Path('a'), []),
(pathlib.Path('a/'), pathlib.Path('a'), []),
(pathlib.Path('a'), pathlib.Path('a/'), []),
(pathlib.Path('a/'), pathlib.Path('a/'), []),
(pathlib.Path('a/b'), pathlib.Path(''), ['a', 'b']),
(pathlib.Path('a/b/'), pathlib.Path(''), ['a', 'b']),
(pathlib.Path('a/b'), pathlib.Path('a'), ['b']),
(pathlib.Path('a/b/'), pathlib.Path('a'), ['b']),
(pathlib.Path('a/b'), pathlib.Path('a/'), ['b']),
(pathlib.Path('a/b/'), pathlib.Path('a/'), ['b']),
(pathlib.Path('a/b'), pathlib.Path('a/b'), []),
(pathlib.Path('a/b/'), pathlib.Path('a/b'), []),
(pathlib.Path('a/b'), pathlib.Path('a/b/'), []),
(pathlib.Path('a/b/'), pathlib.Path('a/b/'), []),
],
)
def test_get_relative_path_segments(path, root, expected):
assert get_relative_path_segments(path, root) == expected
+44 -40
View File
@@ -15,45 +15,49 @@
from src.jcloud_docsgen.utils import human_readable_list
import pytest
@pytest.mark.parametrize('ls,final_separator,quotation_mark,expected', [
([], '', '', ''),
([], 'and', '', ''),
([], 'and', '\'', ''),
([1], '', '', '1'),
([-1], '', '', '-1'),
([0], '', '', '0'),
([0.0], '', '', '0.0'),
([-0.0], '', '', '-0.0'),
([4.2], '', '', '4.2'),
([-4.2], '', '', '-4.2'),
(['a'], '', '', 'a'),
([1], 'and', '', '1'),
(['a'], 'and', '', 'a'),
([1], 'and', '\'', '\'1\''),
(['a'], 'and', '\'', '\'a\''),
([1, 2], 'and', '', '1 and 2'),
([1, 2], 'and', '\'', '\'1\' and \'2\''),
([1, 'a'], 'and', '', '1 and a'),
([1, 'a'], 'and', '\'', '\'1\' and \'a\''),
([1, 'a'], 'and', '', '1 and a'),
([1, 'a'], 'and', '\'', '\'1\' and \'a\''),
([1, 'a'], 'and', '\'', '\'1\' and \'a\''),
([1, 'a'], '', '', '1 a'),
([1, 'a'], '', '\'', '\'1\' \'a\''),
([1, 'a'], '', '', '1 a'),
([1, 'a'], '', '\'', '\'1\' \'a\''),
([1, 'a'], '', '\'', '\'1\' \'a\''),
([1, 2, 3], 'and', '', '1, 2 and 3'),
([1, 2, 3], '', '', '1, 2 3'),
([1, 2, 3], 'and', '\'', '\'1\', \'2\' and \'3\''),
([1, 2, 3], '', '\'', '\'1\', \'2\' \'3\''),
([1, 2, 3, 42, 'a', 23], 'and', '', '1, 2, 3, 42, a and 23'),
(
[1, 2, 3, 42, 'a', 23],
'and',
'\'',
'\'1\', \'2\', \'3\', \'42\', \'a\' and \'23\'',
),
])
@pytest.mark.parametrize(
'ls,final_separator,quotation_mark,expected',
[
([], '', '', ''),
([], 'and', '', ''),
([], 'and', "'", ''),
([1], '', '', '1'),
([-1], '', '', '-1'),
([0], '', '', '0'),
([0.0], '', '', '0.0'),
([-0.0], '', '', '-0.0'),
([4.2], '', '', '4.2'),
([-4.2], '', '', '-4.2'),
(['a'], '', '', 'a'),
([1], 'and', '', '1'),
(['a'], 'and', '', 'a'),
([1], 'and', "'", "'1'"),
(['a'], 'and', "'", "'a'"),
([1, 2], 'and', '', '1 and 2'),
([1, 2], 'and', "'", "'1' and '2'"),
([1, 'a'], 'and', '', '1 and a'),
([1, 'a'], 'and', "'", "'1' and 'a'"),
([1, 'a'], 'and', '', '1 and a'),
([1, 'a'], 'and', "'", "'1' and 'a'"),
([1, 'a'], 'and', "'", "'1' and 'a'"),
([1, 'a'], '', '', '1 a'),
([1, 'a'], '', "'", "'1' 'a'"),
([1, 'a'], '', '', '1 a'),
([1, 'a'], '', "'", "'1' 'a'"),
([1, 'a'], '', "'", "'1' 'a'"),
([1, 2, 3], 'and', '', '1, 2 and 3'),
([1, 2, 3], '', '', '1, 2 3'),
([1, 2, 3], 'and', "'", "'1', '2' and '3'"),
([1, 2, 3], '', "'", "'1', '2' '3'"),
([1, 2, 3, 42, 'a', 23], 'and', '', '1, 2, 3, 42, a and 23'),
(
[1, 2, 3, 42, 'a', 23],
'and',
"'",
"'1', '2', '3', '42', 'a' and '23'",
),
],
)
def test_human_readable_list(ls, final_separator, quotation_mark, expected):
assert human_readable_list(ls, final_separator, quotation_mark) == expected
+15 -11
View File
@@ -15,16 +15,20 @@
from src.jcloud_docsgen.utils import non_empty_str
import pytest
@pytest.mark.parametrize('value,expected', [
(None, None),
('', None),
(' ', None),
(' ', None),
(' ', None),
(' a ', ' a '),
(' a', ' a'),
('a ', 'a '),
('a', 'a'),
])
@pytest.mark.parametrize(
'value,expected',
[
(None, None),
('', None),
(' ', None),
(' ', None),
(' ', None),
(' a ', ' a '),
(' a', ' a'),
('a ', 'a '),
('a', 'a'),
],
)
def test_non_empty_str(value, expected):
assert non_empty_str(value) == expected
+4 -5
View File
@@ -15,12 +15,11 @@
from typing import Union
import ast
__all__ = [
'ast_node'
]
__all__ = ['ast_node']
def ast_node(statement: str) -> Union[ast.stmt, None]:
'''
"""
Returns an AST node from the statement.
:param statement: The statement.
@@ -28,7 +27,7 @@ def ast_node(statement: str) -> Union[ast.stmt, None]:
:return: The node.
:rtype: Union[ast.stmt, None]
'''
"""
body = ast.parse(statement).body
if not body: