mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #7018 from tk0miya/6418_autodoc_typehints_description
6418 autodoc typehints description
This commit is contained in:
commit
0f8a868bf7
5
CHANGES
5
CHANGES
@ -41,8 +41,13 @@ Features added
|
|||||||
annotation (python3.8+ or `typed_ast <https://github.com/python/typed_ast>`_
|
annotation (python3.8+ or `typed_ast <https://github.com/python/typed_ast>`_
|
||||||
is required)
|
is required)
|
||||||
* #7051: autodoc: Support instance variables without defaults (PEP-526)
|
* #7051: autodoc: Support instance variables without defaults (PEP-526)
|
||||||
|
* #6418: autodoc: Add a new extension ``sphinx.ext.autodoc.typehints``. It shows
|
||||||
|
typehints as object description if ``autodoc_typehints = "description"`` set.
|
||||||
|
This is an experimental extension and it will be integrated into autodoc core
|
||||||
|
in Sphinx-3.0
|
||||||
* SphinxTranslator now calls visitor/departure method for super node class if
|
* SphinxTranslator now calls visitor/departure method for super node class if
|
||||||
visitor/departure method for original node class not found
|
visitor/departure method for original node class not found
|
||||||
|
* #6418: Add new event: :event:`object-description-transform`
|
||||||
|
|
||||||
Bugs fixed
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
@ -218,6 +218,14 @@ connect handlers to the events. Example:
|
|||||||
|
|
||||||
.. versionadded:: 0.5
|
.. versionadded:: 0.5
|
||||||
|
|
||||||
|
.. event:: object-description-transform (app, domain, objtype, contentnode)
|
||||||
|
|
||||||
|
Emitted when an object description directive has run. The *domain* and
|
||||||
|
*objtype* arguments are strings indicating object description of the object.
|
||||||
|
And *contentnode* is a content for the object. It can be modified in-place.
|
||||||
|
|
||||||
|
.. versionadded:: 2.4
|
||||||
|
|
||||||
.. event:: doctree-read (app, doctree)
|
.. event:: doctree-read (app, doctree)
|
||||||
|
|
||||||
Emitted when a doctree has been parsed and read by the environment, and is
|
Emitted when a doctree has been parsed and read by the environment, and is
|
||||||
|
@ -567,3 +567,24 @@ member should be included in the documentation by using the following event:
|
|||||||
``inherited_members``, ``undoc_members``, ``show_inheritance`` and
|
``inherited_members``, ``undoc_members``, ``show_inheritance`` and
|
||||||
``noindex`` that are true if the flag option of same name was given to the
|
``noindex`` that are true if the flag option of same name was given to the
|
||||||
auto directive
|
auto directive
|
||||||
|
|
||||||
|
Generating documents from type annotations
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
As an experimental feature, autodoc provides ``sphinx.ext.autodoc.typehints`` as
|
||||||
|
an additional extension. It extends autodoc itself to generate function document
|
||||||
|
from its type annotations.
|
||||||
|
|
||||||
|
To enable the feature, please add ``sphinx.ext.autodoc.typehints`` to list of
|
||||||
|
extensions and set `'description'` to :confval:`autodoc_typehints`:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autodoc.typehints']
|
||||||
|
|
||||||
|
autodoc_typehints = 'description'
|
||||||
|
|
||||||
|
.. versionadded:: 2.4
|
||||||
|
|
||||||
|
Added as an experimental feature. This will be integrated into autodoc core
|
||||||
|
in Sphinx-3.0.
|
||||||
|
@ -193,6 +193,8 @@ class ObjectDescription(SphinxDirective):
|
|||||||
self.env.temp_data['object'] = self.names[0]
|
self.env.temp_data['object'] = self.names[0]
|
||||||
self.before_content()
|
self.before_content()
|
||||||
self.state.nested_parse(self.content, self.content_offset, contentnode)
|
self.state.nested_parse(self.content, self.content_offset, contentnode)
|
||||||
|
self.env.app.emit('object-description-transform',
|
||||||
|
self.domain, self.objtype, contentnode)
|
||||||
DocFieldTransformer(self).transform_all(contentnode)
|
DocFieldTransformer(self).transform_all(contentnode)
|
||||||
self.env.temp_data['object'] = None
|
self.env.temp_data['object'] = None
|
||||||
self.after_content()
|
self.after_content()
|
||||||
@ -295,6 +297,8 @@ def setup(app: "Sphinx") -> Dict[str, Any]:
|
|||||||
# new, more consistent, name
|
# new, more consistent, name
|
||||||
directives.register_directive('object', ObjectDescription)
|
directives.register_directive('object', ObjectDescription)
|
||||||
|
|
||||||
|
app.add_event('object-description-transform')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'version': 'builtin',
|
'version': 'builtin',
|
||||||
'parallel_read_safe': True,
|
'parallel_read_safe': True,
|
||||||
|
144
sphinx/ext/autodoc/typehints.py
Normal file
144
sphinx/ext/autodoc/typehints.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
"""
|
||||||
|
sphinx.ext.autodoc.typehints
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Generating content for autodoc using typehints
|
||||||
|
|
||||||
|
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
|
||||||
|
:license: BSD, see LICENSE for details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import Any, Dict, Iterable
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
from docutils import nodes
|
||||||
|
from docutils.nodes import Element
|
||||||
|
|
||||||
|
from sphinx import addnodes
|
||||||
|
from sphinx.application import Sphinx
|
||||||
|
from sphinx.config import ENUM
|
||||||
|
from sphinx.util import inspect, typing
|
||||||
|
|
||||||
|
|
||||||
|
def config_inited(app, config):
|
||||||
|
if config.autodoc_typehints == 'description':
|
||||||
|
# HACK: override this to make autodoc suppressing typehints in signatures
|
||||||
|
config.autodoc_typehints = 'none'
|
||||||
|
|
||||||
|
# preserve user settings
|
||||||
|
app._autodoc_typehints_description = True
|
||||||
|
else:
|
||||||
|
app._autodoc_typehints_description = False
|
||||||
|
|
||||||
|
|
||||||
|
def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any,
|
||||||
|
options: Dict, args: str, retann: str) -> None:
|
||||||
|
"""Record type hints to env object."""
|
||||||
|
try:
|
||||||
|
if callable(obj):
|
||||||
|
annotations = app.env.temp_data.setdefault('annotations', {}).setdefault(name, {})
|
||||||
|
sig = inspect.signature(obj)
|
||||||
|
for param in sig.parameters.values():
|
||||||
|
if param.annotation is not param.empty:
|
||||||
|
annotations[param.name] = typing.stringify(param.annotation)
|
||||||
|
if sig.return_annotation is not sig.empty:
|
||||||
|
annotations['return'] = typing.stringify(sig.return_annotation)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element) -> None:
|
||||||
|
if domain != 'py':
|
||||||
|
return
|
||||||
|
if app._autodoc_typehints_description is False: # type: ignore
|
||||||
|
return
|
||||||
|
|
||||||
|
signature = cast(addnodes.desc_signature, contentnode.parent[0])
|
||||||
|
fullname = '.'.join([signature['module'], signature['fullname']])
|
||||||
|
annotations = app.env.temp_data.get('annotations', {})
|
||||||
|
if annotations.get(fullname, {}):
|
||||||
|
field_lists = [n for n in contentnode if isinstance(n, nodes.field_list)]
|
||||||
|
if field_lists == []:
|
||||||
|
field_list = insert_field_list(contentnode)
|
||||||
|
field_lists.append(field_list)
|
||||||
|
|
||||||
|
for field_list in field_lists:
|
||||||
|
modify_field_list(field_list, annotations[fullname])
|
||||||
|
|
||||||
|
|
||||||
|
def insert_field_list(node: Element) -> nodes.field_list:
|
||||||
|
field_list = nodes.field_list()
|
||||||
|
desc = [n for n in node if isinstance(n, addnodes.desc)]
|
||||||
|
if desc:
|
||||||
|
# insert just before sub object descriptions (ex. methods, nested classes, etc.)
|
||||||
|
index = node.index(desc[0])
|
||||||
|
node.insert(index - 1, [field_list])
|
||||||
|
else:
|
||||||
|
node += field_list
|
||||||
|
|
||||||
|
return field_list
|
||||||
|
|
||||||
|
|
||||||
|
def modify_field_list(node: nodes.field_list, annotations: Dict[str, str]) -> None:
|
||||||
|
arguments = {} # type: Dict[str, Dict[str, bool]]
|
||||||
|
fields = cast(Iterable[nodes.field], node)
|
||||||
|
for field in fields:
|
||||||
|
field_name = field[0].astext()
|
||||||
|
parts = re.split(' +', field_name)
|
||||||
|
if parts[0] == 'param':
|
||||||
|
if len(parts) == 2:
|
||||||
|
# :param xxx:
|
||||||
|
arg = arguments.setdefault(parts[1], {})
|
||||||
|
arg['param'] = True
|
||||||
|
elif len(parts) > 2:
|
||||||
|
# :param xxx yyy:
|
||||||
|
name = ' '.join(parts[2:])
|
||||||
|
arg = arguments.setdefault(name, {})
|
||||||
|
arg['param'] = True
|
||||||
|
arg['type'] = True
|
||||||
|
elif parts[0] == 'type':
|
||||||
|
name = ' '.join(parts[1:])
|
||||||
|
arg = arguments.setdefault(name, {})
|
||||||
|
arg['type'] = True
|
||||||
|
elif parts[0] == 'rtype':
|
||||||
|
arguments['return'] = {'type': True}
|
||||||
|
|
||||||
|
for name, annotation in annotations.items():
|
||||||
|
if name == 'return':
|
||||||
|
continue
|
||||||
|
|
||||||
|
arg = arguments.get(name, {})
|
||||||
|
field = nodes.field()
|
||||||
|
if arg.get('param') and arg.get('type'):
|
||||||
|
# both param and type are already filled manually
|
||||||
|
continue
|
||||||
|
elif arg.get('param'):
|
||||||
|
# only param: fill type field
|
||||||
|
field += nodes.field_name('', 'type ' + name)
|
||||||
|
field += nodes.field_body('', nodes.paragraph('', annotation))
|
||||||
|
elif arg.get('type'):
|
||||||
|
# only type: It's odd...
|
||||||
|
field += nodes.field_name('', 'param ' + name)
|
||||||
|
field += nodes.field_body('', nodes.paragraph('', ''))
|
||||||
|
else:
|
||||||
|
# both param and type are not found
|
||||||
|
field += nodes.field_name('', 'param ' + annotation + ' ' + name)
|
||||||
|
field += nodes.field_body('', nodes.paragraph('', ''))
|
||||||
|
|
||||||
|
node += field
|
||||||
|
|
||||||
|
if 'return' in annotations and 'return' not in arguments:
|
||||||
|
field = nodes.field()
|
||||||
|
field += nodes.field_name('', 'rtype')
|
||||||
|
field += nodes.field_body('', nodes.paragraph('', annotation))
|
||||||
|
node += field
|
||||||
|
|
||||||
|
|
||||||
|
def setup(app):
|
||||||
|
app.setup_extension('sphinx.ext.autodoc')
|
||||||
|
app.config.values['autodoc_typehints'] = ('signature', True,
|
||||||
|
ENUM("signature", "description", "none"))
|
||||||
|
app.connect('config-inited', config_inited)
|
||||||
|
app.connect('autodoc-process-signature', record_typehints)
|
||||||
|
app.connect('object-description-transform', merge_typehints)
|
@ -452,6 +452,8 @@ class Signature:
|
|||||||
its return annotation.
|
its return annotation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
empty = inspect.Signature.empty
|
||||||
|
|
||||||
def __init__(self, subject: Callable, bound_method: bool = False,
|
def __init__(self, subject: Callable, bound_method: bool = False,
|
||||||
has_retval: bool = True) -> None:
|
has_retval: bool = True) -> None:
|
||||||
warnings.warn('sphinx.util.inspect.Signature() is deprecated',
|
warnings.warn('sphinx.util.inspect.Signature() is deprecated',
|
||||||
|
@ -7,3 +7,5 @@
|
|||||||
|
|
||||||
.. automodule:: autodoc_dummy_bar
|
.. automodule:: autodoc_dummy_bar
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autofunction:: target.typehints.incr
|
||||||
|
@ -540,6 +540,24 @@ def test_autodoc_typehints_none(app):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sphinx('text', testroot='ext-autodoc',
|
||||||
|
confoverrides={'extensions': ['sphinx.ext.autodoc.typehints'],
|
||||||
|
'autodoc_typehints': 'description'})
|
||||||
|
def test_autodoc_typehints_description(app):
|
||||||
|
app.build()
|
||||||
|
context = (app.outdir / 'index.txt').text()
|
||||||
|
assert ('target.typehints.incr(a, b=1)\n'
|
||||||
|
'\n'
|
||||||
|
' Parameters:\n'
|
||||||
|
' * **a** (*int*) --\n'
|
||||||
|
'\n'
|
||||||
|
' * **b** (*int*) --\n'
|
||||||
|
'\n'
|
||||||
|
' Return type:\n'
|
||||||
|
' int\n'
|
||||||
|
in context)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||||
@pytest.mark.filterwarnings('ignore:autodoc_default_flags is now deprecated.')
|
@pytest.mark.filterwarnings('ignore:autodoc_default_flags is now deprecated.')
|
||||||
def test_merge_autodoc_default_flags1(app):
|
def test_merge_autodoc_default_flags1(app):
|
||||||
|
Loading…
Reference in New Issue
Block a user