You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
138 lines
4.7 KiB
138 lines
4.7 KiB
"""
|
|
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 collections import OrderedDict
|
|
from typing import Any, Dict, Iterable, cast
|
|
|
|
from docutils import nodes
|
|
from docutils.nodes import Element
|
|
|
|
from sphinx import addnodes
|
|
from sphinx.application import Sphinx
|
|
from sphinx.util import inspect, typing
|
|
|
|
|
|
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', {})
|
|
annotation = annotations.setdefault(name, OrderedDict())
|
|
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)
|
|
if sig.return_annotation is not sig.empty:
|
|
annotation['return'] = typing.stringify(sig.return_annotation)
|
|
except (TypeError, ValueError):
|
|
pass
|
|
|
|
|
|
def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element) -> None:
|
|
if domain != 'py':
|
|
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])
|
|
if signature['module']:
|
|
fullname = '.'.join([signature['module'], signature['fullname']])
|
|
else:
|
|
fullname = signature['fullname']
|
|
except KeyError:
|
|
# signature node does not have valid context info for the target object
|
|
return
|
|
|
|
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, {})
|
|
if not arg.get('type'):
|
|
field = nodes.field()
|
|
field += nodes.field_name('', 'type ' + name)
|
|
field += nodes.field_body('', nodes.paragraph('', annotation))
|
|
node += field
|
|
if not arg.get('param'):
|
|
field = nodes.field()
|
|
field += nodes.field_name('', 'param ' + 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: Sphinx) -> Dict[str, Any]:
|
|
app.connect('autodoc-process-signature', record_typehints)
|
|
app.connect('object-description-transform', merge_typehints)
|
|
|
|
return {
|
|
'version': 'builtin',
|
|
'parallel_read_safe': True,
|
|
'parallel_write_safe': True,
|
|
}
|