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.py141
1 files changed, 84 insertions, 57 deletions
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index 19da49ac6..fc1136ae2 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -14,7 +14,7 @@ import re
import typing
import warnings
from inspect import Parameter
-from typing import Any, Dict, Iterable, Iterator, List, Tuple
+from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Tuple
from typing import cast
from docutils import nodes
@@ -67,18 +67,29 @@ pairindextypes = {
'builtin': _('built-in function'),
}
+ObjectEntry = NamedTuple('ObjectEntry', [('docname', str),
+ ('node_id', str),
+ ('objtype', str)])
+ModuleEntry = NamedTuple('ModuleEntry', [('docname', str),
+ ('node_id', str),
+ ('synopsis', str),
+ ('platform', str),
+ ('deprecated', bool)])
-def _parse_annotation(annotation: str) -> List[Node]:
- """Parse type annotation."""
- def make_xref(text: str) -> addnodes.pending_xref:
- if text == 'None':
- reftype = 'obj'
- else:
- reftype = 'class'
- return pending_xref('', nodes.Text(text),
- refdomain='py', reftype=reftype, reftarget=text)
+def type_to_xref(text: str) -> addnodes.pending_xref:
+ """Convert a type string to a cross reference node."""
+ if text == 'None':
+ reftype = 'obj'
+ else:
+ reftype = 'class'
+ return pending_xref('', nodes.Text(text),
+ refdomain='py', reftype=reftype, reftarget=text)
+
+
+def _parse_annotation(annotation: str) -> List[Node]:
+ """Parse type annotation."""
def unparse(node: ast.AST) -> List[Node]:
if isinstance(node, ast.Attribute):
return [nodes.Text("%s.%s" % (unparse(node.value)[0], node.attr))]
@@ -124,10 +135,10 @@ def _parse_annotation(annotation: str) -> List[Node]:
result = unparse(tree)
for i, node in enumerate(result):
if isinstance(node, nodes.Text):
- result[i] = make_xref(str(node))
+ result[i] = type_to_xref(str(node))
return result
except SyntaxError:
- return [make_xref(annotation)]
+ return [type_to_xref(annotation)]
def _parse_arglist(arglist: str) -> addnodes.desc_parameterlist:
@@ -521,10 +532,11 @@ class PyModulelevel(PyObject):
if cls.__name__ != 'DirectiveAdapter':
warnings.warn('PyModulelevel is deprecated. '
'Please check the implementation of %s' % cls,
- RemovedInSphinx40Warning)
+ RemovedInSphinx40Warning, stacklevel=2)
break
else:
- warnings.warn('PyModulelevel is deprecated', RemovedInSphinx40Warning)
+ warnings.warn('PyModulelevel is deprecated',
+ RemovedInSphinx40Warning, stacklevel=2)
return super().run()
@@ -611,7 +623,7 @@ class PyVariable(PyObject):
typ = self.options.get('type')
if typ:
- signode += addnodes.desc_annotation(typ, ': ' + typ)
+ signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), type_to_xref(typ))
value = self.options.get('value')
if value:
@@ -632,10 +644,18 @@ class PyClasslike(PyObject):
Description of a class-like object (classes, interfaces, exceptions).
"""
+ option_spec = PyObject.option_spec.copy()
+ option_spec.update({
+ 'final': directives.flag,
+ })
+
allow_nesting = True
def get_signature_prefix(self, sig: str) -> str:
- return self.objtype + ' '
+ if 'final' in self.options:
+ return 'final %s ' % self.objtype
+ else:
+ return '%s ' % self.objtype
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
if self.objtype == 'class':
@@ -658,10 +678,11 @@ class PyClassmember(PyObject):
if cls.__name__ != 'DirectiveAdapter':
warnings.warn('PyClassmember is deprecated. '
'Please check the implementation of %s' % cls,
- RemovedInSphinx40Warning)
+ RemovedInSphinx40Warning, stacklevel=2)
break
else:
- warnings.warn('PyClassmember is deprecated', RemovedInSphinx40Warning)
+ warnings.warn('PyClassmember is deprecated',
+ RemovedInSphinx40Warning, stacklevel=2)
return super().run()
@@ -740,6 +761,7 @@ class PyMethod(PyObject):
'abstractmethod': directives.flag,
'async': directives.flag,
'classmethod': directives.flag,
+ 'final': directives.flag,
'property': directives.flag,
'staticmethod': directives.flag,
})
@@ -752,6 +774,8 @@ class PyMethod(PyObject):
def get_signature_prefix(self, sig: str) -> str:
prefix = []
+ if 'final' in self.options:
+ prefix.append('final')
if 'abstractmethod' in self.options:
prefix.append('abstract')
if 'async' in self.options:
@@ -844,7 +868,7 @@ class PyAttribute(PyObject):
typ = self.options.get('type')
if typ:
- signode += addnodes.desc_annotation(typ, ': ' + typ)
+ signode += addnodes.desc_annotation(typ, '', nodes.Text(': '), type_to_xref(typ))
value = self.options.get('value')
if value:
@@ -876,10 +900,11 @@ class PyDecoratorMixin:
if cls.__name__ != 'DirectiveAdapter':
warnings.warn('PyDecoratorMixin is deprecated. '
'Please check the implementation of %s' % cls,
- RemovedInSphinx50Warning)
+ RemovedInSphinx50Warning, stacklevel=2)
break
else:
- warnings.warn('PyDecoratorMixin is deprecated', RemovedInSphinx50Warning)
+ warnings.warn('PyDecoratorMixin is deprecated',
+ RemovedInSphinx50Warning, stacklevel=2)
ret = super().handle_signature(sig, signode) # type: ignore
signode.insert(0, addnodes.desc_addname('@', '@'))
@@ -1134,8 +1159,8 @@ class PythonDomain(Domain):
]
@property
- def objects(self) -> Dict[str, Tuple[str, str, str]]:
- return self.data.setdefault('objects', {}) # fullname -> docname, node_id, objtype
+ def objects(self) -> Dict[str, ObjectEntry]:
+ return self.data.setdefault('objects', {}) # fullname -> ObjectEntry
def note_object(self, name: str, objtype: str, node_id: str, location: Any = None) -> None:
"""Note a python object for cross reference.
@@ -1143,15 +1168,15 @@ class PythonDomain(Domain):
.. versionadded:: 2.1
"""
if name in self.objects:
- docname = self.objects[name][0]
+ other = self.objects[name]
logger.warning(__('duplicate object description of %s, '
'other instance in %s, use :noindex: for one of them'),
- name, docname, location=location)
- self.objects[name] = (self.env.docname, node_id, objtype)
+ name, other.docname, location=location)
+ self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype)
@property
- def modules(self) -> Dict[str, Tuple[str, str, str, str, bool]]:
- return self.data.setdefault('modules', {}) # modname -> docname, node_id, synopsis, platform, deprecated # NOQA
+ def modules(self) -> Dict[str, ModuleEntry]:
+ return self.data.setdefault('modules', {}) # modname -> ModuleEntry
def note_module(self, name: str, node_id: str, synopsis: str,
platform: str, deprecated: bool) -> None:
@@ -1159,28 +1184,29 @@ class PythonDomain(Domain):
.. versionadded:: 2.1
"""
- self.modules[name] = (self.env.docname, node_id, synopsis, platform, deprecated)
+ self.modules[name] = ModuleEntry(self.env.docname, node_id,
+ synopsis, platform, deprecated)
def clear_doc(self, docname: str) -> None:
- for fullname, (fn, _x, _x) in list(self.objects.items()):
- if fn == docname:
+ for fullname, obj in list(self.objects.items()):
+ if obj.docname == docname:
del self.objects[fullname]
- for modname, (fn, _x, _x, _x, _y) in list(self.modules.items()):
- if fn == docname:
+ for modname, mod in list(self.modules.items()):
+ if mod.docname == docname:
del self.modules[modname]
def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None:
# XXX check duplicates?
- for fullname, (fn, node_id, objtype) in otherdata['objects'].items():
- if fn in docnames:
- self.objects[fullname] = (fn, node_id, objtype)
- for modname, data in otherdata['modules'].items():
- if data[0] in docnames:
- self.modules[modname] = data
+ for fullname, obj in otherdata['objects'].items():
+ if obj.docname in docnames:
+ self.objects[fullname] = obj
+ for modname, mod in otherdata['modules'].items():
+ if mod.docname in docnames:
+ self.modules[modname] = mod
def find_obj(self, env: BuildEnvironment, modname: str, classname: str,
name: str, type: str, searchmode: int = 0
- ) -> List[Tuple[str, Tuple[str, str, str]]]:
+ ) -> List[Tuple[str, ObjectEntry]]:
"""Find a Python object for "name", perhaps using the given module
and/or classname. Returns a list of (name, object entry) tuples.
"""
@@ -1191,7 +1217,7 @@ class PythonDomain(Domain):
if not name:
return []
- matches = [] # type: List[Tuple[str, Tuple[str, str, str]]]
+ matches = [] # type: List[Tuple[str, ObjectEntry]]
newname = None
if searchmode == 1:
@@ -1202,20 +1228,20 @@ class PythonDomain(Domain):
if objtypes is not None:
if modname and classname:
fullname = modname + '.' + classname + '.' + name
- if fullname in self.objects and self.objects[fullname][2] in objtypes:
+ if fullname in self.objects and self.objects[fullname].objtype in objtypes:
newname = fullname
if not newname:
if modname and modname + '.' + name in self.objects and \
- self.objects[modname + '.' + name][2] in objtypes:
+ self.objects[modname + '.' + name].objtype in objtypes:
newname = modname + '.' + name
- elif name in self.objects and self.objects[name][2] in objtypes:
+ elif name in self.objects and self.objects[name].objtype in objtypes:
newname = name
else:
# "fuzzy" searching mode
searchname = '.' + name
matches = [(oname, self.objects[oname]) for oname in self.objects
if oname.endswith(searchname) and
- self.objects[oname][2] in objtypes]
+ self.objects[oname].objtype in objtypes]
else:
# NOTE: searching for exact match, object type is not considered
if name in self.objects:
@@ -1283,22 +1309,23 @@ class PythonDomain(Domain):
def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str,
contnode: Node) -> Element:
# get additional info for modules
- docname, node_id, synopsis, platform, deprecated = self.modules[name]
+ module = self.modules[name]
title = name
- if synopsis:
- title += ': ' + synopsis
- if deprecated:
+ if module.synopsis:
+ title += ': ' + module.synopsis
+ if module.deprecated:
title += _(' (deprecated)')
- if platform:
- title += ' (' + platform + ')'
- return make_refnode(builder, fromdocname, docname, node_id, contnode, title)
+ if module.platform:
+ title += ' (' + module.platform + ')'
+ return make_refnode(builder, fromdocname, module.docname, module.node_id,
+ contnode, title)
def get_objects(self) -> Iterator[Tuple[str, str, str, str, str, int]]:
- for modname, info in self.modules.items():
- yield (modname, modname, 'module', info[0], info[1], 0)
- for refname, (docname, node_id, type) in self.objects.items():
- if type != 'module': # modules are already handled
- yield (refname, refname, type, docname, node_id, 1)
+ for modname, mod in self.modules.items():
+ yield (modname, modname, 'module', mod.docname, mod.node_id, 0)
+ for refname, obj in self.objects.items():
+ if obj.objtype != 'module': # modules are already handled
+ yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
def get_full_qualified_name(self, node: Element) -> str:
modname = node.get('py:module')