Add Python definition documentation generator

This commit is contained in:
2026-04-19 19:12:33 +02:00
parent 3b45c1a8ed
commit 3fd51d126f
9 changed files with 2486 additions and 10 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,27 @@
# Copyright 2026 jCloud Services GbR
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from src.jcloud_docsgen.core.python._core import _argument_kind_human_readable
from src.jcloud_docsgen.core.python.arguments import PythonArgumentKind
import pytest
@pytest.mark.parametrize('argument_kind,expected', [
(PythonArgumentKind.NORMAL, 'normal'),
(PythonArgumentKind.POSITIONAL_ONLY, 'positional-only'),
(PythonArgumentKind.KEYWORD_ONLY, 'keyword-only'),
(PythonArgumentKind.VARARG, 'vararg'),
(PythonArgumentKind.KWARGS, 'kwargs'),
])
def test__argument_kind_human_readable(argument_kind, expected):
assert _argument_kind_human_readable(argument_kind) == expected
@@ -0,0 +1,56 @@
# Copyright 2026 jCloud Services GbR
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from src.jcloud_docsgen.core.python._core import _fit_row_length
import pytest
@pytest.mark.parametrize('expected_length,row,expected', [
(0, (), ()),
(1, (), (None,)),
(2, (), (None, None)),
(2, (1,), (1, None)),
(0, (1,), ()),
(1, (1, 2), (1,)),
(1, (1, 2), (1,)),
(0, (1, 2), ()),
(0, (1, 2, 3), ()),
(1, (1, 2, 3), (1,)),
(2, (1, 2, 3), (1, 2)),
(1, (1,), (1,)),
(2, (1, 1), (1, 1)),
(2, (1, 2), (1, 2)),
(3, (1, 2, 3), (1, 2, 3)),
])
def test__fit_row_length(expected_length, row, expected):
assert _fit_row_length(expected_length, row) == expected
@pytest.mark.parametrize('row', [
(),
(1,),
(1, 2),
(1, 2, 3),
(1, 2, 3, 456),
])
@pytest.mark.parametrize('expected_length,expected_exception', [
(-1, ValueError),
(-2, ValueError),
(-3, ValueError),
(-42, ValueError),
])
def test__fit_row_length_exceptions(expected_length, row, expected_exception):
with pytest.raises(expected_exception) as exc_info:
_fit_row_length(expected_length, row)
assert str(exc_info.value) == 'expected must be larger than or equal 0.'
@@ -0,0 +1,104 @@
# Copyright 2026 jCloud Services GbR
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from src.jcloud_docsgen.core.python._core import _function_params_info, _FunctionParamInfo
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
import pytest
import docstring_parser
def docstring_args_list(docstring: str) -> list[docstring_parser.DocstringParam]:
'''
Returns the parameter list of a docstring.
:param docstring: The docstring
:type docstring: str
:return: The parameter list.
:rtype: list[docstring_parser.DocstringParam]
'''
return docstring_parser.parse(docstring).params
@pytest.mark.parametrize('arg_list,docstring,expected', [
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'int'),
], ':param a: a\n:type a: str', {
'a': _FunctionParamInfo('int', 'a')
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
], ':param a: a\n:type a: str', {
'a': _FunctionParamInfo('str', 'a')
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, '', None),
], ':param a: a\n:type a: str', {
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo(None, None)
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, '', 'int'),
], ':param a: a\n:type a: str', {
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo('int', None)
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, '', 'int'),
], ':param a: a\n:type a: str\n:param b: b\n:type b: str', {
'a': _FunctionParamInfo('str', 'a'),
'b': _FunctionParamInfo('int', 'b')
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
], ':param a: a\n:type a: str\n:param b: b\n:type b: int', {
'a': _FunctionParamInfo('str', 'a')
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
], '', {
'a': _FunctionParamInfo(None, None)
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'str'),
], '', {
'a': _FunctionParamInfo('str', None)
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'str'),
], ':param a:\n:type a: int', {
'a': _FunctionParamInfo('str', '')
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
], ':param a:\n:type a: int', {
'a': _FunctionParamInfo('int', '')
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', None),
], ':param a: a', {
'a': _FunctionParamInfo(None, 'a')
}),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '', 'str'),
], ':param a: a', {
'a': _FunctionParamInfo('str', 'a')
}),
([], ':param a: a\n:type a: str', {}),
([], '', {}),
])
def test__function_params_types(arg_list, docstring, expected):
assert _function_params_info(arg_list, docstring_args_list(docstring)) == expected
@@ -0,0 +1,93 @@
# Copyright 2026 jCloud Services GbR
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from src.jcloud_docsgen.core.python._core import _markdown_table
import pytest
@pytest.mark.parametrize('header,body,allow_markdown_table,force_row_lengths,one_line_values,missing_value,expected', [
([], [], False, False, True, '--', ''),
([], [('a',)], False, False, True, '--', ''),
(['a'], [], False, False, True, '--', ''),
(['a'], [(1,)], True, False, True, '--', '''| a |
| --- |
| 1 |'''),
(['a'], [(1, 2)], True, False, True, '--', '''| a |
| --- |
| 1 |'''),
(['a', 'b'], [(1, 2)], True, False, True, '--', '''| a | b |
| --- | --- |
| 1 | 2 |'''),
(['a', 'b'], [(1,)], True, False, True, '--', '''| a | b |
| --- | --- |
| 1 | -- |'''),
(['a', 'b'], [()], True, False, True, '--', '''| a | b |
| --- | --- |
| -- | -- |'''),
(['a', 'b'], [(1, 2), (3, 4)], True, False, True, '--', '''| a | b |
| --- | --- |
| 1 | 2 |
| 3 | 4 |'''),
(['a'], [('line1\nline2',)], True, False, True, '--', '''| a |
| --- |
| line1 line2 |'''),
(['a'], [('line1\nline2',)], True, False, False, '--', '''| a |
| --- |
| line1\nline2 |'''),
([], [], False, False, True, '--', ''),
([], [('a',)], False, False, True, '--', ''),
(['a'], [], False, False, True, '--', ''),
(['a'], [(1,)], False, False, True, '--', '''- a: 1'''),
(['a'], [(1, 2)], False, False, True, '--', '''- a: 1'''),
(['a', 'b'], [(1, 2)], False, False, True, '--', '''- a: 1
b: 2'''),
(['a', 'b'], [(1,)], False, False, True, '--', '''- a: 1
b: --'''),
(['a', 'b'], [()], False, False, True, '--', '''- a: --
b: --'''),
(['a', 'b'], [(1, 2), (3, 4)], False, False, True, '--', '''- a: 1
b: 2
- a: 3
b: 4'''),
(['a'], [('line1\nline2',)], False, False, True, '--', '- a: line1 line2'),
(['a'], [('line1\nline2',)], False, False, False, '--', '- a: line1\nline2'),
])
def test__markdown_table(header, body, allow_markdown_table, force_row_lengths, one_line_values, missing_value, expected):
assert _markdown_table(header, body, allow_markdown_table = allow_markdown_table, force_row_lengths = force_row_lengths, one_line_values = one_line_values, missing_value = missing_value) == expected
@pytest.mark.parametrize('header,body,allow_markdown_table,force_row_lengths,one_line_values,missing_value,expected_exception,expected_exception_msg', [
(['a'], [(1, 2)], True, True, True, '--', ValueError, 'expected 1 value, got 2'),
(['a'], [(1, 2, 3)], True, True, True, '--', ValueError, 'expected 1 value, got 3'),
(['a'], [()], True, True, True, '--', ValueError, 'expected 1 value, got 0'),
(['a', 'b'], [()], True, True, True, '--', ValueError, 'expected 2 values, got 0'),
(['a', 'b'], [(1,)], True, True, True, '--', ValueError, 'expected 2 values, got 1'),
(['a', 'b'], [(1, 2, 3)], True, True, True, '--', ValueError, 'expected 2 values, got 3'),
(['a'], [(1, 2)], False, True, True, '--', ValueError, 'expected 1 value, got 2'),
(['a'], [(1, 2, 3)], False, True, True, '--', ValueError, 'expected 1 value, got 3'),
(['a'], [()], False, True, True, '--', ValueError, 'expected 1 value, got 0'),
(['a', 'b'], [()], False, True, True, '--', ValueError, 'expected 2 values, got 0'),
(['a', 'b'], [(1,)], False, True, True, '--', ValueError, 'expected 2 values, got 1'),
(['a', 'b'], [(1, 2, 3)], False, True, True, '--', ValueError, 'expected 2 values, got 3'),
])
def test__markdown_table_exceptions(header, body, allow_markdown_table, force_row_lengths, one_line_values, missing_value, expected_exception, expected_exception_msg):
with pytest.raises(expected_exception) as exc_info:
_markdown_table(header, body, allow_markdown_table = allow_markdown_table, force_row_lengths = force_row_lengths, one_line_values = one_line_values, missing_value = missing_value)
assert str(exc_info.value) == expected_exception_msg
@@ -0,0 +1,97 @@
# Copyright 2026 jCloud Services GbR
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from src.jcloud_docsgen.core.python._core import _signature_argument_list
from src.jcloud_docsgen.core.python.arguments import PythonFunctionArgument, PythonArgumentKind
import pytest
@pytest.mark.parametrize('argument_list,expected', [
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None)
], ['a']),
([
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None)
], ['a', '/']),
([
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
], ['a', '/', 'b']),
([
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
], ['a', '/', 'b', '*', 'c']),
([
PythonFunctionArgument('b', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
], ['b', '*', 'c']),
([
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
], ['*', 'c']),
([
PythonFunctionArgument('b', PythonArgumentKind.POSITIONAL_ONLY, None, None),
PythonFunctionArgument('c', PythonArgumentKind.KEYWORD_ONLY, None, None),
], ['b', '/', '*', 'c']),
([
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
], ['*args']),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
], ['a', '*args']),
([
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
], ['a', '/', '*args']),
([
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None),
], ['*args', 'a']),
([
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
], ['*args', '**kwargs']),
([
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
], ['a', '/', '*args', '**kwargs']),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
], ['a', '*args', '**kwargs']),
([
PythonFunctionArgument('args', PythonArgumentKind.VARARG, None, None),
PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
], ['*args', 'a', '**kwargs']),
([
PythonFunctionArgument('a', PythonArgumentKind.KEYWORD_ONLY, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
], ['*', 'a', '**kwargs']),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
], ['a', '**kwargs']),
([
PythonFunctionArgument('a', PythonArgumentKind.POSITIONAL_ONLY, None, None),
PythonFunctionArgument('kwargs', PythonArgumentKind.KWARGS, None, None),
], ['a', '/', '**kwargs']),
([
PythonFunctionArgument('a', PythonArgumentKind.NORMAL, '1', 'int'),
], ['a: int = 1']),
])
def test__signature_argument_list(argument_list, expected):
assert _signature_argument_list(argument_list) == expected