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.py91
1 files changed, 75 insertions, 16 deletions
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index 3a49d6745..a9edd85b4 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -10,6 +10,7 @@
import re
import warnings
+from inspect import Parameter
from typing import Any, Dict, Iterable, Iterator, List, Tuple
from typing import cast
@@ -17,13 +18,11 @@ from docutils import nodes
from docutils.nodes import Element, Node
from docutils.parsers.rst import directives
-from sphinx import addnodes, locale
+from sphinx import addnodes
from sphinx.addnodes import pending_xref, desc_signature
from sphinx.application import Sphinx
from sphinx.builders import Builder
-from sphinx.deprecation import (
- DeprecatedDict, RemovedInSphinx30Warning, RemovedInSphinx40Warning
-)
+from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType, Index, IndexEntry
from sphinx.environment import BuildEnvironment
@@ -32,6 +31,7 @@ from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.docfields import Field, GroupedField, TypedField
from sphinx.util.docutils import SphinxDirective
+from sphinx.util.inspect import signature_from_str
from sphinx.util.nodes import make_refnode
from sphinx.util.typing import TextlikeNode
@@ -63,12 +63,46 @@ pairindextypes = {
'builtin': _('built-in function'),
}
-locale.pairindextypes = DeprecatedDict(
- pairindextypes,
- 'sphinx.locale.pairindextypes is deprecated. '
- 'Please use sphinx.domains.python.pairindextypes instead.',
- RemovedInSphinx30Warning
-)
+
+def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
+ """Parse a list of arguments using AST parser"""
+ params = addnodes.desc_parameterlist(arglist)
+ sig = signature_from_str('(%s)' % arglist)
+ last_kind = None
+ for param in sig.parameters.values():
+ if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY:
+ # PEP-570: Separator for Positional Only Parameter: /
+ params += addnodes.desc_parameter('', nodes.Text('/'))
+ if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD,
+ param.POSITIONAL_ONLY,
+ None):
+ # PEP-3102: Separator for Keyword Only Parameter: *
+ params += addnodes.desc_parameter('', nodes.Text('*'))
+
+ node = addnodes.desc_parameter()
+ if param.kind == param.VAR_POSITIONAL:
+ node += nodes.Text('*' + param.name)
+ elif param.kind == param.VAR_KEYWORD:
+ node += nodes.Text('**' + param.name)
+ else:
+ node += nodes.Text(param.name)
+
+ if param.annotation is not param.empty:
+ node += nodes.Text(': ' + param.annotation)
+ if param.default is not param.empty:
+ if param.annotation is not param.empty:
+ node += nodes.Text(' = ' + str(param.default))
+ else:
+ node += nodes.Text('=' + str(param.default))
+
+ params += node
+ last_kind = param.kind
+
+ if last_kind == Parameter.POSITIONAL_ONLY:
+ # PEP-570: Separator for Positional Only Parameter: /
+ params += addnodes.desc_parameter('', nodes.Text('/'))
+
+ return params
def _pseudo_parse_arglist(signode: desc_signature, arglist: str) -> None:
@@ -293,7 +327,15 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_name(name, name)
if arglist:
- _pseudo_parse_arglist(signode, arglist)
+ try:
+ signode += _parse_arglist(arglist)
+ except SyntaxError:
+ # fallback to parse arglist original parser.
+ # it supports to represent optional arguments (ex. "func(foo [, bar])")
+ _pseudo_parse_arglist(signode, arglist)
+ except NotImplementedError as exc:
+ logger.warning(exc)
+ _pseudo_parse_arglist(signode, arglist)
else:
if self.needs_arglist():
# for callables, add an empty parameter list
@@ -320,12 +362,10 @@ class PyObject(ObjectDescription):
if fullname not in self.state.document.ids:
signode['names'].append(fullname)
signode['ids'].append(fullname)
- signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
domain = cast(PythonDomain, self.env.get_domain('py'))
- domain.note_object(fullname, self.objtype,
- location=(self.env.docname, self.lineno))
+ domain.note_object(fullname, self.objtype, location=signode)
indextext = self.get_index_text(modname, name_cls)
if indextext:
@@ -811,6 +851,21 @@ class PyXRefRole(XRefRole):
return title, target
+def filter_meta_fields(app: Sphinx, domain: str, objtype: str, content: Element) -> None:
+ """Filter ``:meta:`` field from its docstring."""
+ if domain != 'py':
+ return
+
+ for node in content:
+ if isinstance(node, nodes.field_list):
+ fields = cast(List[nodes.field], node)
+ for field in fields:
+ field_name = cast(nodes.field_body, field[0]).astext().strip()
+ if field_name == 'meta' or field_name.startswith('meta '):
+ node.remove(field)
+ break
+
+
class PythonModuleIndex(Index):
"""
Index subclass to provide the Python module index.
@@ -979,7 +1034,8 @@ class PythonDomain(Domain):
self.modules[modname] = data
def find_obj(self, env: BuildEnvironment, modname: str, classname: str,
- name: str, type: str, searchmode: int = 0) -> List[Tuple[str, Any]]:
+ name: str, type: str, searchmode: int = 0
+ ) -> List[Tuple[str, Tuple[str, str]]]:
"""Find a Python object for "name", perhaps using the given module
and/or classname. Returns a list of (name, object entry) tuples.
"""
@@ -990,7 +1046,7 @@ class PythonDomain(Domain):
if not name:
return []
- matches = [] # type: List[Tuple[str, Any]]
+ matches = [] # type: List[Tuple[str, Tuple[str, str]]]
newname = None
if searchmode == 1:
@@ -1119,7 +1175,10 @@ class PythonDomain(Domain):
def setup(app: Sphinx) -> Dict[str, Any]:
+ app.setup_extension('sphinx.directives')
+
app.add_domain(PythonDomain)
+ app.connect('object-description-transform', filter_meta_fields)
return {
'version': 'builtin',