Skip to content

Commit

Permalink
Merge pull request #10451 from tk0miya/9648_autodoc_typehints_descrip…
Browse files Browse the repository at this point in the history
…tion_and_stared_args

Fix #9648: autodoc: *args and **kwargs entries are duplicated
  • Loading branch information
tk0miya committed May 22, 2022
2 parents 195e911 + d3af85c commit e0835d8
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 14 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -39,6 +39,8 @@ Bugs fixed

* #9575: autodoc: The annotation of return value should not be shown when
``autodoc_typehints="description"``
* #9648: autodoc: ``*args`` and ``**kwargs`` entries are duplicated when
``autodoc_typehints="description"``
* #10456: py domain: ``:meta:`` fields are displayed if docstring contains two
or more meta-field

Expand Down
20 changes: 17 additions & 3 deletions sphinx/ext/autodoc/typehints.py
Expand Up @@ -115,7 +115,15 @@ def modify_field_list(node: nodes.field_list, annotations: Dict[str, str],
if name == 'return':
continue

arg = arguments.get(name, {})
if '*' + name in arguments:
name = '*' + name
arguments.get(name)
elif '**' + name in arguments:
name = '**' + name
arguments.get(name)
else:
arg = arguments.get(name, {})

if not arg.get('type'):
field = nodes.field()
field += nodes.field_name('', 'type ' + name)
Expand Down Expand Up @@ -167,13 +175,19 @@ def augment_descriptions_with_types(
has_type.add('return')

# Add 'type' for parameters with a description but no declared type.
for name in annotations:
for name, annotation in annotations.items():
if name in ('return', 'returns'):
continue

if '*' + name in has_description:
name = '*' + name
elif '**' + name in has_description:
name = '**' + name

if name in has_description and name not in has_type:
field = nodes.field()
field += nodes.field_name('', 'type ' + name)
field += nodes.field_body('', nodes.paragraph('', annotations[name]))
field += nodes.field_body('', nodes.paragraph('', annotation))
node += field

# Add 'rtype' if 'return' is present and 'rtype' isn't.
Expand Down
4 changes: 3 additions & 1 deletion tests/roots/test-ext-autodoc/target/typehints.py
Expand Up @@ -94,8 +94,10 @@ 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
"""
5 changes: 5 additions & 0 deletions tests/roots/test-ext-napoleon/conf.py
@@ -0,0 +1,5 @@
import os
import sys

sys.path.insert(0, os.path.abspath('.'))
extensions = ['sphinx.ext.napoleon']
6 changes: 6 additions & 0 deletions tests/roots/test-ext-napoleon/index.rst
@@ -0,0 +1,6 @@
test-ext-napoleon
=================

.. toctree::

typehints
Empty file.
11 changes: 11 additions & 0 deletions tests/roots/test-ext-napoleon/mypackage/typehints.py
@@ -0,0 +1,11 @@
def hello(x: int, *args: int, **kwargs: int) -> None:
"""
Parameters
----------
x
X
*args
Additional arguments.
**kwargs
Extra arguments.
"""
5 changes: 5 additions & 0 deletions tests/roots/test-ext-napoleon/typehints.rst
@@ -0,0 +1,5 @@
typehints
=========

.. automodule:: mypackage.typehints
:members:
36 changes: 26 additions & 10 deletions tests/test_ext_autodoc_configs.py
Expand Up @@ -1034,19 +1034,27 @@ def test_autodoc_typehints_description_with_documented_init(app):
)
app.build()
context = (app.outdir / 'index.txt').read_text(encoding='utf8')
assert ('class target.typehints._ClassWithDocumentedInit(x)\n'
assert ('class target.typehints._ClassWithDocumentedInit(x, *args, **kwargs)\n'
'\n'
' Class docstring.\n'
'\n'
' Parameters:\n'
' **x** (*int*) --\n'
' * **x** (*int*) --\n'
'\n'
' __init__(x)\n'
' * **args** (*int*) --\n'
'\n'
' * **kwargs** (*int*) --\n'
'\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*) -- Some integer\n'
'\n'
' * **kwargs** (*int*) -- Some integer\n'
'\n'
' Return type:\n'
' None\n' == context)
Expand All @@ -1063,16 +1071,20 @@ def test_autodoc_typehints_description_with_documented_init_no_undoc(app):
)
app.build()
context = (app.outdir / 'index.txt').read_text(encoding='utf8')
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',
Expand All @@ -1089,16 +1101,20 @@ def test_autodoc_typehints_description_with_documented_init_no_undoc_doc_rtype(a
)
app.build()
context = (app.outdir / 'index.txt').read_text(encoding='utf8')
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',
Expand Down
45 changes: 45 additions & 0 deletions tests/test_ext_napoleon_docstring.py
Expand Up @@ -2593,3 +2593,48 @@ def test_pep526_annotations(self):
"""
print(actual)
assert expected == actual


@pytest.mark.sphinx('text', testroot='ext-napoleon',
confoverrides={'autodoc_typehints': 'description',
'autodoc_typehints_description_target': 'all'})
def test_napoleon_and_autodoc_typehints_description_all(app, status, warning):
app.build()
content = (app.outdir / 'typehints.txt').read_text(encoding='utf-8')
assert content == (
'typehints\n'
'*********\n'
'\n'
'mypackage.typehints.hello(x, *args, **kwargs)\n'
'\n'
' Parameters:\n'
' * **x** (*int*) -- X\n'
'\n'
' * ***args** (*int*) -- Additional arguments.\n'
'\n'
' * ****kwargs** (*int*) -- Extra arguments.\n'
'\n'
' Return type:\n'
' None\n'
)


@pytest.mark.sphinx('text', testroot='ext-napoleon',
confoverrides={'autodoc_typehints': 'description',
'autodoc_typehints_description_target': 'documented_params'})
def test_napoleon_and_autodoc_typehints_description_documented_params(app, status, warning):
app.build()
content = (app.outdir / 'typehints.txt').read_text(encoding='utf-8')
assert content == (
'typehints\n'
'*********\n'
'\n'
'mypackage.typehints.hello(x, *args, **kwargs)\n'
'\n'
' Parameters:\n'
' * **x** (*int*) -- X\n'
'\n'
' * ***args** (*int*) -- Additional arguments.\n'
'\n'
' * ****kwargs** (*int*) -- Extra arguments.\n'
)

0 comments on commit e0835d8

Please sign in to comment.