mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #8539 from godlygeek/description_typehints_for_classes
Make autodoc_typehints="description" work with autoclass_content="class"
This commit is contained in:
commit
ddb6e9c61c
@ -571,6 +571,19 @@ There are also config values that you can set:
|
||||
|
||||
New option ``'description'`` is added.
|
||||
|
||||
.. confval:: autodoc_typehints_description_target
|
||||
|
||||
This value controls whether the types of undocumented parameters and return
|
||||
values are documented when ``autodoc_typehints`` is set to ``description``.
|
||||
|
||||
The default value is ``"all"``, meaning that types are documented for all
|
||||
parameters and return values, whether they are documented or not.
|
||||
|
||||
When set to ``"documented"``, types will only be documented for a parameter
|
||||
or a return value that is already documented by the docstring.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
|
||||
.. confval:: autodoc_type_aliases
|
||||
|
||||
A dictionary for users defined `type aliases`__ that maps a type name to the
|
||||
|
@ -2656,6 +2656,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_config_value('autodoc_mock_imports', [], True)
|
||||
app.add_config_value('autodoc_typehints', "signature", True,
|
||||
ENUM("signature", "description", "none"))
|
||||
app.add_config_value('autodoc_typehints_description_target', 'all', True,
|
||||
ENUM('all', 'documented'))
|
||||
app.add_config_value('autodoc_type_aliases', {}, True)
|
||||
app.add_config_value('autodoc_warningiserror', True, True)
|
||||
app.add_config_value('autodoc_inherit_docstrings', True, True)
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, Iterable, cast
|
||||
from typing import Any, Dict, Iterable, Set, cast
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.nodes import Element
|
||||
@ -42,8 +42,6 @@ def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element
|
||||
return
|
||||
if app.config.autodoc_typehints != 'description':
|
||||
return
|
||||
if objtype == 'class' and app.config.autoclass_content not in ('init', 'both'):
|
||||
return
|
||||
|
||||
try:
|
||||
signature = cast(addnodes.desc_signature, contentnode.parent[0])
|
||||
@ -63,7 +61,10 @@ def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element
|
||||
field_lists.append(field_list)
|
||||
|
||||
for field_list in field_lists:
|
||||
modify_field_list(field_list, annotations[fullname])
|
||||
if app.config.autodoc_typehints_description_target == "all":
|
||||
modify_field_list(field_list, annotations[fullname])
|
||||
else:
|
||||
augment_descriptions_with_types(field_list, annotations[fullname])
|
||||
|
||||
|
||||
def insert_field_list(node: Element) -> nodes.field_list:
|
||||
@ -126,6 +127,52 @@ def modify_field_list(node: nodes.field_list, annotations: Dict[str, str]) -> No
|
||||
node += field
|
||||
|
||||
|
||||
def augment_descriptions_with_types(
|
||||
node: nodes.field_list,
|
||||
annotations: Dict[str, str],
|
||||
) -> None:
|
||||
fields = cast(Iterable[nodes.field], node)
|
||||
has_description = set() # type: Set[str]
|
||||
has_type = set() # type: Set[str]
|
||||
for field in fields:
|
||||
field_name = field[0].astext()
|
||||
parts = re.split(' +', field_name)
|
||||
if parts[0] == 'param':
|
||||
if len(parts) == 2:
|
||||
# :param xxx:
|
||||
has_description.add(parts[1])
|
||||
elif len(parts) > 2:
|
||||
# :param xxx yyy:
|
||||
name = ' '.join(parts[2:])
|
||||
has_description.add(name)
|
||||
has_type.add(name)
|
||||
elif parts[0] == 'type':
|
||||
name = ' '.join(parts[1:])
|
||||
has_type.add(name)
|
||||
elif parts[0] == 'return':
|
||||
has_description.add('return')
|
||||
elif parts[0] == 'rtype':
|
||||
has_type.add('return')
|
||||
|
||||
# Add 'type' for parameters with a description but no declared type.
|
||||
for name in annotations:
|
||||
if name == 'return':
|
||||
continue
|
||||
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]))
|
||||
node += field
|
||||
|
||||
# Add 'rtype' if 'return' is present and 'rtype' isn't.
|
||||
if 'return' in annotations:
|
||||
if 'return' in has_description and 'return' not in has_type:
|
||||
field = nodes.field()
|
||||
field += nodes.field_name('', 'rtype')
|
||||
field += nodes.field_body('', nodes.paragraph('', annotations['return']))
|
||||
node += field
|
||||
|
||||
|
||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.connect('autodoc-process-signature', record_typehints)
|
||||
app.connect('object-description-transform', merge_typehints)
|
||||
|
@ -68,3 +68,13 @@ def missing_attr(c,
|
||||
):
|
||||
# type: (...) -> str
|
||||
return a + (b or "")
|
||||
|
||||
|
||||
class _ClassWithDocumentedInit:
|
||||
"""Class docstring."""
|
||||
|
||||
def __init__(self, x: int) -> None:
|
||||
"""Init docstring.
|
||||
|
||||
:param x: Some integer
|
||||
"""
|
||||
|
@ -682,6 +682,90 @@ def test_autodoc_typehints_description(app):
|
||||
in context)
|
||||
|
||||
|
||||
@pytest.mark.sphinx('text', testroot='ext-autodoc',
|
||||
confoverrides={'autodoc_typehints': "description",
|
||||
'autodoc_typehints_description_target': 'documented'})
|
||||
def test_autodoc_typehints_description_no_undoc(app):
|
||||
# No :type: or :rtype: will be injected for `incr`, which does not have
|
||||
# a description for its parameters or its return. `tuple_args` does
|
||||
# describe them, so :type: and :rtype: will be added.
|
||||
(app.srcdir / 'index.rst').write_text(
|
||||
'.. autofunction:: target.typehints.incr\n'
|
||||
'\n'
|
||||
'.. autofunction:: target.typehints.tuple_args\n'
|
||||
'\n'
|
||||
' :param x: arg\n'
|
||||
' :return: another tuple\n'
|
||||
)
|
||||
app.build()
|
||||
context = (app.outdir / 'index.txt').read_text()
|
||||
assert ('target.typehints.incr(a, b=1)\n'
|
||||
'\n'
|
||||
'target.typehints.tuple_args(x)\n'
|
||||
'\n'
|
||||
' Parameters:\n'
|
||||
' **x** (*Tuple**[**int**, **Union**[**int**, **str**]**]*) -- arg\n'
|
||||
'\n'
|
||||
' Returns:\n'
|
||||
' another tuple\n'
|
||||
'\n'
|
||||
' Return type:\n'
|
||||
' Tuple[int, int]\n'
|
||||
in 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'
|
||||
' :special-members: __init__\n'
|
||||
)
|
||||
app.build()
|
||||
context = (app.outdir / 'index.txt').read_text()
|
||||
assert ('class target.typehints._ClassWithDocumentedInit(x)\n'
|
||||
'\n'
|
||||
' Class docstring.\n'
|
||||
'\n'
|
||||
' Parameters:\n'
|
||||
' **x** (*int*) --\n'
|
||||
'\n'
|
||||
' Return type:\n'
|
||||
' None\n'
|
||||
'\n'
|
||||
' __init__(x)\n'
|
||||
'\n'
|
||||
' Init docstring.\n'
|
||||
'\n'
|
||||
' Parameters:\n'
|
||||
' **x** (*int*) -- Some integer\n'
|
||||
'\n'
|
||||
' Return type:\n'
|
||||
' None\n' == context)
|
||||
|
||||
|
||||
@pytest.mark.sphinx('text', testroot='ext-autodoc',
|
||||
confoverrides={'autodoc_typehints': "description",
|
||||
'autodoc_typehints_description_target': 'documented'})
|
||||
def test_autodoc_typehints_description_with_documented_init_no_undoc(app):
|
||||
(app.srcdir / 'index.rst').write_text(
|
||||
'.. autoclass:: target.typehints._ClassWithDocumentedInit\n'
|
||||
' :special-members: __init__\n'
|
||||
)
|
||||
app.build()
|
||||
context = (app.outdir / 'index.txt').read_text()
|
||||
assert ('class target.typehints._ClassWithDocumentedInit(x)\n'
|
||||
'\n'
|
||||
' Class docstring.\n'
|
||||
'\n'
|
||||
' __init__(x)\n'
|
||||
'\n'
|
||||
' Init docstring.\n'
|
||||
'\n'
|
||||
' Parameters:\n'
|
||||
' **x** (*int*) -- Some integer\n' == context)
|
||||
|
||||
|
||||
@pytest.mark.sphinx('text', testroot='ext-autodoc',
|
||||
confoverrides={'autodoc_typehints': "description"})
|
||||
def test_autodoc_typehints_description_for_invalid_node(app):
|
||||
|
Loading…
Reference in New Issue
Block a user