Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix duplicated *args and **kwargs with autodoc_typehints #9648

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion sphinx/ext/autodoc/typehints.py
Expand Up @@ -19,6 +19,11 @@
from sphinx.application import Sphinx
from sphinx.util import inspect, typing

__ANNOTATION_KIND_TO_PARAM_PREFIX: Dict[inspect.inspect._ParameterKind, str] = {
inspect.Parameter.VAR_POSITIONAL: '*',
inspect.Parameter.VAR_KEYWORD: '**',
}


def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
options: Dict, args: str, retann: str) -> None:
Expand All @@ -30,7 +35,9 @@ def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
sig = inspect.signature(obj, type_aliases=app.config.autodoc_type_aliases)
for param in sig.parameters.values():
if param.annotation is not param.empty:
annotation[param.name] = typing.stringify(param.annotation)
prefix = __ANNOTATION_KIND_TO_PARAM_PREFIX.get(param.kind, '')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change will cause the :param: definition without prefixes (ex. args, kwargs). So this change is not good. I think it would be better to change modify_field_list() and augment_descriptions_with_types() instead (without modifying this annotation data).

name = f'{prefix}{param.name}'
annotation[name] = typing.stringify(param.annotation)
if sig.return_annotation is not sig.empty:
annotation['return'] = typing.stringify(sig.return_annotation)
except (TypeError, ValueError):
Expand Down
30 changes: 29 additions & 1 deletion tests/roots/test-ext-autodoc/target/typehints.py
Expand Up @@ -83,8 +83,36 @@ def missing_attr(c,
class _ClassWithDocumentedInit:
"""Class docstring."""

def __init__(self, x: int) -> None:
def __init__(self, x: int, args: int, kwargs: int) -> None:
"""Init docstring.

:param x: Some integer
:param args: Some integer
:param kwargs: Some integer
"""


class _ClassWithoutDocumentedInit:
"""Class docstring."""

def __init__(self, x: int, args: int, kwargs: int) -> None:
pass


class _ClassWithDocumentedInitAndStarArgs:
"""Class docstring."""

def __init__(self, x: int, *args: int, **kwargs: int) -> None:
"""Init docstring.

:param x: Some integer
:param *args: Some integer
:param **kwargs: Some integer
tk0miya marked this conversation as resolved.
Show resolved Hide resolved
"""


class _ClassWithDocumentedoutInitAndStarArgs:
"""Class docstring."""

def __init__(self, x: int, *args: int, **kwargs: int) -> None:
pass
105 changes: 97 additions & 8 deletions tests/test_ext_autodoc_configs.py
Expand Up @@ -878,31 +878,79 @@ def test_autodoc_typehints_description_no_undoc(app):
in context)


@pytest.mark.sphinx('text', testroot='ext-autodoc',
confoverrides={'autodoc_typehints': "description"})
def test_autodoc_typehints_description_without_documented_init(app):
(app.srcdir / 'index.rst').write_text(
'.. autoclass:: target.typehints._ClassWithDocumentedoutInitAndStarArgs\n'
' :special-members: __init__\n'
)
app.build()
context = (app.outdir / 'index.txt').read_text()
assert ('class target.typehints._ClassWithDocumentedoutInitAndStarArgs(x, *args, **kwargs)\n'
'\n'
' Class docstring.\n'
'\n'
' Parameters:\n'
' * **x** (*int*) --\n'
'\n'
' * ***args** (*int*) --\n'
'\n'
' * ****kwargs** (*int*) --\n'
'\n'
' Return type:\n'
' None\n'
'\n'
' __init__(x, *args, **kwargs)\n'
'\n'
' Parameters:\n'
' * **x** (*int*) --\n'
'\n'
' * ***args** (*int*) --\n'
'\n'
' * ****kwargs** (*int*) --\n'
'\n'
' Return type:\n'
' None\n' == context)


@pytest.mark.sphinx('text', testroot='ext-autodoc',
confoverrides={'autodoc_typehints': "description"})
def test_autodoc_typehints_description_with_documented_init(app):
(app.srcdir / 'index.rst').write_text(
'.. autoclass:: target.typehints._ClassWithDocumentedInit\n'
'.. autoclass:: target.typehints._ClassWithDocumentedInitAndStarArgs\n'
' :special-members: __init__\n'
)
app.build()
context = (app.outdir / 'index.txt').read_text()
assert ('class target.typehints._ClassWithDocumentedInit(x)\n'
assert ('class target.typehints._ClassWithDocumentedInitAndStarArgs(x, *args, **kwargs)\n'
'\n'
' Class docstring.\n'
'\n'
' Parameters:\n'
' **x** (*int*) --\n'
' * **x** (*int*) --\n'
'\n'
' * ***args** (*int*) --\n'
'\n'
' * ****kwargs** (*int*) --\n'
'\n'
' Return type:\n'
' None\n'
'\n'
' __init__(x)\n'
' __init__(x, *args, **kwargs)\n'
'\n'
' Init docstring.\n'
'\n'
' Parameters:\n'
' **x** (*int*) -- Some integer\n'
' * **x** (*int*) -- Some integer\n'
'\n'
' * ***args** (*int*) --\n'
'\n'
' Some integer\n'
'\n'
' * ****kwargs** (*int*) --\n'
'\n'
' Some integer\n'
'\n'
' Return type:\n'
' None\n' == context)
Expand All @@ -918,16 +966,57 @@ def test_autodoc_typehints_description_with_documented_init_no_undoc(app):
)
app.build()
context = (app.outdir / 'index.txt').read_text()
assert ('class target.typehints._ClassWithDocumentedInit(x)\n'
assert ('class target.typehints._ClassWithDocumentedInit(x, args, kwargs)\n'
'\n'
' Class docstring.\n'
'\n'
' __init__(x)\n'
' __init__(x, args, kwargs)\n'
'\n'
' Init docstring.\n'
'\n'
' Parameters:\n'
' **x** (*int*) -- Some integer\n' == context)
' * **x** (*int*) -- Some integer\n'
'\n'
' * **args** (*int*) -- Some integer\n'
'\n'
' * **kwargs** (*int*) -- Some integer\n' == context)


@pytest.mark.sphinx('text', testroot='ext-autodoc',
confoverrides={'autodoc_typehints': "description"})
def test_autodoc_typehints_description_without_documented_init_no_undoc(app):
(app.srcdir / 'index.rst').write_text(
'.. autoclass:: target.typehints._ClassWithoutDocumentedInit\n'
' :special-members: __init__\n'
)
app.build()
context = (app.outdir / 'index.txt').read_text()
assert ('class target.typehints._ClassWithoutDocumentedInit(x, args, kwargs)\n'
'\n'
' Class docstring.\n'
'\n'
' Parameters:\n'
' * **x** (*int*) --\n'
'\n'
' * **args** (*int*) --\n'
'\n'
' * **kwargs** (*int*) --\n'
'\n'
' Return type:\n'
' None\n'

'\n'
' __init__(x, args, kwargs)\n'
'\n'
' Parameters:\n'
' * **x** (*int*) --\n'
'\n'
' * **args** (*int*) --\n'
'\n'
' * **kwargs** (*int*) --\n'
'\n'
' Return type:\n'
' None\n' == context)


@pytest.mark.sphinx('text', testroot='ext-autodoc',
Expand Down