Style: ensure line character limit and remove trailing whitespace #19

Merged
jakob.scheid merged 2 commits from style/pip-8-compliance into main 2026-09-18 17:13:00 +02:00
25 changed files with 6708 additions and 1053 deletions
Showing only changes of commit caa8e04047 - Show all commits

No files matched your search

+20 -5
View File
@@ -35,7 +35,9 @@ def existing_directory(path: str) -> pathlib.Path:
_path = path _path = path
path = pathlib.Path(_path) path = pathlib.Path(_path)
if not path.exists(): 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(): if not path.is_dir():
raise argparse.ArgumentTypeError(f'{_path}: not a directory') raise argparse.ArgumentTypeError(f'{_path}: not a directory')
return path return path
@@ -79,7 +81,10 @@ def create_argument_parser() -> argparse.ArgumentParser:
argument_parser = argparse.ArgumentParser() argument_parser = argparse.ArgumentParser()
subparsers = argument_parser.add_subparsers(dest = 'language', required = True) subparsers = argument_parser.add_subparsers(
dest = 'language',
required = True
)
python_parser = subparsers.add_parser( python_parser = subparsers.add_parser(
_LANG_PYTHON, _LANG_PYTHON,
@@ -99,19 +104,29 @@ def create_argument_parser() -> argparse.ArgumentParser:
nargs = '+', nargs = '+',
default = [], default = [],
choices = VALID_PYTHON_SECTIONS, choices = VALID_PYTHON_SECTIONS,
help = 'the sections to be excluded in the documentation. If a section shall be included AND excluded, it is excluded.' help = (
'the sections to be excluded in the documentation. '
'If a section shall be included AND excluded, it is excluded.'
)
) )
python_parser.add_argument( python_parser.add_argument(
'-H', '-H',
'--allow-html', '--allow-html',
action = 'store_true', action = 'store_true',
help = 'indicates whether HTML is allowed in the documentation. By default, HTML is disabled.' help = (
'indicates whether HTML is allowed in the documentation. '
'By default, HTML is disabled.'
)
) )
python_parser.add_argument( python_parser.add_argument(
'-T', '-T',
'--allow-tables', '--allow-tables',
action = 'store_true', 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.' help = (
'indicates whether Markdown tables are allowed in the '
'documentation. By default, Markdown tables are disabled. '
'Warning: Markdown tables are not CommonMark.'
)
) )
return argument_parser return argument_parser
+14 -4
View File
@@ -13,7 +13,10 @@
# limitations under the License. # limitations under the License.
import pathlib import pathlib
from ...core.python import PythonDefinitionDocumentationIncludeSections, PythonDocumentationGenerator from ...core.python import (
PythonDefinitionDocumentationIncludeSections,
PythonDocumentationGenerator
)
from typing import Optional from typing import Optional
__all__ = [ __all__ = [
@@ -22,9 +25,14 @@ __all__ = [
'generate' 'generate'
] ]
VALID_SECTIONS = list(PythonDefinitionDocumentationIncludeSections()._args.keys()) VALID_SECTIONS = list(
PythonDefinitionDocumentationIncludeSections()._args.keys()
)
def parse_sections_list(include_sections: Optional[list[str]] = None, exclude_sections: list[str] = []) -> PythonDefinitionDocumentationIncludeSections: def parse_sections_list(
include_sections: Optional[list[str]] = None,
exclude_sections: list[str] = []
) -> PythonDefinitionDocumentationIncludeSections:
''' '''
Returns a Python definition documentation sections list from the Returns a Python definition documentation sections list from the
sections list to be included and the sections list to be excluded. sections list to be included and the sections list to be excluded.
@@ -63,7 +71,9 @@ def _generate(
project_directory: pathlib.Path, project_directory: pathlib.Path,
output_directory: pathlib.Path, output_directory: pathlib.Path,
*, *,
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(), include_sections: PythonDefinitionDocumentationIncludeSections = (
PythonDefinitionDocumentationIncludeSections()
),
allow_html: bool = False, allow_html: bool = False,
allow_tables: bool = False allow_tables: bool = False
) -> None: ) -> None:
+199 -42
View File
@@ -16,11 +16,16 @@ from __future__ import annotations
from ...utils import non_empty_str, get_relative_path_segments from ...utils import non_empty_str, get_relative_path_segments
from ...exceptions import PythonArgumentStructureError, InvalidNamespaceError from ...exceptions import PythonArgumentStructureError, InvalidNamespaceError
from .namespaces import PythonModuleNamespace, PythonPackageNamespace from .namespaces import PythonModuleNamespace, PythonPackageNamespace
from .definitions import PythonDefinition, PythonFunctionDefinition, PythonAsyncFunctionDefinition, PythonClassDefinition from .definitions import (
PythonAsyncFunctionDefinition,
PythonClassDefinition,
PythonDefinition,
PythonFunctionDefinition
)
from .arguments import PythonArgumentKind, PythonFunctionArgument from .arguments import PythonArgumentKind, PythonFunctionArgument
from .docstrings import PythonDocstring from .docstrings import PythonDocstring
from collections.abc import Iterator from collections.abc import Iterator
from typing import Union from typing import TypeAlias, Union
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
import docstring_parser import docstring_parser
@@ -53,7 +58,17 @@ def _collect_definitions(tree_or_node) -> Iterator[PythonDefinition]:
if isinstance(node, ast.ClassDef): if isinstance(node, ast.ClassDef):
yield PythonClassDefinition.from_node(node) yield PythonClassDefinition.from_node(node)
def _group_argument_list(argument_list: list[PythonFunctionArgument]) -> tuple[list[PythonFunctionArgument], list[PythonFunctionArgument], Union[PythonFunctionArgument, None], list[PythonFunctionArgument], Union[PythonFunctionArgument, None]]: _Arguments: TypeAlias = tuple[
list[PythonFunctionArgument],
list[PythonFunctionArgument],
Union[PythonFunctionArgument, None],
list[PythonFunctionArgument],
Union[PythonFunctionArgument, None]
]
def _group_argument_list(
argument_list: list[PythonFunctionArgument]
) -> _Arguments:
''' '''
Groups the argument list into a list of positional-only arguments, a 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 list of normal arguments, the * argument if there is one, a list of
@@ -62,7 +77,7 @@ def _group_argument_list(argument_list: list[PythonFunctionArgument]) -> tuple[l
:param argument_list: The argument list. :param argument_list: The argument list.
:type argument_list: list[PythonFunctionArgument] :type argument_list: list[PythonFunctionArgument]
:rtype: tuple[list[PythonFunctionArgument], list[PythonFunctionArgument], Union[PythonFunctionArgument, None], list[PythonFunctionArgument], Union[PythonFunctionArgument, None]] :rtype: _Arguments
''' '''
if argument_list: if argument_list:
@@ -70,7 +85,9 @@ def _group_argument_list(argument_list: list[PythonFunctionArgument]) -> tuple[l
arg.kind == PythonArgumentKind.KWARGS arg.kind == PythonArgumentKind.KWARGS
for arg in argument_list for arg in argument_list
]): ]):
raise PythonArgumentStructureError('arguments cannot follow var-keyword argument') raise PythonArgumentStructureError(
'arguments cannot follow var-keyword argument'
)
positional_only_args = [] positional_only_args = []
normal_args = [] normal_args = []
@@ -87,7 +104,10 @@ def _group_argument_list(argument_list: list[PythonFunctionArgument]) -> tuple[l
if arg.kind == PythonArgumentKind.VARARG: if arg.kind == PythonArgumentKind.VARARG:
if vararg is not None: if vararg is not None:
raise PythonArgumentStructureError('* argument may only appear once', definition = arg) raise PythonArgumentStructureError(
'* argument may only appear once',
definition = arg
)
vararg = arg vararg = arg
if arg.kind == PythonArgumentKind.KEYWORD_ONLY: if arg.kind == PythonArgumentKind.KEYWORD_ONLY:
@@ -96,9 +116,17 @@ def _group_argument_list(argument_list: list[PythonFunctionArgument]) -> tuple[l
if arg.kind == PythonArgumentKind.KWARGS: if arg.kind == PythonArgumentKind.KWARGS:
kwargs = arg kwargs = arg
return positional_only_args, normal_args, vararg, keyword_only_args, kwargs return (
positional_only_args,
normal_args,
vararg,
keyword_only_args,
kwargs
)
def _signature_argument_list(argument_list: list[PythonFunctionArgument]) -> list[str]: def _signature_argument_list(
argument_list: list[PythonFunctionArgument]
) -> list[str]:
''' '''
Returns a list of the arguments as strings for a function signature. Returns a list of the arguments as strings for a function signature.
@@ -109,7 +137,13 @@ def _signature_argument_list(argument_list: list[PythonFunctionArgument]) -> lis
:rtype: list[str] :rtype: list[str]
''' '''
positional_only_args, normal_args, vararg, keyword_only_args, kwargs = _group_argument_list(argument_list) (
positional_only_args,
normal_args,
vararg,
keyword_only_args,
kwargs
) = _group_argument_list(argument_list)
args = [] args = []
@@ -157,7 +191,15 @@ def _fit_row_length(expected: int, row: tuple) -> tuple:
else: else:
return row + (None,) * (expected - len(row)) return row + (None,) * (expected - len(row))
def _markdown_table(header: tuple, body: list[tuple], *, allow_markdown_table: bool = False, force_row_lengths: bool = False, one_line_values: bool = True, missing_value: str = '--') -> str: def _markdown_table(
header: tuple,
body: list[tuple],
*,
allow_markdown_table: bool = False,
force_row_lengths: bool = False,
one_line_values: bool = True,
missing_value: str = '--'
) -> str:
''' '''
Generates a table. Generates a table.
@@ -188,18 +230,34 @@ def _markdown_table(header: tuple, body: list[tuple], *, allow_markdown_table: b
header_len = len(header) header_len = len(header)
if allow_markdown_table: if allow_markdown_table:
table = '| ' + ' | '.join(header) + ' |\n|' + ' --- |' * len(header) + '\n' table = (
'| ' \
+ ' | '.join(header) \
+ ' |\n|' \
+ ' --- |' * len(header) \
+ '\n'
)
for row in body: for row in body:
if len(row) != header_len: if len(row) != header_len:
if force_row_lengths: if force_row_lengths:
raise ValueError(f'expected {header_len} value{"s" if header_len > 1 else ""}, got {len(row)}') raise ValueError(
f'expected {header_len} value{
"s"
if header_len > 1
else ""
}, got {len(row)}'
)
else: else:
row = _fit_row_length(header_len, row) row = _fit_row_length(header_len, row)
table += '| ' + ' | '.join([ table += '| ' + ' | '.join([
str(col if col is not None else missing_value).replace('\n', ' ') str(
col
if col is not None
else missing_value
).replace('\n', ' ')
if one_line_values if one_line_values
else str(col if col is not None else missing_value) else str(col if col is not None else missing_value)
for col in row for col in row
@@ -210,15 +268,36 @@ def _markdown_table(header: tuple, body: list[tuple], *, allow_markdown_table: b
for row in body: for row in body:
if len(row) != header_len: if len(row) != header_len:
if force_row_lengths: if force_row_lengths:
raise ValueError(f'expected {header_len} value{"s" if header_len > 1 else ""}, got {len(row)}') raise ValueError(
f'expected {header_len} value{
"s"
if header_len > 1
else ""
}, got {len(row)}'
)
else: else:
row = _fit_row_length(header_len, row) row = _fit_row_length(header_len, row)
sep = '- ' sep = '- '
for i, col in enumerate(row): for i, col in enumerate(row):
table += sep + header[i] + ': ' + (str(col if col is not None else missing_value).replace('\n', ' ') \ table += (
sep \
+ header[i] \
+ ': ' \
+ (
str(
col
if col is not None
else missing_value
).replace('\n', ' ') \
if one_line_values \ if one_line_values \
else str(col if col is not None else missing_value)) + '\n\n' else str(
col
if col is not None
else missing_value
)
) + '\n\n'
)
sep = ' ' sep = ' '
return table.strip() return table.strip()
@@ -277,7 +356,10 @@ def _argument_kind_human_readable(argument_kind: PythonArgumentKind) -> str:
return argument_kind.name.lower().replace('_', '-') return argument_kind.name.lower().replace('_', '-')
def _function_params_info(arg_list: list[PythonFunctionArgument], docstring_arg_list: list[docstring_parser.DocstringParam]) -> dict[str, _FunctionParamInfo]: 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. Returns a dictionary with the parameter info.
@@ -297,7 +379,10 @@ def _function_params_info(arg_list: list[PythonFunctionArgument], docstring_arg_
:rtype: dict[str, _FunctionParamInfo] :rtype: dict[str, _FunctionParamInfo]
''' '''
arg_annotations = {arg.name: _FunctionParamInfo(arg.annotation, None) for arg in arg_list} arg_annotations = {
arg.name: _FunctionParamInfo(arg.annotation, None)
for arg in arg_list
}
for arg in docstring_arg_list: for arg in docstring_arg_list:
if arg.arg_name in arg_annotations.keys(): if arg.arg_name in arg_annotations.keys():
@@ -375,7 +460,10 @@ class PythonDefinitionDocumentationIncludeSections:
) + ')' ) + ')'
def __eq__(self, value) -> str: def __eq__(self, value) -> str:
if not isinstance(value, PythonDefinitionDocumentationIncludeSections): if not isinstance(
value,
PythonDefinitionDocumentationIncludeSections
):
return False return False
return self._args == value._args return self._args == value._args
@@ -409,7 +497,9 @@ class PythonDefinitionDocumentationGenerator:
def __init__( def __init__(
self, self,
definition: PythonDefinition, definition: PythonDefinition,
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(), include_sections: PythonDefinitionDocumentationIncludeSections = (
PythonDefinitionDocumentationIncludeSections()
),
level: int = 1, level: int = 1,
*, *,
allow_html: bool = True, allow_html: bool = True,
@@ -431,7 +521,9 @@ class PythonDefinitionDocumentationGenerator:
@property @property
def _docstring(self): def _docstring(self):
if self._docstring_cache is None: if self._docstring_cache is None:
self._docstring_cache = self.definition.doc.parse() if _is_docstring(self.definition.doc) else _NoDocstring self._docstring_cache = self.definition.doc.parse() \
if _is_docstring(self.definition.doc) \
else _NoDocstring
return self._docstring_cache return self._docstring_cache
def generate_signature(self) -> str: def generate_signature(self) -> str:
@@ -446,9 +538,15 @@ class PythonDefinitionDocumentationGenerator:
''' '''
if isinstance(self.definition, PythonClassDefinition): if isinstance(self.definition, PythonClassDefinition):
raise TypeError('Cannot generate a signature of a class definition.') 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: if self.definition.returns is not None:
signature += ' -> ' + self.definition.returns signature += ' -> ' + self.definition.returns
@@ -463,7 +561,13 @@ class PythonDefinitionDocumentationGenerator:
:rtype: str :rtype: str
''' '''
return self.level * '#' + '# ' + ('Function' if not self.is_method else 'Method') +' signature\n\n```python\n' + self.generate_signature() + '\n```' definition_type = 'Function' if not self.is_method else 'Method'
heading = self.level * '#' + '# ' + (definition_type) +' signature'
return f'''{heading}
```python
{self.generate_signature()}
```'''
def _generate_docstring_section(self) -> str: def _generate_docstring_section(self) -> str:
''' '''
@@ -477,7 +581,11 @@ class PythonDefinitionDocumentationGenerator:
if _is_docstring(self.definition.doc) or not self.skip_empty_sections: if _is_docstring(self.definition.doc) or not self.skip_empty_sections:
if self.allow_html: if self.allow_html:
md += '<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: else:
md += self.level * '#' + '# Docstring\n\n' md += self.level * '#' + '# Docstring\n\n'
@@ -506,19 +614,30 @@ class PythonDefinitionDocumentationGenerator:
if self.definition.args or not self.skip_empty_sections: if self.definition.args or not self.skip_empty_sections:
md += self.level * '#' + '# Parameters\n\n' md += self.level * '#' + '# Parameters\n\n'
params_types = _function_params_info(self.definition.args, self._docstring.params) params_types = _function_params_info(
self.definition.args,
self._docstring.params
)
if params_types: if params_types:
md += _markdown_table(['Parameter', 'Type', 'Kind', 'Default value', 'Description'], [ md += _markdown_table(
['Parameter', 'Type', 'Kind', 'Default value', 'Description'],
[
( (
'`' + arg.name + '`', '`' + arg.name + '`',
('`' + params_types.get(arg.name).annotation + '`') if params_types.get(arg.name).annotation is not None else None, ('`' + params_types.get(arg.name).annotation + '`')
if params_types.get(arg.name).annotation is not None
else None,
_argument_kind_human_readable(arg.kind), _argument_kind_human_readable(arg.kind),
('`' + arg.default + '`') if arg.default is not None else None, ('`' + arg.default + '`')
if arg.default is not None
else None,
non_empty_str(params_types.get(arg.name).description) non_empty_str(params_types.get(arg.name).description)
) )
for arg in self.definition.args for arg in self.definition.args
], allow_markdown_table = self.allow_tables) ],
allow_markdown_table = self.allow_tables
)
md += '\n\n' md += '\n\n'
@@ -537,7 +656,11 @@ class PythonDefinitionDocumentationGenerator:
md = '' md = ''
if self._docstring.returns or self.definition.returns or not self.skip_empty_sections: if (
self._docstring.returns
or self.definition.returns
or not self.skip_empty_sections
):
md += self.level * '#' + '# Returns\n\n' md += self.level * '#' + '# Returns\n\n'
returns = False returns = False
@@ -555,7 +678,10 @@ class PythonDefinitionDocumentationGenerator:
md += 'Return type: `' + rtype + '`' md += 'Return type: `' + rtype + '`'
if not self.skip_empty_sections and not returns: if not self.skip_empty_sections and not returns:
md += 'This function does not provide any information about its return value.' md += (
'This function does not provide any '
'information about its return value.'
)
return md.strip() return md.strip()
@@ -574,7 +700,11 @@ class PythonDefinitionDocumentationGenerator:
if self._docstring.raises: if self._docstring.raises:
md += _markdown_table(('Exception', 'Description'), [ md += _markdown_table(('Exception', 'Description'), [
(('`' + exception.type_name + '`') if exception.type_name is not None else None, non_empty_str(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 for exception in self._docstring.raises
], allow_markdown_table = self.allow_tables) ], allow_markdown_table = self.allow_tables)
elif not self.skip_empty_sections: elif not self.skip_empty_sections:
@@ -689,7 +819,9 @@ class PythonDefinitionDocumentationGenerator:
md = self.level * '#' + ' ' md = self.level * '#' + ' '
if isinstance(self.definition, PythonAsyncFunctionDefinition): if isinstance(self.definition, PythonAsyncFunctionDefinition):
md += 'Asynchronous ' + ('function' if not self.is_method else 'method') md += 'Asynchronous ' + (
'function' if not self.is_method else 'method'
)
elif isinstance(self.definition, PythonFunctionDefinition): elif isinstance(self.definition, PythonFunctionDefinition):
if self.is_method and self.definition.name == '__init__': if self.is_method and self.definition.name == '__init__':
md += 'Constructor (method' md += 'Constructor (method'
@@ -699,7 +831,12 @@ class PythonDefinitionDocumentationGenerator:
md += 'Class' md += 'Class'
md += ' `' + self.definition.name + '`' md += ' `' + self.definition.name + '`'
if isinstance(self.definition, PythonFunctionDefinition) and not isinstance(self.definition, PythonAsyncFunctionDefinition) and self.is_method and self.definition.name == '__init__': if (
isinstance(self.definition, PythonFunctionDefinition)
and not isinstance(self.definition, PythonAsyncFunctionDefinition)
and self.is_method
and self.definition.name == '__init__'
):
md += ')' md += ')'
md += '\n\n' md += '\n\n'
@@ -756,14 +893,23 @@ class PythonModuleDocumentationGenerator:
self, self,
module_path: pathlib.Path, module_path: pathlib.Path,
src_directory: Union[pathlib.Path, None] = None, src_directory: Union[pathlib.Path, None] = None,
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(), include_sections: PythonDefinitionDocumentationIncludeSections = (
PythonDefinitionDocumentationIncludeSections()
),
**kwargs **kwargs
) -> None: ) -> None:
if module_path.name.count('.') > 1: if module_path.name.count('.') > 1:
raise InvalidNamespaceError('namespace identifier cannot contain more than one dot', namespace_identifier = module_path.name) raise InvalidNamespaceError(
'namespace identifier cannot contain more than one dot',
namespace_identifier = module_path.name
)
self.module_path = pathlib.Path(str(module_path)) self.module_path = pathlib.Path(str(module_path))
self.src_directory = pathlib.Path(str(src_directory)) if src_directory is not None else None self.src_directory = (
pathlib.Path(str(src_directory))
if src_directory is not None
else None
)
self.include_sections = include_sections self.include_sections = include_sections
self.kwargs = kwargs self.kwargs = kwargs
@@ -807,7 +953,12 @@ class PythonModuleDocumentationGenerator:
md = '# Module `' md = '# Module `'
if self.src_directory is not None: if self.src_directory is not None:
module_identifier = '.'.join(get_relative_path_segments(self.module_path, self.src_directory)) module_identifier = '.'.join(
get_relative_path_segments(
self.module_path,
self.src_directory
)
)
if module_identifier.endswith('.py'): if module_identifier.endswith('.py'):
md += module_identifier[:-3] md += module_identifier[:-3]
else: else:
@@ -836,7 +987,9 @@ def _generate_package_namespace_documentation(
documentation_directory: pathlib.Path, documentation_directory: pathlib.Path,
root_directory: pathlib.Path, root_directory: pathlib.Path,
dir: pathlib.Path, dir: pathlib.Path,
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(), include_sections: PythonDefinitionDocumentationIncludeSections = (
PythonDefinitionDocumentationIncludeSections()
),
kwargs: dict = {} kwargs: dict = {}
) -> None: ) -> None:
''' '''
@@ -860,7 +1013,9 @@ def _generate_package_namespace_documentation(
for sub_namespace in namespace.sub_namespaces: for sub_namespace in namespace.sub_namespaces:
if isinstance(sub_namespace, PythonPackageNamespace): 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( _generate_package_namespace_documentation(
sub_namespace, sub_namespace,
documentation_directory / sub_namespace.name, documentation_directory / sub_namespace.name,
@@ -897,7 +1052,9 @@ class PythonDocumentationGenerator:
self, self,
project_directory: pathlib.Path, project_directory: pathlib.Path,
docs_directory: pathlib.Path, docs_directory: pathlib.Path,
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(), include_sections: PythonDefinitionDocumentationIncludeSections = (
PythonDefinitionDocumentationIncludeSections()
),
**kwargs **kwargs
) -> None: ) -> None:
self.project_directory = pathlib.Path(str(project_directory)) self.project_directory = pathlib.Path(str(project_directory))
+133 -21
View File
@@ -17,7 +17,11 @@ from enum import Enum
from typing import Union from typing import Union
import ast import ast
from ...utils import assert_that_is_instance from ...utils import assert_that_is_instance
from ...exceptions import InvalidPythonIdentifierError, InvalidPythonAnnotationError, PythonArgumentStructureError from ...exceptions import (
InvalidPythonAnnotationError,
InvalidPythonIdentifierError,
PythonArgumentStructureError
)
import keyword import keyword
import types import types
@@ -48,7 +52,13 @@ class PythonFunctionArgument:
:type annotation: Union[str, None] :type annotation: Union[str, None]
''' '''
def __init__(self, name: str, kind: PythonArgumentKind, default: Union[str, None], annotation: Union[str, None]) -> None: def __init__(
self,
name: str,
kind: PythonArgumentKind,
default: Union[str, None],
annotation: Union[str, None]
) -> None:
assert_that_is_instance(name, str) assert_that_is_instance(name, str)
assert_that_is_instance(kind, PythonArgumentKind) assert_that_is_instance(kind, PythonArgumentKind)
assert_that_is_instance(default, (str, types.NoneType)) assert_that_is_instance(default, (str, types.NoneType))
@@ -56,26 +66,43 @@ class PythonFunctionArgument:
# check whether name is a valid identifier # check whether name is a valid identifier
if not name or not name.isidentifier() or keyword.iskeyword(name): if not name or not name.isidentifier() or keyword.iskeyword(name):
raise InvalidPythonIdentifierError('invalid identifier', identifier = name) raise InvalidPythonIdentifierError(
'invalid identifier',
identifier = name
)
# check whether the annotation is a valid annotation # check whether the annotation is a valid annotation
# Currently, it is only checked whether the parameter is not # Currently, it is only checked whether the parameter is not
# empty. # empty.
if annotation is not None and not annotation: if annotation is not None and not annotation:
raise InvalidPythonAnnotationError('invalid annotation', annotation = annotation) raise InvalidPythonAnnotationError(
'invalid annotation',
annotation = annotation
)
self.name = name self.name = name
self.kind = kind self.kind = kind
self.default = default self.default = default
self.annotation = annotation self.annotation = annotation
if self.kind in (PythonArgumentKind.VARARG, PythonArgumentKind.KWARGS) and self.default is not None: if self.kind in (
raise PythonArgumentStructureError('* or ** arguments cannot have default values', definition = self.signature_repr()) PythonArgumentKind.VARARG,
PythonArgumentKind.KWARGS
) and self.default is not None:
raise PythonArgumentStructureError(
'* or ** arguments cannot have default values',
definition = self.signature_repr()
)
def __eq__(self, value: PythonFunctionArgument) -> bool: def __eq__(self, value: PythonFunctionArgument) -> bool:
if not isinstance(value, PythonFunctionArgument): if not isinstance(value, PythonFunctionArgument):
return False return False
return self.name == value.name and self.kind == value.kind and self.default == value.default and self.annotation == value.annotation return (
self.name == value.name
and self.kind == value.kind
and self.default == value.default
and self.annotation == value.annotation
)
def __repr__(self) -> str: def __repr__(self) -> str:
return type(self).__name__ + repr(( return type(self).__name__ + repr((
@@ -131,43 +158,128 @@ class PythonASTArgumentsListParser:
:rtype: list[PythonFunctionArgument] :rtype: list[PythonFunctionArgument]
''' '''
defaults = [None for _ in range(len( defaults = (
[
None
for _ in range(len(
self.ast_arguments_list.posonlyargs + self.ast_arguments_list.posonlyargs +
self.ast_arguments_list.args + self.ast_arguments_list.args +
self.ast_arguments_list.kwonlyargs self.ast_arguments_list.kwonlyargs
) - len(self.ast_arguments_list.defaults) - len(self.ast_arguments_list.kw_defaults))] + \ ) - len(
[ast.unparse(d) for d in self.ast_arguments_list.defaults] + \ self.ast_arguments_list.defaults
[ast.unparse(d) if d is not None else None for d in self.ast_arguments_list.kw_defaults] ) - len(
self.ast_arguments_list.kw_defaults
))
]
+ [
ast.unparse(d)
for d in self.ast_arguments_list.defaults
]
+ [
ast.unparse(d)
if d is not None
else None
for d in self.ast_arguments_list.kw_defaults
]
)
i = 0 i = 0
arguments = [] arguments = []
for arg in self.ast_arguments_list.posonlyargs: for arg in self.ast_arguments_list.posonlyargs:
if arg.annotation is None: if arg.annotation is None:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.POSITIONAL_ONLY, defaults[i], None)) arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.POSITIONAL_ONLY,
defaults[i],
None
)
)
else: else:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.POSITIONAL_ONLY, defaults[i], ast.unparse(arg.annotation))) arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.POSITIONAL_ONLY,
defaults[i],
ast.unparse(arg.annotation)))
i += 1 i += 1
for arg in self.ast_arguments_list.args: for arg in self.ast_arguments_list.args:
if arg.annotation is None: if arg.annotation is None:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.NORMAL, defaults[i], None)) arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.NORMAL,
defaults[i],
None
)
)
else: else:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.NORMAL, defaults[i], ast.unparse(arg.annotation))) arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.NORMAL,
defaults[i],
ast.unparse(arg.annotation)
)
)
i += 1 i += 1
if self.ast_arguments_list.vararg is not None: if self.ast_arguments_list.vararg is not None:
if self.ast_arguments_list.vararg.annotation is None: if self.ast_arguments_list.vararg.annotation is None:
arguments.append(PythonFunctionArgument(self.ast_arguments_list.vararg.arg, PythonArgumentKind.VARARG, None, None)) arguments.append(
PythonFunctionArgument(
self.ast_arguments_list.vararg.arg,
PythonArgumentKind.VARARG,
None,
None
)
)
else: else:
arguments.append(PythonFunctionArgument(self.ast_arguments_list.vararg.arg, PythonArgumentKind.VARARG, None, ast.unparse(self.ast_arguments_list.vararg.annotation))) arguments.append(
PythonFunctionArgument(
self.ast_arguments_list.vararg.arg,
PythonArgumentKind.VARARG,
None,
ast.unparse(self.ast_arguments_list.vararg.annotation)
)
)
for arg in self.ast_arguments_list.kwonlyargs: for arg in self.ast_arguments_list.kwonlyargs:
if arg.annotation is None: if arg.annotation is None:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.KEYWORD_ONLY, defaults[i], None)) arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.KEYWORD_ONLY,
defaults[i],
None
)
)
else: else:
arguments.append(PythonFunctionArgument(arg.arg, PythonArgumentKind.KEYWORD_ONLY, defaults[i], ast.unparse(arg.annotation))) arguments.append(
PythonFunctionArgument(
arg.arg,
PythonArgumentKind.KEYWORD_ONLY,
defaults[i],
ast.unparse(arg.annotation)
)
)
i += 1 i += 1
if self.ast_arguments_list.kwarg is not None: if self.ast_arguments_list.kwarg is not None:
if self.ast_arguments_list.kwarg.annotation is None: if self.ast_arguments_list.kwarg.annotation is None:
arguments.append(PythonFunctionArgument(self.ast_arguments_list.kwarg.arg, PythonArgumentKind.KWARGS, None, None)) arguments.append(
PythonFunctionArgument(
self.ast_arguments_list.kwarg.arg,
PythonArgumentKind.KWARGS,
None,
None
)
)
else: else:
arguments.append(PythonFunctionArgument(self.ast_arguments_list.kwarg.arg, PythonArgumentKind.KWARGS, None, ast.unparse(self.ast_arguments_list.kwarg.annotation))) arguments.append(
PythonFunctionArgument(
self.ast_arguments_list.kwarg.arg,
PythonArgumentKind.KWARGS,
None,
ast.unparse(self.ast_arguments_list.kwarg.annotation)
)
)
return arguments return arguments
+30 -5
View File
@@ -59,9 +59,17 @@ class PythonDefinition:
Python identifier. Python identifier.
''' '''
def __init__(self, name: str, doc: Union[PythonDocstring, None], decorators: list[str]) -> None: def __init__(
self,
name: str,
doc: Union[PythonDocstring, None],
decorators: list[str]
) -> None:
if keyword.iskeyword(name) or not name.isidentifier() or not name: if keyword.iskeyword(name) or not name.isidentifier() or not name:
raise InvalidPythonIdentifierError('invalid identifier', identifier = name) raise InvalidPythonIdentifierError(
'invalid identifier',
identifier = name
)
assert_that_is_instance(doc, (PythonDocstring, types.NoneType)) assert_that_is_instance(doc, (PythonDocstring, types.NoneType))
@@ -119,7 +127,14 @@ class PythonFunctionDefinition(PythonDefinition):
Python identifier. Python identifier.
''' '''
def __init__(self, name: str, args: list[PythonFunctionArgument], returns: Union[str, None], doc: Union[PythonDocstring, None], decorators: list[str]) -> None: def __init__(
self,
name: str,
args: list[PythonFunctionArgument],
returns: Union[str, None],
doc: Union[PythonDocstring, None],
decorators: list[str]
) -> None:
super().__init__(name, doc, decorators) super().__init__(name, doc, decorators)
self.args = args self.args = args
self.returns = returns self.returns = returns
@@ -189,7 +204,14 @@ class PythonClassDefinition(PythonDefinition):
Python identifier. Python identifier.
''' '''
def __init__(self, name: str, bases: list[str], doc: Union[PythonDocstring, None], decorators: list[str], body: list[PythonDefinition]) -> None: def __init__(
self,
name: str,
bases: list[str],
doc: Union[PythonDocstring, None],
decorators: list[str],
body: list[PythonDefinition]
) -> None:
super().__init__(name, doc, decorators) super().__init__(name, doc, decorators)
self.bases = bases self.bases = bases
self.body = body self.body = body
@@ -227,6 +249,9 @@ class PythonClassDefinition(PythonDefinition):
else PythonClassDefinition.from_node(expr) else PythonClassDefinition.from_node(expr)
for expr in node.body for expr in node.body
if isinstance(expr, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) if isinstance(
expr,
(ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)
)
] ]
) )
+38 -8
View File
@@ -13,7 +13,11 @@
# limitations under the License. # limitations under the License.
from __future__ import annotations from __future__ import annotations
from ...exceptions import InvalidNamespaceError, NamespaceNotFoundError, NamespaceExistsError from ...exceptions import (
InvalidNamespaceError,
NamespaceNotFoundError,
NamespaceExistsError
)
from ...utils import assert_that_is_instance from ...utils import assert_that_is_instance
from typing import Union, TypeAlias, Dict, List from typing import Union, TypeAlias, Dict, List
@@ -34,14 +38,23 @@ class PythonNamespace:
assert_that_is_instance(name, str) assert_that_is_instance(name, str)
if not name.strip(): if not name.strip():
raise InvalidNamespaceError('invalid namespace', namespace_identifier = name) raise InvalidNamespaceError(
'invalid namespace',
namespace_identifier = name
)
self.name = name self.name = name
def __repr__(self) -> str: def __repr__(self) -> str:
return f'{type(self).__name__}({self.name!r})' return f'{type(self).__name__}({self.name!r})'
PythonPackageNamespaceDictType: TypeAlias = Dict[str, Union[str, 'PythonPackageNamespaceDictType']] PythonPackageNamespaceDictType: TypeAlias = Dict[
str,
Union[
str,
'PythonPackageNamespaceDictType'
]
]
class PythonPackageNamespace(PythonNamespace): class PythonPackageNamespace(PythonNamespace):
''' '''
@@ -53,7 +66,11 @@ class PythonPackageNamespace(PythonNamespace):
:type sub_namespaces: list[PythonNamespace] :type sub_namespaces: list[PythonNamespace]
''' '''
def __init__(self, name: str, sub_namespaces: List[PythonNamespace]) -> None: def __init__(
self,
name: str,
sub_namespaces: List[PythonNamespace]
) -> None:
assert_that_is_instance(sub_namespaces, list) assert_that_is_instance(sub_namespaces, list)
super().__init__(name) super().__init__(name)
@@ -94,7 +111,10 @@ class PythonPackageNamespace(PythonNamespace):
else: else:
return sub_namespace return sub_namespace
def _sub_namespaces_names(self, package: PythonPackageNamespace) -> PythonPackageNamespaceDictType: def _sub_namespaces_names(
self,
package: PythonPackageNamespace
) -> PythonPackageNamespaceDictType:
''' '''
Returns the names of all sub-namespaces of the specified package Returns the names of all sub-namespaces of the specified package
namespace and keeps the package structure. namespace and keeps the package structure.
@@ -107,7 +127,11 @@ class PythonPackageNamespace(PythonNamespace):
''' '''
return { return {
sn.name: self._sub_namespaces_names(sn) if isinstance(sn, PythonPackageNamespace) else sn.name sn.name: (
self._sub_namespaces_names(sn)
if isinstance(sn, PythonPackageNamespace)
else sn.name
)
for sn in package._sub_namespaces for sn in package._sub_namespaces
} }
@@ -151,7 +175,10 @@ class PythonPackageNamespace(PythonNamespace):
if sub.name == name: if sub.name == name:
return sub return sub
raise NamespaceNotFoundError('no such namespace', namespace_identifier = name) raise NamespaceNotFoundError(
'no such namespace',
namespace_identifier = name
)
def add(self, namespace: PythonNamespace) -> None: def add(self, namespace: PythonNamespace) -> None:
''' '''
@@ -166,7 +193,10 @@ class PythonPackageNamespace(PythonNamespace):
''' '''
try: try:
self.__getitem__(namespace.name) self.__getitem__(namespace.name)
raise NamespaceExistsError('namespace already exists', namespace_identifier = namespace.name) raise NamespaceExistsError(
'namespace already exists',
namespace_identifier = namespace.name
)
except NamespaceNotFoundError: except NamespaceNotFoundError:
self._sub_namespaces.append(namespace) self._sub_namespaces.append(namespace)
+36 -4
View File
@@ -24,7 +24,15 @@ class NamespaceError(ValueError):
if not self.args: if not self.args:
return '' return ''
else: else:
return f'{self.args[0]}{": " if self.namespace_identifier and self.args[0] else ""}{self.namespace_identifier if self.args[0] else ""}' return f'{self.args[0]}{
": "
if self.namespace_identifier and self.args[0]
else ""
}{
self.namespace_identifier
if self.args[0]
else ""
}'
class InvalidNamespaceError(NamespaceError): ... class InvalidNamespaceError(NamespaceError): ...
class NamespaceNotFoundError(NamespaceError): ... class NamespaceNotFoundError(NamespaceError): ...
@@ -42,7 +50,15 @@ class PythonIdentifierError(ValueError):
if not self.args: if not self.args:
return '' return ''
else: else:
return f'{self.args[0]}{": " if self.identifier and self.args[0] else ""}{self.identifier if self.args[0] else ""}' return f'{self.args[0]}{
": "
if self.identifier and self.args[0]
else ""
}{
self.identifier
if self.args[0]
else ""
}'
class InvalidPythonIdentifierError(PythonIdentifierError): ... class InvalidPythonIdentifierError(PythonIdentifierError): ...
@@ -58,7 +74,15 @@ class PythonAnnotationError(ValueError):
if not self.args: if not self.args:
return '' return ''
else: else:
return f'{self.args[0]}{": " if self.annotation and self.args[0] else ""}{self.annotation if self.args[0] else ""}' return f'{self.args[0]}{
": "
if self.annotation and self.args[0]
else ""
}{
self.annotation
if self.args[0]
else ""
}'
class InvalidPythonAnnotationError(PythonAnnotationError): ... class InvalidPythonAnnotationError(PythonAnnotationError): ...
@@ -74,6 +98,14 @@ class PythonDefinitionError(ValueError):
if not self.args: if not self.args:
return '' return ''
else: else:
return f'{self.args[0]}{": " if self.definition and self.args[0] else ""}{self.definition if self.args[0] else ""}' return f'{self.args[0]}{
": "
if self.definition and self.args[0]
else ""
}{
self.definition
if self.args[0]
else ""
}'
class PythonArgumentStructureError(PythonDefinitionError): ... class PythonArgumentStructureError(PythonDefinitionError): ...
+42 -9
View File
@@ -38,7 +38,11 @@ class ExistingDirectory(pathlib.Path):
''' '''
def __init__(self, path, *args, **kwargs) -> None: def __init__(self, path, *args, **kwargs) -> None:
if not os.path.exists(path): if not os.path.exists(path):
raise FileNotFoundError(errno.ENOENT, 'no such file or directory', path) raise FileNotFoundError(
errno.ENOENT,
'no such file or directory',
path
)
if not os.path.isdir(path): if not os.path.isdir(path):
raise NotADirectoryError(errno.ENOTDIR, 'not a directory', path) raise NotADirectoryError(errno.ENOTDIR, 'not a directory', path)
super().__init__(*(path, *args), **kwargs) super().__init__(*(path, *args), **kwargs)
@@ -54,7 +58,11 @@ class ExistingFile(pathlib.Path):
''' '''
def __init__(self, path, *args, **kwargs) -> None: def __init__(self, path, *args, **kwargs) -> None:
if not os.path.exists(path): if not os.path.exists(path):
raise FileNotFoundError(errno.ENOENT, 'no such file or directory', path) raise FileNotFoundError(
errno.ENOENT,
'no such file or directory',
path
)
if not os.path.isfile(path): if not os.path.isfile(path):
raise IsADirectoryError(errno.EISDIR, 'is a directory', path) raise IsADirectoryError(errno.EISDIR, 'is a directory', path)
super().__init__(*(path, *args), **kwargs) super().__init__(*(path, *args), **kwargs)
@@ -74,7 +82,11 @@ def _quote(string: str, quotation_mark: str) -> str:
return quotation_mark + string + quotation_mark return quotation_mark + string + quotation_mark
def human_readable_list(ls: list, final_separator: str = 'and', quotation_mark: str = '') -> str: def human_readable_list(
ls: list,
final_separator: str = 'and',
quotation_mark: str = ''
) -> str:
''' '''
Generates a human-readable list from a list. Generates a human-readable list from a list.
@@ -101,7 +113,10 @@ def human_readable_list(ls: list, final_separator: str = 'and', quotation_mark:
return ', '.join( return ', '.join(
[ [
_quote(str(obj), quotation_mark) # quote the string representation of the object _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 for obj in ls[:-1] # do not include the last element of the list
] ]
) + ' ' + final_separator + ' ' + _quote(str(ls[-1]), quotation_mark) ) + ' ' + final_separator + ' ' + _quote(str(ls[-1]), quotation_mark)
@@ -120,9 +135,19 @@ def _list_type_names(types: list[type]) -> list[str]:
return [tp.__name__ for tp in types] return [tp.__name__ for tp in types]
def assert_that_is_instance(obj: object, class_or_tuple: Union[type, types.UnionType, tuple[type, ...]]) -> None: def assert_that_is_instance(
obj: object,
class_or_tuple: Union[
type,
types.UnionType,
tuple[type, ...]
]
) -> None:
if not isinstance(class_or_tuple, Union[type, types.UnionType, tuple]): if not isinstance(class_or_tuple, Union[type, types.UnionType, tuple]):
raise TypeError(f'class_or_tuple: expected \'Union[type, types.UnionType, tuple[type, ...]]\', \'got {type(class_or_tuple).__name__}\'') raise TypeError(
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 not isinstance(obj, class_or_tuple):
if isinstance(class_or_tuple, (tuple, types.UnionType)): if isinstance(class_or_tuple, (tuple, types.UnionType)):
if isinstance(class_or_tuple, types.UnionType): if isinstance(class_or_tuple, types.UnionType):
@@ -131,10 +156,15 @@ def assert_that_is_instance(obj: object, class_or_tuple: Union[type, types.Union
exception_message_expected = 'either ' exception_message_expected = 'either '
else: else:
exception_message_expected = '' exception_message_expected = ''
exception_message_expected += human_readable_list(_list_type_names(class_or_tuple), 'or', '\'') exception_message_expected += human_readable_list(
_list_type_names(class_or_tuple), 'or', '\'')
else: 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__}\'') raise TypeError(
f'expected {exception_message_expected}, got \'{type(
obj
).__name__}\''
)
def non_empty_str(value: Union[str, None]) -> Union[str, None]: def non_empty_str(value: Union[str, None]) -> Union[str, None]:
''' '''
@@ -160,7 +190,10 @@ def non_empty_str(value: Union[str, None]) -> Union[str, None]:
return value return value
def get_relative_path_segments(path: pathlib.Path, root: pathlib.Path) -> list[str]: def get_relative_path_segments(
path: pathlib.Path,
root: pathlib.Path
) -> list[str]:
''' '''
Returns the path segments relative to ``root``. Returns the path segments relative to ``root``.
+3 -1
View File
@@ -16,7 +16,9 @@ import pathlib
import pytest import pytest
from jcloud_docsgen.cli.cli import create_argument_parser, main as cli_main 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.cli.languages.python import parse_sections_list
from jcloud_docsgen.core.python import PythonDefinitionDocumentationIncludeSections from jcloud_docsgen.core.python import (
PythonDefinitionDocumentationIncludeSections
)
@pytest.mark.parametrize('include,exclude,expected', [ @pytest.mark.parametrize('include,exclude,expected', [
( (
File diff suppressed because it is too large. Load diff
@@ -13,28 +13,98 @@
# limitations under the License. # limitations under the License.
from src.jcloud_docsgen.core.python import PythonDocumentationGenerator from src.jcloud_docsgen.core.python import PythonDocumentationGenerator
from src.jcloud_docsgen.core.python.namespaces import PythonModuleNamespace, PythonPackageNamespace from src.jcloud_docsgen.core.python.namespaces import (
PythonModuleNamespace,
PythonPackageNamespace
)
import pytest import pytest
import pathlib import pathlib
@pytest.mark.parametrize('python_documentation_generator,expected', [ @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')), [PythonPackageNamespace('pkg', [PythonModuleNamespace('module')])]), PythonDocumentationGenerator(
(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')])])]), 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'
)
),
[
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'
)
),
[
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace('module'),
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace(
'module2'
)
]
)
]
)
]
),
]) ])
def test_PythonDocumentationGenerator_collect_modules(python_documentation_generator: PythonDocumentationGenerator, expected): def test_PythonDocumentationGenerator_collect_modules(
python_documentation_generator: PythonDocumentationGenerator,
expected
):
assert python_documentation_generator.namespace() == expected assert python_documentation_generator.namespace() == expected
@pytest.mark.parametrize('dir', [ @pytest.mark.parametrize('dir', [
d.name d.name
for d in pathlib.Path('tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/').iterdir() for d in pathlib.Path(
(
'tests/unit/core/python/_core/test_PythonDocume'
'ntationGenerator/test_generate_documentation/'
)
).iterdir()
]) ])
def test_generate_documentation(dir, expectdir): def test_generate_documentation(dir, expectdir):
with expectdir('test_PythonDocumentationGenerator/test_generate_documentation/' + dir) as d: with expectdir(
'test_PythonDocumentationGenerator/test_generate_documentation/' + dir
) as d:
PythonDocumentationGenerator( PythonDocumentationGenerator(
pathlib.Path( pathlib.Path(
str( str(
pathlib.Path('tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/') / pathlib.Path(
(
'tests/unit/core/python/_core/test_PythonDocume'
'ntationGenerator/test_generate_documentation/'
)
) /
dir / dir /
'project' 'project'
) )
@@ -42,5 +112,4 @@ def test_generate_documentation(dir, expectdir):
pathlib.Path(str(d)), pathlib.Path(str(d)),
allow_tables = True, allow_tables = True,
allow_html = False allow_html = False
).generate_documentation( ).generate_documentation() # simplify the tests
) # simplify the tests
@@ -12,7 +12,13 @@ class UserService:
def __init__(self, storage: InMemoryUserStorage): def __init__(self, storage: InMemoryUserStorage):
self.storage = storage self.storage = storage
def create_user(self, user_id: str, name: str, email: str, age: int | None = None) -> User: def create_user(
self,
user_id: str,
name: str,
email: str,
age: int | None = None
) -> User:
""" """
Creates and stores a new user. Creates and stores a new user.
""" """
@@ -17,29 +17,223 @@ from src.jcloud_docsgen.exceptions import InvalidNamespaceError
import pathlib import pathlib
import pytest import pytest
@pytest.mark.parametrize('module_path,src_directory,expected_exception,expected_exception_msg', [ @pytest.mark.parametrize(
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c'), 'module_path,src_directory,expected_exception,expected_exception_msg',
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d'), [
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e'), (
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f'), pathlib.Path(
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f.g'), (
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.h'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f.g.h'), 'tests/unit/core/python/_core/fil'
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.h.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f.g.h.py'), 'es_with_more_than_one_dot/a.b.c'
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f.g.py'), )
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.f.py'), ),
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.e.py'), None,
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.d.py'), InvalidNamespaceError,
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.c.py'), 'namespace identifier cannot contain more than one dot: a.b.c'
(pathlib.Path('tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.py'), ),
(pathlib.Path('tests/unit/core/python/_core/directory.with.more.than.one.dot/a.b.py'), None, InvalidNamespaceError, 'namespace identifier cannot contain more than one dot: a.b.py'), (
]) pathlib.Path(
def test_PythonModuleDocumentationGenerator_exceptions(module_path, src_directory, expected_exception, expected_exception_msg): (
'tests/unit/core/python/_core/file'
's_with_more_than_one_dot/a.b.c.d'
)
),
None,
InvalidNamespaceError,
'namespace identifier cannot contain more than one dot: a.b.c.d'
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/files'
'_with_more_than_one_dot/a.b.c.d.e'
)
),
None,
InvalidNamespaceError,
(
'namespace identifier cannot cont'
'ain more than one dot: a.b.c.d.e'
)
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/files_'
'with_more_than_one_dot/a.b.c.d.e.f'
)
),
None,
InvalidNamespaceError,
(
'namespace identifier cannot conta'
'in more than one dot: a.b.c.d.e.f'
)
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/files_w'
'ith_more_than_one_dot/a.b.c.d.e.f.g'
)
),
None,
InvalidNamespaceError,
(
'namespace identifier cannot contai'
'n more than one dot: a.b.c.d.e.f.g'
)
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/files_wi'
'th_more_than_one_dot/a.b.c.d.e.f.g.h'
)
),
None,
InvalidNamespaceError,
(
'namespace identifier cannot contain'
' more than one dot: a.b.c.d.e.f.g.h'
)
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/files_wit'
'h_more_than_one_dot/a.b.c.d.e.f.g.h.py'
)
),
None,
InvalidNamespaceError,
(
'namespace identifier cannot contain m'
'ore than one dot: a.b.c.d.e.f.g.h.py'
)
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/files_wi'
'th_more_than_one_dot/a.b.c.d.e.f.g.py'
)
),
None,
InvalidNamespaceError,
(
'namespace identifier cannot contain '
'more than one dot: a.b.c.d.e.f.g.py'
)
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/files_w'
'ith_more_than_one_dot/a.b.c.d.e.f.py'
)
),
None,
InvalidNamespaceError,
(
'namespace identifier cannot contain'
' more than one dot: a.b.c.d.e.f.py'
)
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/files_'
'with_more_than_one_dot/a.b.c.d.e.py'
)
),
None,
InvalidNamespaceError,
(
'namespace identifier cannot contai'
'n more than one dot: a.b.c.d.e.py'
)
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/files'
'_with_more_than_one_dot/a.b.c.d.py'
)
),
None,
InvalidNamespaceError,
(
'namespace identifier cannot conta'
'in more than one dot: a.b.c.d.py'
)
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/file'
's_with_more_than_one_dot/a.b.c.py'
)
),
None,
InvalidNamespaceError,
(
'namespace identifier cannot cont'
'ain more than one dot: a.b.c.py'
)
),
(
pathlib.Path(
(
(
'tests/unit/core/python/_core/fil'
'es_with_more_than_one_dot/a.b.py'
)
)
),
None,
InvalidNamespaceError,
'namespace identifier cannot contain more than one dot: a.b.py'
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/direc'
'tory.with.more.than.one.dot/a.b.py'
)
),
None,
InvalidNamespaceError,
'namespace identifier cannot contain more than one dot: a.b.py'
),
]
)
def test_PythonModuleDocumentationGenerator_exceptions(
module_path,
src_directory,
expected_exception,
expected_exception_msg
):
with pytest.raises(expected_exception) as exc_info: with pytest.raises(expected_exception) as exc_info:
PythonModuleDocumentationGenerator(module_path, src_directory) PythonModuleDocumentationGenerator(module_path, src_directory)
assert str(exc_info.value) == expected_exception_msg assert str(exc_info.value) == expected_exception_msg
@pytest.mark.parametrize('module_documentation_generator,expected', [ @pytest.mark.parametrize('module_documentation_generator,expected', [
(PythonModuleDocumentationGenerator(pathlib.Path('tests/unit/core/python/_core/test_project_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), '''# Module `pkg.module` (
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
),
'''# Module `pkg.module`
A test module. A test module.
@@ -206,9 +400,22 @@ Barks.
### Docstring ### Docstring
A dog.'''), A dog.'''
),
(PythonModuleDocumentationGenerator(pathlib.Path('tests/unit/core/python/_core/test_project_dirs/pdir_2/src/pkg/module.py'), None, allow_tables = True, 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 = True,
allow_html = False
),
'''# Module `module`
A test module. A test module.
@@ -375,9 +582,23 @@ Barks.
### Docstring ### Docstring
A dog.'''), A dog.'''
),
(PythonModuleDocumentationGenerator(pathlib.Path('tests/unit/core/python/_core/test_project_dirs/pdir_2/src/pkg/module.py'), None, allow_tables = True, 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 = True,
allow_html = True
),
(
'''# Module `module`
A test module. A test module.
@@ -411,7 +632,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. :param a: The first number.
:type a: int | float :type a: int | float
@@ -478,7 +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. :return: The greeting.
:rtype: str</details> :rtype: str</details>
@@ -487,7 +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. :param name: Its name.
:type name: str</details> :type name: str</details>
@@ -528,15 +752,32 @@ 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>'''), <details><summary><h3 style="display:inline">Docstring</h3></summary>A dog.'''
'</details>'
)
),
(PythonModuleDocumentationGenerator(pathlib.Path('tests/unit/core/python/_core/test_project_dirs/pdir_2/src/pkg/module.py'), 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. A test module.
@@ -585,7 +826,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. :param a: The first number.
:type a: int | float :type a: int | float
@@ -673,7 +915,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. :return: The greeting.
:rtype: str</details> :rtype: str</details>
@@ -682,7 +925,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. :param name: Its name.
:type name: str</details> :type name: str</details>
@@ -729,15 +973,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>'''), <details><summary><h3 style="display:inline">Docstring</h3></summary>A '''
'dog.</details>'
)
),
(PythonModuleDocumentationGenerator(pathlib.Path('tests/unit/core/python/_core/test_project_dirs/pdir_2/src/pkg/module.py'), 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. A test module.
@@ -946,7 +1206,11 @@ Barks.
### Docstring ### Docstring
A dog.'''), A dog.'''
),
]) ])
def test_PythonModuleDocumentationGenerator_exceptions(module_documentation_generator, expected): def test_PythonModuleDocumentationGenerator_exceptions(
module_documentation_generator,
expected
):
assert module_documentation_generator.generate_documentation() == expected assert module_documentation_generator.generate_documentation() == expected
@@ -13,8 +13,14 @@
# limitations under the License. # limitations under the License.
from src.jcloud_docsgen.core.python._core import _collect_definitions from src.jcloud_docsgen.core.python._core import _collect_definitions
from src.jcloud_docsgen.core.python.definitions import PythonClassDefinition, PythonFunctionDefinition from src.jcloud_docsgen.core.python.definitions import (
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind PythonClassDefinition,
PythonFunctionDefinition
)
from src.jcloud_docsgen.core.python.arguments import (
PythonFunctionArgument,
PythonArgumentKind
)
from src.jcloud_docsgen.core.python.docstrings import PythonDocstring from src.jcloud_docsgen.core.python.docstrings import PythonDocstring
import pytest import pytest
import ast import ast
@@ -33,13 +39,33 @@ def module(source_code: str) -> ast.Module:
return ast.parse(source_code) return ast.parse(source_code)
@pytest.mark.parametrize('module,expected', [ @pytest.mark.parametrize('module,expected', [
(module('def add(a, b):\n\treturn a + b'), [ (
PythonFunctionDefinition('add', [ module('def add(a, b):\n\treturn a + b'),
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None), [
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None), PythonFunctionDefinition(
], None, None, []) 'add',
]), [
(module('''def add(a: int | float, b: int | float) -> int | float: 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.""" """Adds two numbers."""
return a + b return a + b
@@ -62,26 +88,94 @@ class Something:
Returns the value. Returns the value.
""" """
return self._value return self._value
'''), [ '''),
PythonFunctionDefinition('add', [ [
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int | float'), PythonFunctionDefinition(
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int | float'), 'add',
], 'int | float', PythonDocstring('Adds two numbers.'), []), [
PythonClassDefinition('Something', [], PythonDocstring('A class.'), [], [ PythonFunctionArgument(
PythonFunctionDefinition('__init__', [ 'a',
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None), PythonArgumentKind.NORMAL,
PythonFunctionArgument('value', PythonArgumentKind.NORMAL, '42', 'typing.Any'), None,
], 'None', None, []), 'int | float'
PythonFunctionDefinition('value', [ ),
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None), PythonFunctionArgument(
], 'typing.Any', PythonDocstring('The value.'), ['property']), 'b',
PythonFunctionDefinition('get_value', [ PythonArgumentKind.NORMAL,
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None), None,
], 'typing.Any', PythonDocstring('Returns the value.'), []), 'int | float'
]) ),
]), ],
(module('''from src.jcloud_docsgen.core.python import PythonDocumentationGenerator 'int | float',
from src.jcloud_docsgen.core.python.namespaces import PythonModuleNamespace, PythonPackageNamespace PythonDocstring('Adds two numbers.'),
[]
),
PythonClassDefinition(
'Something',
[],
PythonDocstring('A class.'),
[],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None
),
PythonFunctionArgument(
'value',
PythonArgumentKind.NORMAL,
'42',
'typing.Any'
),
],
'None',
None,
[]
),
PythonFunctionDefinition(
'value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None
),
],
'typing.Any',
PythonDocstring('The value.'),
['property']
),
PythonFunctionDefinition(
'get_value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None
),
],
'typing.Any',
PythonDocstring('Returns the value.'),
[]
),
]
)
]
),
(
module('''from src.jcloud_docsgen.core.python import (
PythonDocumentationGenerator
)
from src.jcloud_docsgen.core.python.namespaces import (
PythonModuleNamespace,
PythonPackageNamespace
)
import pytest import pytest
@pytest.mark.parametrize('project_directory,docs_directory', [ @pytest.mark.parametrize('project_directory,docs_directory', [
@@ -94,45 +188,186 @@ import pytest
(1, pathlib.Path('tests')), (1, pathlib.Path('tests')),
(None, pathlib.Path('tests')), (None, pathlib.Path('tests')),
]) ])
def test_PythonDocumentationGenerator_type_exceptions(project_directory, docs_directory): def test_PythonDocumentationGenerator_type_exceptions(
project_directory,
docs_directory
):
with pytest.raises(TypeError): with pytest.raises(TypeError):
PythonDocumentationGenerator(project_directory, docs_directory) PythonDocumentationGenerator(project_directory, docs_directory)
@pytest.mark.parametrize('python_documentation_generator,expected', [ @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(
(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')])]), pathlib.Path(
(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')])])]), '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'
)
), [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'
)
), [
PythonPackageNamespace(
'pkg',
[
PythonModuleNamespace('module'),
PythonPackageNamespace('pkg',
[PythonModuleNamespace('module2')]
)
]
)
])
]) ])
def test_PythonDocumentationGenerator_collect_modules(python_documentation_generator: PythonDocumentationGenerator, expected): def test_PythonDocumentationGenerator_collect_modules(
assert python_documentation_generator.namespace() == expected'''), [ python_documentation_generator: PythonDocumentationGenerator,
PythonFunctionDefinition('test_PythonDocumentationGenerator_type_exceptions', [ expected
PythonFunctionArgument('project_directory', PythonArgumentKind.NORMAL, None, None), ):
PythonFunctionArgument('docs_directory', PythonArgumentKind.NORMAL, None, None), assert python_documentation_generator.namespace() == expected'''),
], None, None, ['''pytest.mark.parametrize('project_directory,docs_directory', [(1, 1), (1, None), (42, None), (pathlib.Path('tests'), 1), (pathlib.Path('tests/'), 1), (1, pathlib.Path('tests/')), (1, pathlib.Path('tests')), (None, pathlib.Path('tests'))])''']), [
PythonFunctionDefinition('test_PythonDocumentationGenerator_collect_modules', [ PythonFunctionDefinition(
PythonFunctionArgument('python_documentation_generator', PythonArgumentKind.NORMAL, None, 'PythonDocumentationGenerator'), 'test_PythonDocumentationGenerator_type_exceptions',
PythonFunctionArgument('expected', PythonArgumentKind.NORMAL, None, None) [
], None, None, ['''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')), [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')), [PythonPackageNamespace('pkg', [PythonModuleNamespace('module'), PythonPackageNamespace('pkg', [PythonModuleNamespace('module2')])])])])''']) PythonFunctionArgument(
]), 'project_directory',
(module('''class C1: 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 C2:
class C3: class C3:
def function(self, value: bool, /, arg) -> bool: def function(self, value: bool, /, arg) -> bool:
def should_be_not_in_the_result(arg: int) -> object: def should_be_not_in_the_result(arg: int) -> object:
pass pass
return not value'''), [ return not value'''
PythonClassDefinition('C1', [], None, [], [ ),
PythonClassDefinition('C2', [], None, [], [ [
PythonClassDefinition('C3', [], None, [], [ PythonClassDefinition(
PythonFunctionDefinition('function', [ 'C1',
PythonFunctionArgument('self', PythonArgumentKind.POSITIONAL_ONLY, None, None), [],
PythonFunctionArgument('value', PythonArgumentKind.POSITIONAL_ONLY, None, 'bool'), None,
PythonFunctionArgument('arg', PythonArgumentKind.NORMAL, None, None), [],
], 'bool', 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): def test___collect_definitions(module, expected):
assert list(_collect_definitions(module)) == expected assert list(_collect_definitions(module)) == expected
@@ -12,12 +12,20 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from src.jcloud_docsgen.core.python._core import _function_params_info, _FunctionParamInfo from src.jcloud_docsgen.core.python._core import (
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind _function_params_info,
_FunctionParamInfo
)
from src.jcloud_docsgen.core.python.arguments import (
PythonFunctionArgument,
PythonArgumentKind
)
import pytest import pytest
import docstring_parser import docstring_parser
def docstring_args_list(docstring: str) -> list[docstring_parser.DocstringParam]: def docstring_args_list(
docstring: str
) -> list[docstring_parser.DocstringParam]:
''' '''
Returns the parameter list of a docstring. Returns the parameter list of a docstring.
@@ -101,4 +109,7 @@ def docstring_args_list(docstring: str) -> list[docstring_parser.DocstringParam]
([], '', {}), ([], '', {}),
]) ])
def test__function_params_types(arg_list, docstring, expected): 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,79 +15,406 @@
from src.jcloud_docsgen.core.python._core import _markdown_table from src.jcloud_docsgen.core.python._core import _markdown_table
import pytest import pytest
@pytest.mark.parametrize('header,body,allow_markdown_table,force_row_lengths,one_line_values,missing_value,expected', [ @pytest.mark.parametrize(
([], [], False, False, True, '--', ''), (
([], [('a',)], False, False, True, '--', ''), 'header,body,allow_markdown_table,force_row_le'
(['a'], [], False, False, True, '--', ''), 'ngths,one_line_values,missing_value,expected'
(['a'], [(1,)], True, False, True, '--', '''| a | ),
[
(
[],
[],
False,
False,
True,
'--',
''
),
(
[],
[('a',)],
False,
False,
True,
'--',
''
),
(
['a'],
[],
False,
False,
True,
'--',
''
),
(
['a'],
[(1,)],
True,
False,
True,
'--',
'''| a |
| --- | | --- |
| 1 |'''), | 1 |'''
(['a'], [(1, 2)], True, False, True, '--', '''| a | ),
(
['a'],
[(1, 2)],
True,
False,
True,
'--',
'''| a |
| --- | | --- |
| 1 |'''), | 1 |'''
(['a', 'b'], [(1, 2)], True, False, True, '--', '''| a | b | ),
(
['a', 'b'],
[(1, 2)],
True,
False,
True,
'--',
'''| a | b |
| --- | --- | | --- | --- |
| 1 | 2 |'''), | 1 | 2 |'''
(['a', 'b'], [(1,)], True, False, True, '--', '''| a | b | ),
(
['a', 'b'],
[(1,)],
True,
False,
True,
'--',
'''| a | b |
| --- | --- | | --- | --- |
| 1 | -- |'''), | 1 | -- |'''
(['a', 'b'], [()], True, False, True, '--', '''| a | b | ),
(
['a', 'b'],
[()],
True,
False,
True,
'--',
'''| a | b |
| --- | --- | | --- | --- |
| -- | -- |'''), | -- | -- |'''
(['a', 'b'], [(1, 2), (3, 4)], True, False, True, '--', '''| a | b | ),
(
['a', 'b'],
[(1, 2), (3, 4)],
True,
False,
True,
'--',
'''| a | b |
| --- | --- | | --- | --- |
| 1 | 2 | | 1 | 2 |
| 3 | 4 |'''), | 3 | 4 |'''
(['a'], [('line1\nline2',)], True, False, True, '--', '''| a | ),
(
['a'],
[('line1\nline2',)],
True,
False,
True,
'--',
'''| a |
| --- | | --- |
| line1 line2 |'''), | line1 line2 |'''
(['a'], [('line1\nline2',)], True, False, False, '--', '''| a | ),
(
['a'],
[('line1\nline2',)],
True,
False,
False,
'--',
'''| a |
| --- | | --- |
| line1\nline2 |'''), | line1\nline2 |'''
),
([], [], False, False, True, '--', ''), (
([], [('a',)], False, False, True, '--', ''), [],
(['a'], [], False, False, True, '--', ''), [],
(['a'], [(1,)], False, False, True, '--', '''- a: 1'''), False,
(['a'], [(1, 2)], False, False, True, '--', '''- a: 1'''), False,
(['a', 'b'], [(1, 2)], False, False, True, '--', '''- a: 1 True,
'--',
''
),
(
[],
[('a',)],
False,
False,
True,
'--',
''
),
(
['a'],
[],
False,
False,
True,
'--',
''
),
(
['a'],
[(1,)],
False,
False,
True,
'--',
'- a: 1'
),
(
['a'],
[(1, 2)],
False,
False,
True,
'--',
'- a: 1'
),
(
['a', 'b'],
[(1, 2)],
False,
False,
True,
'--',
'''- a: 1
b: 2'''), b: 2'''
(['a', 'b'], [(1,)], False, False, True, '--', '''- a: 1 ),
(
['a', 'b'],
[(1,)],
False,
False,
True,
'--',
'''- a: 1
b: --'''), b: --'''
(['a', 'b'], [()], False, False, True, '--', '''- a: -- ),
(
['a', 'b'],
[()],
False,
False,
True,
'--',
'''- a: --
b: --'''), b: --'''
(['a', 'b'], [(1, 2), (3, 4)], False, False, True, '--', '''- a: 1 ),
(
['a', 'b'],
[(1, 2), (3, 4)],
False,
False,
True,
'--',
'''- a: 1
b: 2 b: 2
- a: 3 - a: 3
b: 4'''), b: 4'''
(['a'], [('line1\nline2',)], False, False, True, '--', '- a: line1 line2'), ),
(['a'], [('line1\nline2',)], False, False, False, '--', '- a: line1\nline2'), (
]) ['a'],
def test__markdown_table(header, body, allow_markdown_table, force_row_lengths, one_line_values, missing_value, expected): [('line1\nline2',)],
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 False,
False,
True,
'--',
'- a: line1 line2'
),
(
['a'],
[('line1\nline2',)],
False,
False,
False,
'--',
'- a: line1\nline2'
)
]
)
def test__markdown_table(
header,
body,
allow_markdown_table,
force_row_lengths,
one_line_values,
missing_value,
expected
):
assert _markdown_table(
header,
body,
allow_markdown_table = allow_markdown_table,
force_row_lengths = force_row_lengths,
one_line_values = one_line_values,
missing_value = missing_value
) == expected
@pytest.mark.parametrize('header,body,allow_markdown_table,force_row_lengths,one_line_values,missing_value,expected_exception,expected_exception_msg', [ @pytest.mark.parametrize(
(['a'], [(1, 2)], True, True, True, '--', ValueError, 'expected 1 value, got 2'), (
(['a'], [(1, 2, 3)], True, True, True, '--', ValueError, 'expected 1 value, got 3'), 'header,body,allow_markdown_table,force_row_lengths,one_line_v'
(['a'], [()], True, True, True, '--', ValueError, 'expected 1 value, got 0'), 'alues,missing_value,expected_exception,expected_exception_msg'
(['a', 'b'], [()], True, True, True, '--', ValueError, 'expected 2 values, got 0'), ),
(['a', 'b'], [(1,)], True, True, True, '--', ValueError, 'expected 2 values, got 1'), [
(['a', 'b'], [(1, 2, 3)], True, True, True, '--', ValueError, 'expected 2 values, got 3'), (
['a'],
[(1, 2)],
True,
True,
True,
'--',
ValueError,
'expected 1 value, got 2'
),
(
['a'],
[(1, 2, 3)],
True,
True,
True,
'--',
ValueError,
'expected 1 value, got 3'
),
(
['a'],
[()],
True,
True,
True,
'--',
ValueError,
'expected 1 value, got 0'
),
(
['a', 'b'],
[()],
True,
True,
True,
'--',
ValueError,
'expected 2 values, got 0'
),
(
['a', 'b'],
[(1,)],
True,
True,
True,
'--',
ValueError,
'expected 2 values, got 1'
),
(
['a', 'b'],
[(1, 2, 3)],
True,
True,
True,
'--',
ValueError,
'expected 2 values, got 3'
),
(['a'], [(1, 2)], False, True, True, '--', ValueError, 'expected 1 value, got 2'), (
(['a'], [(1, 2, 3)], False, True, True, '--', ValueError, 'expected 1 value, got 3'), ['a'],
(['a'], [()], False, True, True, '--', ValueError, 'expected 1 value, got 0'), [(1, 2)],
(['a', 'b'], [()], False, True, True, '--', ValueError, 'expected 2 values, got 0'), False,
(['a', 'b'], [(1,)], False, True, True, '--', ValueError, 'expected 2 values, got 1'), True,
(['a', 'b'], [(1, 2, 3)], False, True, True, '--', ValueError, 'expected 2 values, got 3'), True,
]) '--',
def test__markdown_table_exceptions(header, body, allow_markdown_table, force_row_lengths, one_line_values, missing_value, expected_exception, expected_exception_msg): ValueError,
'expected 1 value, got 2'
),
(
['a'],
[(1, 2, 3)],
False,
True,
True,
'--',
ValueError,
'expected 1 value, got 3'
),
(
['a'],
[()],
False,
True,
True,
'--',
ValueError,
'expected 1 value, got 0'
),
(
['a', 'b'],
[()],
False,
True,
True,
'--',
ValueError,
'expected 2 values, got 0'
),
(
['a', 'b'],
[(1,)],
False,
True,
True,
'--',
ValueError,
'expected 2 values, got 1'
),
(
['a', 'b'],
[(1, 2, 3)],
False,
True,
True,
'--',
ValueError,
'expected 2 values, got 3'
)
]
)
def test__markdown_table_exceptions(
header,
body,
allow_markdown_table,
force_row_lengths,
one_line_values,
missing_value,
expected_exception,
expected_exception_msg
):
with pytest.raises(expected_exception) as exc_info: with pytest.raises(expected_exception) as exc_info:
_markdown_table(header, body, allow_markdown_table = allow_markdown_table, force_row_lengths = force_row_lengths, one_line_values = one_line_values, missing_value = missing_value) _markdown_table(
header,
body,
allow_markdown_table = allow_markdown_table,
force_row_lengths = force_row_lengths,
one_line_values = one_line_values,
missing_value = missing_value
)
assert str(exc_info.value) == expected_exception_msg assert str(exc_info.value) == expected_exception_msg
@@ -13,84 +13,271 @@
# limitations under the License. # limitations under the License.
from src.jcloud_docsgen.core.python._core import _signature_argument_list from src.jcloud_docsgen.core.python._core import _signature_argument_list
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind from src.jcloud_docsgen.core.python.arguments import (
PythonFunctionArgument,
PythonArgumentKind
)
import pytest import pytest
@pytest.mark.parametrize('argument_list,expected', [ @pytest.mark.parametrize('argument_list,expected', [
([ ([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None) 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(
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None), 'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None
)
], ['a', '/', 'b']), ], ['a', '/', 'b']),
([ ([
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None), PythonFunctionArgument(
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None), 'a',
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None), PythonArgumentKind.POSITIONAL_ONLY,
None,
None
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
None
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None
)
], ['a', '/', 'b', '*', 'c']), ], ['a', '/', 'b', '*', 'c']),
([ ([
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None), PythonFunctionArgument(
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None), 'b',
PythonArgumentKind.NORMAL,
None,
None
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None
)
], ['b', '*', 'c']), ], ['b', '*', 'c']),
([ ([
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None), PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None
)
], ['*', 'c']), ], ['*', 'c']),
([ ([
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None), PythonFunctionArgument(
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None), 'b',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None
),
PythonFunctionArgument(
'c',
PythonArgumentKind.KEYWORD_ONLY,
None,
None
)
], ['b', '/', '*', 'c']), ], ['b', '/', '*', 'c']),
([ ([
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None), PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None
)
], ['*args']), ], ['*args']),
([ ([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None), PythonFunctionArgument(
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None), 'a',
PythonArgumentKind.NORMAL,
None,
None
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None
)
], ['a', '*args']), ], ['a', '*args']),
([ ([
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None), PythonFunctionArgument(
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None), 'a', PythonArgumentKind.POSITIONAL_ONLY,
None,
None
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None
)
], ['a', '/', '*args']), ], ['a', '/', '*args']),
([ ([
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None), PythonFunctionArgument(
PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None), 'args',
PythonArgumentKind.VARARG,
None,
None
),
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None
)
], ['*args', 'a']), ], ['*args', 'a']),
([ ([
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None), PythonFunctionArgument(
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None), 'args',
PythonArgumentKind.VARARG,
None,
None
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None
)
], ['*args', '**kwargs']), ], ['*args', '**kwargs']),
([ ([
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None), PythonFunctionArgument(
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None), 'a',
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None), PythonArgumentKind.POSITIONAL_ONLY,
None,
None
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None
)
], ['a', '/', '*args', '**kwargs']), ], ['a', '/', '*args', '**kwargs']),
([ ([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None), PythonFunctionArgument(
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None), 'a',
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None), PythonArgumentKind.NORMAL,
None,
None
),
PythonFunctionArgument(
'args',
PythonArgumentKind.VARARG,
None,
None
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None
)
], ['a', '*args', '**kwargs']), ], ['a', '*args', '**kwargs']),
([ ([
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None), PythonFunctionArgument(
PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None), 'args',
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None), PythonArgumentKind.VARARG,
None,
None
),
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None
)
], ['*args', 'a', '**kwargs']), ], ['*args', 'a', '**kwargs']),
([ ([
PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None), PythonFunctionArgument(
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None), 'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None
)
], ['*', 'a', '**kwargs']), ], ['*', 'a', '**kwargs']),
([ ([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None), PythonFunctionArgument(
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None), 'a',
PythonArgumentKind.NORMAL,
None,
None
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None
)
], ['a', '**kwargs']), ], ['a', '**kwargs']),
([ ([
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None), PythonFunctionArgument(
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None), 'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None
),
PythonFunctionArgument(
'kwargs',
PythonArgumentKind.KWARGS,
None,
None
)
], ['a', '/', '**kwargs']), ], ['a', '/', '**kwargs']),
([ ([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '1', 'int'), PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
'int'
),
], ['a: int = 1']), ], ['a: int = 1']),
]) ])
def test__signature_argument_list(argument_list, expected): def test__signature_argument_list(argument_list, expected):
File diff suppressed because it is too large. Load diff
@@ -12,56 +12,374 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind from src.jcloud_docsgen.core.python.arguments import (
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError, InvalidPythonAnnotationError, PythonArgumentStructureError PythonFunctionArgument,
PythonArgumentKind
)
from src.jcloud_docsgen.exceptions import (
InvalidPythonIdentifierError,
InvalidPythonAnnotationError,
PythonArgumentStructureError
)
import pytest import pytest
@pytest.mark.parametrize('name,kind,default,annotation,expected_exception,expected_exception_msg', [ @pytest.mark.parametrize(
(None, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'NoneType\''), 'name,kind,default,annotation,expected_exception,expected_exception_msg',
(1, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'int\''), [
(42, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'int\''), (
(4.2, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'float\''), None,
(4.2, PythonArgumentKind.NORMAL, None, None, TypeError, 'expected \'str\', got \'float\''), PythonArgumentKind.NORMAL,
('a', PythonArgumentKind.NORMAL, 1, None, TypeError, 'expected either \'str\' or \'NoneType\', got \'int\''), None,
('a', PythonArgumentKind.NORMAL, None, 1, TypeError, 'expected either \'str\' or \'NoneType\', got \'int\''), None,
('a', 0, None, None, TypeError, 'expected \'PythonArgumentKind\', got \'int\''), TypeError,
('a', PythonArgumentKind.NORMAL, None, '', InvalidPythonAnnotationError, 'invalid annotation'), 'expected \'str\', got \'NoneType\''
('', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier'), ),
('.', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: .'), (
('#', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: #'), 1,
('1a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: 1a'), PythonArgumentKind.NORMAL,
('1_', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: 1_'), None,
('1,', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: 1,'), None,
(',', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ,'), TypeError,
(':', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: :'), 'expected \'str\', got \'int\''
('-', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: -'), ),
(';', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ;'), (
('|', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: |'), 42,
('<', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: <'), PythonArgumentKind.NORMAL,
('>', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: >'), None,
('a,', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a,'), None,
(',a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ,a'), TypeError,
('a:', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a:'), 'expected \'str\', got \'int\''
(':a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: :a'), ),
('a-', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a-'), (
('-a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: -a'), 4.2,
('a;', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a;'), PythonArgumentKind.NORMAL,
(';a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: ;a'), None,
('a|', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a|'), None,
('|a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: |a'), TypeError,
('a<', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a<'), 'expected \'str\', got \'float\''
('<a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: <a'), ),
('a>', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: a>'), (
('>a', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: >a'), 4.2,
('class', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: class'), PythonArgumentKind.NORMAL,
('def', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: def'), None,
('lambda', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: lambda'), None,
('None', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: None'), TypeError,
('True', PythonArgumentKind.NORMAL, None, None, InvalidPythonIdentifierError, 'invalid identifier: True'), 'expected \'str\', got \'float\''
('a', PythonArgumentKind.VARARG, '1', None, PythonArgumentStructureError, '* or ** arguments cannot have default values: *a = 1'), ),
('a', PythonArgumentKind.KWARGS, '1', None, PythonArgumentStructureError, '* or ** arguments cannot have default values: **a = 1'), (
]) 'a',
def test_PythonFunctionArgument_exceptions(name, kind, default, annotation, expected_exception, expected_exception_msg): PythonArgumentKind.NORMAL,
1,
None,
TypeError,
'expected either \'str\' or \'NoneType\', got \'int\''
),
(
'a',
PythonArgumentKind.NORMAL,
None,
1,
TypeError,
'expected either \'str\' or \'NoneType\', got \'int\''
),
(
'a',
0,
None,
None,
TypeError,
'expected \'PythonArgumentKind\', got \'int\''
),
(
'a',
PythonArgumentKind.NORMAL,
None,
'',
InvalidPythonAnnotationError,
'invalid annotation'
),
(
'',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier'
),
(
'.',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: .'
),
(
'#',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: #'
),
(
'1a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: 1a'
),
(
'1_',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: 1_'
),
(
'1,',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: 1,'
),
(
',',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: ,'
),
(
':',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: :'
),
(
'-',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: -'
),
(
';',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: ;'
),
(
'|',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: |'
),
(
'<',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: <'
),
(
'>',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: >'
),
(
'a,',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a,'
),
(
',a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: ,a'
),
(
'a:',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a:'
),
(
':a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: :a'
),
(
'a-',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a-'
),
(
'-a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: -a'
),
(
'a;',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a;'
),
(
';a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: ;a'
),
(
'a|',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a|'
),
(
'|a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: |a'
),
(
'a<',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a<'
),
(
'<a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: <a'
),
(
'a>',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: a>'
),
(
'>a',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: >a'
),
(
'class',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: class'
),
(
'def',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: def'
),
(
'lambda',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: lambda'
),
(
'None',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: None'
),
(
'True',
PythonArgumentKind.NORMAL,
None,
None,
InvalidPythonIdentifierError,
'invalid identifier: True'
),
(
'a',
PythonArgumentKind.VARARG,
'1',
None,
PythonArgumentStructureError,
'* or ** arguments cannot have default values: *a = 1'
),
(
'a',
PythonArgumentKind.KWARGS,
'1',
None,
PythonArgumentStructureError,
'* or ** arguments cannot have default values: **a = 1'
),
]
)
def test_PythonFunctionArgument_exceptions(
name,
kind,
default,
annotation,
expected_exception,
expected_exception_msg
):
with pytest.raises(expected_exception) as exc_info: with pytest.raises(expected_exception) as exc_info:
PythonFunctionArgument(name, kind, default, annotation) PythonFunctionArgument(name, kind, default, annotation)
assert str(exc_info.value) == expected_exception_msg assert str(exc_info.value) == expected_exception_msg
@@ -136,22 +454,150 @@ def test_PythonFunctionArgument_asterik(name, kind, annotation):
PythonFunctionArgument(name, kind, None, annotation) PythonFunctionArgument(name, kind, None, annotation)
@pytest.mark.parametrize('argument,expected', [ @pytest.mark.parametrize('argument,expected', [
(PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None), 'a'), (
(PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '1', None), 'a = 1'), PythonFunctionArgument(
(PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'), 'a: int'), 'a',
(PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '1', 'int'), 'a: int = 1'), PythonArgumentKind.NORMAL,
(PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None), 'a'), None,
(PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, '1', None), 'a = 1'), None
(PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, 'int'), 'a: int'), ),
(PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, '1', 'int'), 'a: int = 1'), 'a'
(PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None), 'a'), ),
(PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, '1', None), 'a = 1'), (
(PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, 'int'), 'a: int'), PythonFunctionArgument(
(PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, '1', 'int'), 'a: int = 1'), 'a',
(PythonFunctionArgument('a', PythonArgumentKind.VARARG, None, None), '*a'), PythonArgumentKind.NORMAL,
(PythonFunctionArgument('a', PythonArgumentKind.VARARG, None, 'int'), '*a: int'), '1',
(PythonFunctionArgument('a', PythonArgumentKind.KWARGS, None, None), '**a'), None
(PythonFunctionArgument('a', PythonArgumentKind.KWARGS, None, 'int'), '**a: int'), ),
'a = 1'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int'
),
'a: int'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
'1',
'int'
),
'a: int = 1'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
None
),
'a'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
'1',
None
),
'a = 1'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
None,
'int'
),
'a: int'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.POSITIONAL_ONLY,
'1',
'int'
),
'a: int = 1'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
None
),
'a'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
'1',
None
),
'a = 1'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
None,
'int'
),
'a: int'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KEYWORD_ONLY,
'1',
'int'
),
'a: int = 1'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.VARARG,
None,
None
),
'*a'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.VARARG,
None,
'int'
),
'*a: int'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KWARGS,
None,
None
),
'**a'
),
(
PythonFunctionArgument(
'a',
PythonArgumentKind.KWARGS,
None,
'int'
),
'**a: int'
),
]) ])
def test_PythonFunctionArgument_signature_repr(argument, expected): def test_PythonFunctionArgument_signature_repr(argument, expected):
assert argument.signature_repr() == expected assert argument.signature_repr() == expected
@@ -12,86 +12,482 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from src.jcloud_docsgen.core.python.definitions import PythonFunctionDefinition, PythonClassDefinition from src.jcloud_docsgen.core.python.definitions import (
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind PythonFunctionDefinition,
PythonClassDefinition
)
from src.jcloud_docsgen.core.python.arguments import (
PythonFunctionArgument,
PythonArgumentKind
)
from src.jcloud_docsgen.core.python.docstrings import PythonDocstring from src.jcloud_docsgen.core.python.docstrings import PythonDocstring
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError
from tests.utils.ast_node import ast_node from tests.utils.ast_node import ast_node
import pytest import pytest
@pytest.mark.parametrize('definition,expected', [ @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,
None,
[]
),
'PythonClassDefinition(\'A\', [], None, None, [])'
),
(
PythonClassDefinition(
'A',
[],
None,
PythonDocstring('Hello, World!'),
[]
),
(
'PythonClassDefinition(\'A\', [], None, '
'PythonDocstring(\'Hello, World!\'), [])'
)
),
]) ])
def test_PythonDefinition_string_representation(definition, expected): def test_PythonDefinition_string_representation(definition, expected):
assert repr(definition) == expected assert repr(definition) == expected
@pytest.mark.parametrize('name,args,returns,doc,decorators,expected_exception,expected_exception_message', [ @pytest.mark.parametrize(
('', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier'), (
('.', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: .'), 'name,args,returns,doc,decorators,expect'
('#', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: #'), 'ed_exception,expected_exception_message'
('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, None, [], InvalidPythonIdentifierError, 'invalid identifier: ;'), None,
('|', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: |'), [],
('<', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: <'), InvalidPythonIdentifierError,
('>', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: >'), '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-'), None,
('-a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: -a'), None,
('a;', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a;'), [],
(';a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: ;a'), InvalidPythonIdentifierError,
('a|', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a|'), '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'), None,
('class', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: class'), None,
('def', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: def'), [],
('lambda', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: lambda'), InvalidPythonIdentifierError,
('None', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: None'), 'invalid identifier: #'
('True', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: True'), ),
]) (
def test_PythonFunctionDefinition_exceptions(name, args, returns, doc, decorators, expected_exception, expected_exception_message): '1a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1a'
),
(
'1_',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1_'
),
(
'1,',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1,'
),
(
',',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ,'
),
(
':',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: :'
),
(
'-',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: -'
),
(
';',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ;'
),
(
'|',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: |'
),
(
'<',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: <'
),
(
'>',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: >'
),
(
'a,',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a,'
),
(
',a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ,a'
),
(
'a:',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a:'
),
(
':a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: :a'
),
(
'a-',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a-'
),
(
'-a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: -a'
),
(
'a;',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a;'
),
(
';a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ;a'
),
(
'a|',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a|'
),
(
'|a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: |a'
),
(
'a<',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a<'
),
(
'<a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: <a'
),
(
'a>',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a>'
),
(
'>a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: >a'
),
(
'class',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: class'
),
(
'def',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: def'
),
(
'lambda',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: lambda'
),
(
'None',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: None'
),
(
'True',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: True'
),
]
)
def test_PythonFunctionDefinition_exceptions(
name,
args,
returns,
doc,
decorators,
expected_exception,
expected_exception_message
):
with pytest.raises(expected_exception) as exc_info: with pytest.raises(expected_exception) as exc_info:
PythonClassDefinition(name, args, returns, doc, decorators) PythonClassDefinition(name, args, returns, doc, decorators)
assert str(exc_info.value) == expected_exception_message assert str(exc_info.value) == expected_exception_message
@pytest.mark.parametrize('node,expected', [ @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', ast_node(
'BaseClass2' (
], None, ['decorator'], [ '@decorator\nclass Class(BaseClass1, BaseClass2)'
PythonFunctionDefinition('__init__', [ ':\n\tdef __init__(self, value: object) -> None:'
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None), '\n\t\tself._value = value\n\n\t@property\n\tdef'
PythonFunctionArgument('value', PythonArgumentKind.NORMAL, None, 'object') ' value(self) -> object:\n\t\treturn self._value'
], 'None', None, []), )
PythonFunctionDefinition('value', [
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None) ),
], 'object', None, ['property']) PythonClassDefinition(
])), 'Class',
(ast_node('class Class: ...'), PythonClassDefinition('Class', [], None, [], [])), ['BaseClass1', 'BaseClass2'],
(ast_node('class Class: \'\'\'docstring\'\'\''), PythonClassDefinition('Class', [], PythonDocstring('docstring'), [], [])), None,
(ast_node('class Class:\n\tdef __init__(self): ...'), PythonClassDefinition('Class', [], None, [], [ ['decorator'],
PythonFunctionDefinition('__init__', [ [
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None) PythonFunctionDefinition(
], None, None, []) '__init__',
])), [
(ast_node('class Class:\n\t\'\'\'doc\'\'\'\n\tdef __init__(self): ...'), PythonClassDefinition('Class', [], PythonDocstring('doc'), [], [ PythonFunctionArgument(
PythonFunctionDefinition('__init__', [ 'self',
PythonFunctionArgument('self', PythonArgumentKind.NORMAL, None, None) PythonArgumentKind.NORMAL,
], None, None, []) None,
])), None
),
PythonFunctionArgument(
'value',
PythonArgumentKind.NORMAL,
None,
'object'
)
],
'None',
None,
[]
),
PythonFunctionDefinition(
'value',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None
)
],
'object',
None,
['property']
)
]
)
),
(
ast_node('class Class: ...'),
PythonClassDefinition(
'Class',
[],
None,
[],
[]
)
),
(
ast_node('class Class: \'\'\'docstring\'\'\''),
PythonClassDefinition(
'Class',
[],
PythonDocstring('docstring'),
[],
[]
)
),
(
ast_node('class Class:\n\tdef __init__(self): ...'),
PythonClassDefinition(
'Class',
[],
None,
[],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None
)
],
None,
None,
[]
)
]
)
),
(
ast_node('class Class:\n\t\'\'\'doc\'\'\'\n\tdef __init__(self): ...'),
PythonClassDefinition(
'Class',
[],
PythonDocstring('doc'),
[],
[
PythonFunctionDefinition(
'__init__',
[
PythonFunctionArgument(
'self',
PythonArgumentKind.NORMAL,
None,
None
)
],
None,
None,
[]
)
]
)
),
]) ])
def test_PythonDefinition_from_node(node, expected): def test_PythonDefinition_from_node(node, expected):
assert PythonClassDefinition.from_node(node) == expected assert PythonClassDefinition.from_node(node) == expected
@@ -18,66 +18,425 @@ from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError
import pytest import pytest
@pytest.mark.parametrize('definition,expected', [ @pytest.mark.parametrize('definition,expected', [
(PythonDefinition('a', None, []), 'PythonDefinition(\'a\', None, [])'), (
(PythonDefinition('a', PythonDocstring('Hello, World!'), []), 'PythonDefinition(\'a\', PythonDocstring(\'Hello, World!\'), [])'), PythonDefinition(
'a',
None,
[]
),
'PythonDefinition(\'a\', None, [])'
),
(
PythonDefinition(
'a',
PythonDocstring('Hello, World!'),
[]
),
'PythonDefinition(\'a\', PythonDocstring(\'Hello, World!\'), [])'
),
]) ])
def test_PythonDefinition_string_representation(definition, expected): def test_PythonDefinition_string_representation(definition, expected):
assert repr(definition) == expected assert repr(definition) == expected
@pytest.mark.parametrize('name,doc,decorators,expected_exception,expected_exception_message', [ @pytest.mark.parametrize(
('', None, [], InvalidPythonIdentifierError, 'invalid identifier'), 'name,doc,decorators,expected_exception,expected_exception_message',
('.', None, [], InvalidPythonIdentifierError, 'invalid identifier: .'), [
('#', None, [], InvalidPythonIdentifierError, 'invalid identifier: #'), (
('1a', None, [], InvalidPythonIdentifierError, 'invalid identifier: 1a'), '',
('1_', None, [], InvalidPythonIdentifierError, 'invalid identifier: 1_'), None,
('1,', None, [], InvalidPythonIdentifierError, 'invalid identifier: 1,'), [],
(',', None, [], InvalidPythonIdentifierError, 'invalid identifier: ,'), InvalidPythonIdentifierError,
(':', None, [], InvalidPythonIdentifierError, 'invalid identifier: :'), 'invalid identifier'
('-', None, [], InvalidPythonIdentifierError, 'invalid identifier: -'), ),
(';', None, [], InvalidPythonIdentifierError, 'invalid identifier: ;'), (
('|', None, [], InvalidPythonIdentifierError, 'invalid identifier: |'), '.',
('<', None, [], InvalidPythonIdentifierError, 'invalid identifier: <'), None,
('>', None, [], InvalidPythonIdentifierError, 'invalid identifier: >'), [],
('a,', None, [], InvalidPythonIdentifierError, 'invalid identifier: a,'), InvalidPythonIdentifierError,
(',a', None, [], InvalidPythonIdentifierError, 'invalid identifier: ,a'), 'invalid identifier: .'
('a:', None, [], InvalidPythonIdentifierError, 'invalid identifier: a:'), ),
(':a', None, [], InvalidPythonIdentifierError, 'invalid identifier: :a'), (
('a-', None, [], InvalidPythonIdentifierError, 'invalid identifier: a-'), '#',
('-a', None, [], InvalidPythonIdentifierError, 'invalid identifier: -a'), None,
('a;', None, [], InvalidPythonIdentifierError, 'invalid identifier: a;'), [],
(';a', None, [], InvalidPythonIdentifierError, 'invalid identifier: ;a'), InvalidPythonIdentifierError,
('a|', None, [], InvalidPythonIdentifierError, 'invalid identifier: a|'), 'invalid identifier: #'
('|a', None, [], InvalidPythonIdentifierError, 'invalid identifier: |a'), ),
('a<', None, [], InvalidPythonIdentifierError, 'invalid identifier: a<'), (
('<a', None, [], InvalidPythonIdentifierError, 'invalid identifier: <a'), '1a',
('a>', None, [], InvalidPythonIdentifierError, 'invalid identifier: a>'), None,
('>a', None, [], InvalidPythonIdentifierError, 'invalid identifier: >a'), [],
('class', None, [], InvalidPythonIdentifierError, 'invalid identifier: class'), InvalidPythonIdentifierError,
('def', None, [], InvalidPythonIdentifierError, 'invalid identifier: def'), 'invalid identifier: 1a'
('lambda', None, [], InvalidPythonIdentifierError, 'invalid identifier: lambda'), ),
('None', None, [], InvalidPythonIdentifierError, 'invalid identifier: None'), (
('True', None, [], InvalidPythonIdentifierError, 'invalid identifier: True'), '1_',
]) None,
def test_PythonDefinition_exceptions(name, doc, decorators, expected_exception, expected_exception_message): [],
InvalidPythonIdentifierError,
'invalid identifier: 1_'
),
(
'1,',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1,'
),
(
',',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ,'
),
(
':',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: :'
),
(
'-',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: -'
),
(
';',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ;'
),
(
'|',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: |'
),
(
'<',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: <'
),
(
'>',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: >'
),
(
'a,',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a,'
),
(
',a',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ,a'
),
(
'a:',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a:'
),
(
':a',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: :a'
),
(
'a-',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a-'
),
(
'-a',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: -a'
),
(
'a;',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a;'
),
(
';a',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ;a'
),
(
'a|',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a|'
),
(
'|a',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: |a'
),
(
'a<',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a<'
),
(
'<a',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: <a'
),
(
'a>',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a>'
),
(
'>a',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: >a'
),
(
'class',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: class'
),
(
'def',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: def'
),
(
'lambda',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: lambda'
),
(
'None',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: None'
),
(
'True',
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: True'
),
]
)
def test_PythonDefinition_exceptions(
name,
doc,
decorators,
expected_exception,
expected_exception_message
):
with pytest.raises(expected_exception) as exc_info: with pytest.raises(expected_exception) as exc_info:
PythonDefinition(name, doc, decorators) PythonDefinition(name, doc, decorators)
assert str(exc_info.value) == expected_exception_message assert str(exc_info.value) == expected_exception_message
@pytest.mark.parametrize('definition1,definition2,expected', [ @pytest.mark.parametrize('definition1,definition2,expected', [
(PythonDefinition('a', None, []), PythonDefinition('a', None, []), True), (
(PythonDefinition('a', PythonDocstring('doc'), []), PythonDefinition('a', PythonDocstring('doc'), []), True), PythonDefinition(
(PythonDefinition('a', PythonDocstring('doc'), ['a']), PythonDefinition('a', PythonDocstring('doc'), ['a']), True), 'a',
(PythonDefinition('a', PythonDocstring('doc'), ['a', 'b']), PythonDefinition('a', PythonDocstring('doc'), ['a', 'b']), True), None,
(PythonDefinition('a', None, []), PythonDefinition('b', None, []), False), []
(PythonDefinition('a', PythonDocstring('doc'), []), PythonDefinition('a', None, []), False), ),
(PythonDefinition('a', PythonDocstring('doc'), []), PythonDefinition('a', PythonDocstring('docs'), []), False), PythonDefinition(
(PythonDefinition('a', None, ['a']), PythonDefinition('a', None, ['b']), False), 'a',
(PythonDefinition('a', None, ['a']), PythonDefinition('a', None, []), False), None,
(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), True
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[]
),
PythonDefinition(
'a',
PythonDocstring('doc'),
[]
),
True
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
['a']
),
PythonDefinition(
'a',
PythonDocstring('doc'),
['a']
),
True
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
['a', 'b']
),
PythonDefinition(
'a',
PythonDocstring('doc'),
['a', 'b']
),
True
),
(
PythonDefinition(
'a',
None,
[]
),
PythonDefinition(
'b',
None,
[]
),
False
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[]
),
PythonDefinition(
'a',
None,
[]
),
False
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[]
),
PythonDefinition(
'a',
PythonDocstring('docs'),
[]
),
False
),
(
PythonDefinition(
'a',
None,
['a']
),
PythonDefinition(
'a',
None,
['b']
),
False
),
(
PythonDefinition(
'a',
None,
['a']
),
PythonDefinition(
'a',
None,
[]
),
False
),
(
PythonDefinition(
'a',
None,
['a']
),
PythonDefinition(
'a',
None,
['a', 'b']
),
False
),
(
PythonDefinition(
'a',
None,
['b', 'a']
),
PythonDefinition(
'a',
None,
['a', 'b']
),
False
),
(
PythonDefinition(
'a',
PythonDocstring('doc'),
[]
),
PythonDefinition(
'b',
None,
['a', 'b']
),
False
),
]) ])
def test_PythonDefinition___eq__(definition1, definition2, expected): def test_PythonDefinition___eq__(definition1, definition2, expected):
assert (definition1 == definition2) == expected assert (definition1 == definition2) == expected
@@ -13,75 +13,447 @@
# limitations under the License. # limitations under the License.
from src.jcloud_docsgen.core.python.definitions import PythonFunctionDefinition from src.jcloud_docsgen.core.python.definitions import PythonFunctionDefinition
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind from src.jcloud_docsgen.core.python.arguments import (
PythonFunctionArgument,
PythonArgumentKind
)
from src.jcloud_docsgen.core.python.docstrings import PythonDocstring from src.jcloud_docsgen.core.python.docstrings import PythonDocstring
from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError from src.jcloud_docsgen.exceptions import InvalidPythonIdentifierError
from tests.utils.ast_node import ast_node from tests.utils.ast_node import ast_node
import pytest import pytest
@pytest.mark.parametrize('definition,expected', [ @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,
None,
[]
),
'PythonFunctionDefinition(\'a\', [], None, None, [])'
),
(
PythonFunctionDefinition(
'a',
[],
None,
PythonDocstring('Hello, World!'),
[]
),
(
'PythonFunctionDefinition(\'a\', [], None,'
' PythonDocstring(\'Hello, World!\'), [])'
)
),
]) ])
def test_PythonDefinition_string_representation(definition, expected): def test_PythonDefinition_string_representation(definition, expected):
assert repr(definition) == expected assert repr(definition) == expected
@pytest.mark.parametrize('name,args,returns,doc,decorators,expected_exception,expected_exception_message', [ @pytest.mark.parametrize(
('', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier'), (
('.', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: .'), 'name,args,returns,doc,decorators,expect'
('#', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: #'), 'ed_exception,expected_exception_message'
('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, None, [], InvalidPythonIdentifierError, 'invalid identifier: ;'), None,
('|', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: |'), [],
('<', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: <'), InvalidPythonIdentifierError,
('>', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: >'), '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-'), None,
('-a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: -a'), None,
('a;', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a;'), [],
(';a', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: ;a'), InvalidPythonIdentifierError,
('a|', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: a|'), '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'), None,
('class', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: class'), None,
('def', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: def'), [],
('lambda', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: lambda'), InvalidPythonIdentifierError,
('None', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: None'), 'invalid identifier: #'
('True', [], None, None, [], InvalidPythonIdentifierError, 'invalid identifier: True'), ),
(
'1a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1a'
),
(
'1_',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1_'
),
(
'1,',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: 1,'
),
(
',',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ,'
),
(
':',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: :'
),
(
'-',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: -'
),
(
';',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ;'
),
(
'|',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: |'
),
(
'<',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: <'
),
(
'>',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: >'
),
(
'a,',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a,'
),
(
',a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ,a'
),
(
'a:',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a:'
),
(
':a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: :a'
),
(
'a-',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a-'
),
(
'-a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: -a'
),
(
'a;',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a;'
),
(
';a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: ;a'
),
(
'a|',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a|'
),
(
'|a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: |a'
),
(
'a<',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a<'
),
(
'<a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: <a'
),
(
'a>',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: a>'
),
(
'>a',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: >a'
),
(
'class',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: class'
),
(
'def',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: def'
),
(
'lambda',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: lambda'
),
(
'None',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: None'
),
(
'True',
[],
None,
None,
[],
InvalidPythonIdentifierError,
'invalid identifier: True'
),
]) ])
def test_PythonFunctionDefinition_exceptions(name, args, returns, doc, decorators, expected_exception, expected_exception_message): def test_PythonFunctionDefinition_exceptions(
name,
args,
returns,
doc,
decorators,
expected_exception,
expected_exception_message
):
with pytest.raises(expected_exception) as exc_info: with pytest.raises(expected_exception) as exc_info:
PythonFunctionDefinition(name, args, returns, doc, decorators) PythonFunctionDefinition(name, args, returns, doc, decorators)
assert str(exc_info.value) == expected_exception_message assert str(exc_info.value) == expected_exception_message
@pytest.mark.parametrize('node,expected', [ @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'), ast_node('def add(a: int, b: int) -> int: return a + b'),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int'), PythonFunctionDefinition(
], 'int', None, [])), 'add', [
(ast_node('@decorator\ndef add(a: int, b: int) -> int: return a + b'), PythonFunctionDefinition('add', [ PythonFunctionArgument(
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'), 'a',
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int'), PythonArgumentKind.NORMAL,
], 'int', None, ['decorator'])), None,
(ast_node('@decorator1\n@decorator2\ndef add(a: int, b: int) -> int: return a + b'), PythonFunctionDefinition('add', [ 'int'
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, 'int'), ),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, 'int'), PythonFunctionArgument(
], 'int', None, ['decorator1', 'decorator2'])), 'b',
(ast_node('def func(): ...'), PythonFunctionDefinition('func', [], None, None, [])), PythonArgumentKind.NORMAL,
(ast_node('@decorator\ndef func(): ...'), PythonFunctionDefinition('func', [], None, None, ['decorator'])), None,
'int'
),
],
'int',
None,
[]
)
),
(
ast_node('@decorator\ndef add(a: int, b: int) -> int: return a + b'),
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int'
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int'
),
],
'int',
None,
['decorator']
)
),
(
ast_node(
(
'@decorator1\n@decorator2\ndef add(a'
': int, b: int) -> int: return a + b'
)
),
PythonFunctionDefinition(
'add',
[
PythonFunctionArgument(
'a',
PythonArgumentKind.NORMAL,
None,
'int'
),
PythonFunctionArgument(
'b',
PythonArgumentKind.NORMAL,
None,
'int'
),
],
'int',
None,
['decorator1', 'decorator2']
)
),
(
ast_node('def func(): ...'),
PythonFunctionDefinition(
'func',
[],
None,
None,
[]
)
),
(
ast_node('@decorator\ndef func(): ...'),
PythonFunctionDefinition(
'func',
[],
None,
None,
['decorator']
)
),
]) ])
def test_PythonDefinition_from_node(node, expected): def test_PythonDefinition_from_node(node, expected):
assert PythonFunctionDefinition.from_node(node) == expected assert PythonFunctionDefinition.from_node(node) == expected
@@ -12,16 +12,40 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from src.jcloud_docsgen.core.python.namespaces import PythonPackageNamespace, PythonModuleNamespace from src.jcloud_docsgen.core.python.namespaces import (
PythonPackageNamespace,
PythonModuleNamespace
)
import pytest import pytest
from src.jcloud_docsgen.exceptions import NamespaceNotFoundError, InvalidNamespaceError from src.jcloud_docsgen.exceptions import (
NamespaceNotFoundError,
InvalidNamespaceError
)
class StrSubclass(str): ... class StrSubclass(str): ...
@pytest.mark.parametrize('namespace,expected_name', [ @pytest.mark.parametrize('namespace,expected_name', [
(PythonPackageNamespace('a', []), 'a'), (
(PythonPackageNamespace('1', []), '1'), PythonPackageNamespace(
(PythonPackageNamespace(StrSubclass('a'), []), StrSubclass('a')), 'a',
[]
),
'a'
),
(
PythonPackageNamespace(
'1',
[]
),
'1'
),
(
PythonPackageNamespace(
StrSubclass('a'),
[]
),
StrSubclass('a')
),
]) ])
def test_PythonPackageNamespace_name_attribute(namespace, expected_name): def test_PythonPackageNamespace_name_attribute(namespace, expected_name):
assert namespace.name == expected_name assert namespace.name == expected_name
@@ -41,68 +65,225 @@ namespace_e = PythonPackageNamespace('e', [namespace_f])
namespace_d = PythonPackageNamespace('d', [namespace_e]) namespace_d = PythonPackageNamespace('d', [namespace_e])
@pytest.mark.parametrize('namespace,sub_namespaces,expected', [ @pytest.mark.parametrize('namespace,sub_namespaces,expected', [
(PythonPackageNamespace('name', [ (
namespace_a, PythonPackageNamespace(
'name',
[namespace_a, namespace_c]
),
['c'],
namespace_c namespace_c
]), ['c'], namespace_c), ),
(PythonPackageNamespace('name', [ (
namespace_a, PythonPackageNamespace(
namespace_c 'name',
]), ['a'], namespace_a), [namespace_a, namespace_c]
(PythonPackageNamespace('name', [ ),
namespace_a, ['a'],
namespace_c namespace_a
]), ['a', 'b'], namespace_b), ),
(namespace_d, ['e', 'f', 'g', 'h', 'i'], namespace_i), (
(namespace_d, ['e', 'f', 'g', 'h', 'i', 'j'], namespace_j), PythonPackageNamespace(
(namespace_d, ['e', 'f', 'g', 'h', 'i', 'j', 'k'], namespace_k), 'name',
[namespace_a, namespace_c]
),
['a', 'b'],
namespace_b
),
(
namespace_d,
['e', 'f', 'g', 'h', 'i'],
namespace_i
),
(
namespace_d,
['e', 'f', 'g', 'h', 'i', 'j'],
namespace_j
),
(
namespace_d,
['e', 'f', 'g', 'h', 'i', 'j', 'k'],
namespace_k
),
]) ])
def test_PythonPackageNamespace_namespace(namespace: PythonPackageNamespace, sub_namespaces, expected): def test_PythonPackageNamespace_namespace(
namespace: PythonPackageNamespace,
sub_namespaces,
expected
):
assert namespace.namespace(sub_namespaces) == expected assert namespace.namespace(sub_namespaces) == expected
@pytest.mark.parametrize('namespace,sub_namespaces,expected_exception,expected_exception_msg', [ @pytest.mark.parametrize(
(namespace_a, ['c'], NamespaceNotFoundError, 'no such namespace: c'), 'namespace,sub_namespaces,expected_exception,expected_exception_msg',
(namespace_h, ['i', 'k'], NamespaceNotFoundError, 'no such namespace: k'), [
(namespace_a, ['b', ''], InvalidNamespaceError, 'invalid namespace'), (
(namespace_a, ['', 'b'], InvalidNamespaceError, 'invalid namespace'), namespace_a,
(namespace_a, ['b', '', 'b'], InvalidNamespaceError, 'invalid namespace'), ['c'],
]) NamespaceNotFoundError,
def test_PythonPackageNamespace_namespace_exceptions(namespace, sub_namespaces, expected_exception, expected_exception_msg): 'no such namespace: c'
),
(
namespace_h,
['i', 'k'],
NamespaceNotFoundError,
'no such namespace: k'
),
(
namespace_a,
['b', ''],
InvalidNamespaceError,
'invalid namespace'
),
(
namespace_a,
['', 'b'],
InvalidNamespaceError,
'invalid namespace'
),
(
namespace_a,
['b', '', 'b'],
InvalidNamespaceError,
'invalid namespace'
),
]
)
def test_PythonPackageNamespace_namespace_exceptions(
namespace,
sub_namespaces,
expected_exception,
expected_exception_msg
):
with pytest.raises(expected_exception) as exc_info: with pytest.raises(expected_exception) as exc_info:
namespace.namespace(sub_namespaces) namespace.namespace(sub_namespaces)
assert str(exc_info.value) == expected_exception_msg assert str(exc_info.value) == expected_exception_msg
@pytest.mark.parametrize('namespace,expected', [ @pytest.mark.parametrize('namespace,expected', [
(PythonPackageNamespace('a', [PythonModuleNamespace('b')]), {'b': 'b'}), (
(PythonPackageNamespace('a', [PythonModuleNamespace('b'), PythonPackageNamespace('c', [PythonModuleNamespace('d')])]), {'b': 'b', 'c': {'d': 'd'}}), PythonPackageNamespace(
(PythonPackageNamespace('a', []), {}) 'a',
[PythonModuleNamespace('b')]
),
{'b': 'b'}
),
(
PythonPackageNamespace(
'a',
[
PythonModuleNamespace('b'),
PythonPackageNamespace(
'c',
[PythonModuleNamespace('d')]
)
]
),
{
'b': 'b',
'c': {'d': 'd'}
}
),
(
PythonPackageNamespace('a', []),
{}
)
]) ])
def test_PythonPackageNamespace_sub_namespace_names(namespace, expected): def test_PythonPackageNamespace_sub_namespace_names(namespace, expected):
assert namespace.sub_namespace_names() == expected assert namespace.sub_namespace_names() == expected
@pytest.mark.parametrize('namespace1,namespace2,expected', [ @pytest.mark.parametrize('namespace1,namespace2,expected', [
(PythonPackageNamespace('a', [PythonModuleNamespace('b')]), PythonPackageNamespace('a', [PythonModuleNamespace('b')]), True), (
(PythonPackageNamespace('a', [PythonModuleNamespace('b'), PythonPackageNamespace('c', [PythonModuleNamespace('d')])]), PythonPackageNamespace('a', [PythonModuleNamespace('b'), PythonPackageNamespace('c', [PythonModuleNamespace('d')])]), True), PythonPackageNamespace(
(PythonPackageNamespace('a', []), PythonPackageNamespace('a', []), True), 'a',
(PythonPackageNamespace('a', []), PythonPackageNamespace('b', []), False), [PythonModuleNamespace('b')]
),
PythonPackageNamespace(
'a',
[PythonModuleNamespace('b')]
),
True
),
(
PythonPackageNamespace(
'a',
[
PythonModuleNamespace('b'),
PythonPackageNamespace(
'c',
[PythonModuleNamespace('d')]
)
]
),
PythonPackageNamespace(
'a',
[
PythonModuleNamespace('b'),
PythonPackageNamespace(
'c',
[PythonModuleNamespace('d')]
)
]
),
True
),
(
PythonPackageNamespace('a', []),
PythonPackageNamespace('a', []),
True
),
(
PythonPackageNamespace('a', []),
PythonPackageNamespace('b', []),
False
),
]) ])
def test_PythonPackageNamespace___eq__(namespace1, namespace2, expected): def test_PythonPackageNamespace___eq__(namespace1, namespace2, expected):
assert (namespace1 == namespace2) == expected assert (namespace1 == namespace2) == expected
assert (namespace2 == namespace1) == expected assert (namespace2 == namespace1) == expected
@pytest.mark.parametrize('namespace,expected', [ @pytest.mark.parametrize('namespace,expected', [
(PythonPackageNamespace('a', []), 'PythonPackageNamespace(\'a\', [])'), (
(PythonPackageNamespace('1', []), 'PythonPackageNamespace(\'1\', [])'), PythonPackageNamespace('a', []),
(PythonPackageNamespace(StrSubclass('a'), []), 'PythonPackageNamespace(\'a\', [])'), 'PythonPackageNamespace(\'a\', [])'
(namespace_a, 'PythonPackageNamespace(\'a\', [PythonModuleNamespace(\'b\')])'), ),
(namespace_d, 'PythonPackageNamespace(\'d\', [PythonPackageNamespace(\'e\', [PythonPackageNamespace(\'f\', [PythonPackageNamespace(\'g\', [PythonPackageNamespace(\'h\', [PythonPackageNamespace(\'i\', [PythonPackageNamespace(\'j\', [PythonModuleNamespace(\'k\')])])])])])])])') (
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): def test_PythonPackageNamespace_string_representation(namespace, expected):
assert repr(namespace) == expected assert repr(namespace) == expected
@pytest.mark.parametrize('namespace,new_namespace,expected', [ @pytest.mark.parametrize('namespace,new_namespace,expected', [
(PythonPackageNamespace('a', []), PythonModuleNamespace('b'), PythonPackageNamespace('a', [PythonModuleNamespace('b')])) (
PythonPackageNamespace('a', []),
PythonModuleNamespace('b'),
PythonPackageNamespace(
'a',
[PythonModuleNamespace('b')]
)
)
]) ])
def test_PythonPackageNamespace___getitem__(namespace, new_namespace, expected): def test_PythonPackageNamespace___getitem__(
namespace,
new_namespace,
expected
):
namespace.add(new_namespace) namespace.add(new_namespace)
assert namespace == expected assert namespace == expected
@@ -31,13 +31,37 @@ def test_assert_that_is_instance(obj, class_or_tuple):
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', [ @pytest.mark.parametrize('obj,class_or_tuple,expected_exception_msg', [
(1, str, 'expected \'str\', got \'int\''), (
(1, (str,), 'expected \'str\', got \'int\''), 1,
(1, str | float, 'expected either \'str\' or \'float\', got \'int\''), str,
(1, str | float | list, 'expected either \'str\', \'float\' or \'list\', got \'int\''), 'expected \'str\', got \'int\''
(1, TestType, 'expected \'NOT TestType\', got \'int\''), ),
(
1,
(str,),
'expected \'str\', got \'int\''
),
(
1,
str | float,
'expected either \'str\' or \'float\', got \'int\''
),
(
1,
str | float | list,
'expected either \'str\', \'float\' or \'list\', got \'int\''
),
(
1,
TestType,
'expected \'NOT TestType\', got \'int\''
),
]) ])
def test_assert_that_is_instance_exceptions(obj, class_or_tuple, expected_exception_msg): def test_assert_that_is_instance_exceptions(
obj,
class_or_tuple,
expected_exception_msg
):
with pytest.raises(TypeError) as exc_info: with pytest.raises(TypeError) as exc_info:
assert_that_is_instance(obj, class_or_tuple) assert_that_is_instance(obj, class_or_tuple)
assert str(exc_info.value) == expected_exception_msg assert str(exc_info.value) == expected_exception_msg
+6 -1
View File
@@ -48,7 +48,12 @@ import pytest
([1, 2, 3], 'and', '\'', '\'1\', \'2\' and \'3\''), ([1, 2, 3], 'and', '\'', '\'1\', \'2\' and \'3\''),
([1, 2, 3], '', '\'', '\'1\', \'2\' \'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'),
([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): def test_human_readable_list(ls, final_separator, quotation_mark, expected):
assert human_readable_list(ls, final_separator, quotation_mark) == expected assert human_readable_list(ls, final_separator, quotation_mark) == expected