summaryrefslogtreecommitdiff
path: root/sphinx/domains/python.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/domains/python.py')
-rw-r--r--sphinx/domains/python.py93
1 files changed, 62 insertions, 31 deletions
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index fc1136ae2..c2af9886f 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -4,33 +4,34 @@
The Python domain.
- :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
+ :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import builtins
import inspect
import re
+import sys
import typing
import warnings
from inspect import Parameter
-from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Tuple
-from typing import cast
+from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Tuple, cast
from docutils import nodes
from docutils.nodes import Element, Node
from docutils.parsers.rst import directives
from sphinx import addnodes
-from sphinx.addnodes import pending_xref, desc_signature
+from sphinx.addnodes import desc_signature, pending_xref
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.directives import ObjectDescription
-from sphinx.domains import Domain, ObjType, Index, IndexEntry
+from sphinx.domains import Domain, Index, IndexEntry, ObjType
from sphinx.environment import BuildEnvironment
from sphinx.locale import _, __
-from sphinx.pycode.ast import ast, parse as ast_parse
+from sphinx.pycode.ast import ast
+from sphinx.pycode.ast import parse as ast_parse
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.docfields import Field, GroupedField, TypedField
@@ -77,18 +78,24 @@ ModuleEntry = NamedTuple('ModuleEntry', [('docname', str),
('deprecated', bool)])
-def type_to_xref(text: str) -> addnodes.pending_xref:
+def type_to_xref(text: str, env: BuildEnvironment = None) -> addnodes.pending_xref:
"""Convert a type string to a cross reference node."""
if text == 'None':
reftype = 'obj'
else:
reftype = 'class'
+ if env:
+ kwargs = {'py:module': env.ref_context.get('py:module'),
+ 'py:class': env.ref_context.get('py:class')}
+ else:
+ kwargs = {}
+
return pending_xref('', nodes.Text(text),
- refdomain='py', reftype=reftype, reftarget=text)
+ refdomain='py', reftype=reftype, reftarget=text, **kwargs)
-def _parse_annotation(annotation: str) -> List[Node]:
+def _parse_annotation(annotation: str, env: BuildEnvironment = None) -> List[Node]:
"""Parse type annotation."""
def unparse(node: ast.AST) -> List[Node]:
if isinstance(node, ast.Attribute):
@@ -128,20 +135,37 @@ def _parse_annotation(annotation: str) -> List[Node]:
return result
else:
+ if sys.version_info >= (3, 6):
+ if isinstance(node, ast.Constant):
+ if node.value is Ellipsis:
+ return [addnodes.desc_sig_punctuation('', "...")]
+ else:
+ return [nodes.Text(node.value)]
+
+ if sys.version_info < (3, 8):
+ if isinstance(node, ast.Ellipsis):
+ return [addnodes.desc_sig_punctuation('', "...")]
+ elif isinstance(node, ast.NameConstant):
+ return [nodes.Text(node.value)]
+
raise SyntaxError # unsupported syntax
+ if env is None:
+ warnings.warn("The env parameter for _parse_annotation becomes required now.",
+ RemovedInSphinx50Warning, stacklevel=2)
+
try:
tree = ast_parse(annotation)
result = unparse(tree)
for i, node in enumerate(result):
if isinstance(node, nodes.Text):
- result[i] = type_to_xref(str(node))
+ result[i] = type_to_xref(str(node), env)
return result
except SyntaxError:
- return [type_to_xref(annotation)]
+ return [type_to_xref(annotation, env)]
-def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
+def _parse_arglist(arglist: str, env: BuildEnvironment = None) -> addnodes.desc_parameterlist:
"""Parse a list of arguments using AST parser"""
params = addnodes.desc_parameterlist(arglist)
sig = signature_from_str('(%s)' % arglist)
@@ -167,7 +191,7 @@ def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
node += addnodes.desc_sig_name('', param.name)
if param.annotation is not param.empty:
- children = _parse_annotation(param.annotation)
+ children = _parse_annotation(param.annotation, env)
node += addnodes.desc_sig_punctuation('', ':')
node += nodes.Text(' ')
node += addnodes.desc_sig_name('', '', *children) # type: ignore
@@ -248,6 +272,8 @@ class PyXrefMixin:
result = super().make_xref(rolename, domain, target, # type: ignore
innernode, contnode, env)
result['refspecific'] = True
+ result['py:module'] = env.ref_context.get('py:module')
+ result['py:class'] = env.ref_context.get('py:class')
if target.startswith(('.', '~')):
prefix, result['reftarget'] = target[0], target[1:]
if prefix == '.':
@@ -308,7 +334,7 @@ class PyTypedField(PyXrefMixin, TypedField):
return super().make_xref(rolename, domain, target, innernode, contnode, env)
-class PyObject(ObjectDescription):
+class PyObject(ObjectDescription[Tuple[str, str]]):
"""
Description of a general Python object.
@@ -317,6 +343,7 @@ class PyObject(ObjectDescription):
"""
option_spec = {
'noindex': directives.flag,
+ 'noindexentry': directives.flag,
'module': directives.unchanged,
'annotation': directives.unchanged,
}
@@ -414,7 +441,7 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_name(name, name)
if arglist:
try:
- signode += _parse_arglist(arglist)
+ signode += _parse_arglist(arglist, self.env)
except SyntaxError:
# fallback to parse arglist original parser.
# it supports to represent optional arguments (ex. "func(foo [, bar])")
@@ -429,7 +456,7 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_parameterlist()
if retann:
- children = _parse_annotation(retann)
+ children = _parse_annotation(retann, self.env)
signode += addnodes.desc_returns(retann, '', *children)
anno = self.options.get('annotation')
@@ -459,16 +486,17 @@ class PyObject(ObjectDescription):
domain = cast(PythonDomain, self.env.get_domain('py'))
domain.note_object(fullname, self.objtype, node_id, location=signode)
- indextext = self.get_index_text(modname, name_cls)
- if indextext:
- self.indexnode['entries'].append(('single', indextext, node_id, '', None))
+ if 'noindexentry' not in self.options:
+ indextext = self.get_index_text(modname, name_cls)
+ if indextext:
+ self.indexnode['entries'].append(('single', indextext, node_id, '', None))
def before_content(self) -> None:
"""Handle object nesting before content
:py:class:`PyObject` represents Python language constructs. For
constructs that are nestable, such as a Python classes, this method will
- build up a stack of the nesting heirarchy so that it can be later
+ build up a stack of the nesting hierarchy so that it can be later
de-nested correctly, in :py:meth:`after_content`.
For constructs that aren't nestable, the stack is bypassed, and instead
@@ -576,16 +604,17 @@ class PyFunction(PyObject):
def add_target_and_index(self, name_cls: Tuple[str, str], sig: str,
signode: desc_signature) -> None:
super().add_target_and_index(name_cls, sig, signode)
- modname = self.options.get('module', self.env.ref_context.get('py:module'))
- node_id = signode['ids'][0]
+ if 'noindexentry' not in self.options:
+ modname = self.options.get('module', self.env.ref_context.get('py:module'))
+ node_id = signode['ids'][0]
- name, cls = name_cls
- if modname:
- text = _('%s() (in module %s)') % (name, modname)
- self.indexnode['entries'].append(('single', text, node_id, '', None))
- else:
- text = '%s; %s()' % (pairindextypes['builtin'], name)
- self.indexnode['entries'].append(('pair', text, node_id, '', None))
+ name, cls = name_cls
+ if modname:
+ text = _('%s() (in module %s)') % (name, modname)
+ self.indexnode['entries'].append(('single', text, node_id, '', None))
+ else:
+ text = '%s; %s()' % (pairindextypes['builtin'], name)
+ self.indexnode['entries'].append(('pair', text, node_id, '', None))
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
# add index in own add_target_and_index() instead.
@@ -623,7 +652,8 @@ class PyVariable(PyObject):
typ = self.options.get('type')
if typ:
- signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), type_to_xref(typ))
+ annotations = _parse_annotation(typ, self.env)
+ signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations)
value = self.options.get('value')
if value:
@@ -868,7 +898,8 @@ class PyAttribute(PyObject):
typ = self.options.get('type')
if typ:
- signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), type_to_xref(typ))
+ annotations = _parse_annotation(typ, self.env)
+ signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), *annotations)
value = self.options.get('value')
if value: