Source code for sphinx.domains.python

"""The Python domain."""

from __future__ import annotations

import builtins
import inspect
import typing
from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, cast

from docutils import nodes
from docutils.parsers.rst import directives

from sphinx import addnodes
from sphinx.domains import Domain, Index, IndexEntry, ObjType
from sphinx.domains.python._annotations import _parse_annotation
from sphinx.domains.python._object import PyObject
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import (
    find_pending_xref_condition,
    make_id,
    make_refnode,
    nested_parse_with_titles,
)

if TYPE_CHECKING:
    from collections.abc import Iterable, Iterator

    from docutils.nodes import Element, Node

    from sphinx.addnodes import desc_signature, pending_xref
    from sphinx.application import Sphinx
    from sphinx.builders import Builder
    from sphinx.environment import BuildEnvironment
    from sphinx.util.typing import ExtensionMetadata, OptionSpec

logger = logging.getLogger(__name__)

pairindextypes = {
    'module': 'module',
    'keyword': 'keyword',
    'operator': 'operator',
    'object': 'object',
    'exception': 'exception',
    'statement': 'statement',
    'builtin': 'built-in function',
}


class ObjectEntry(NamedTuple):
    docname: str
    node_id: str
    objtype: str
    aliased: bool


class ModuleEntry(NamedTuple):
    docname: str
    node_id: str
    synopsis: str
    platform: str
    deprecated: bool


class PyFunction(PyObject):
    """Description of a function."""

    option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()  # noqa: F821
    option_spec.update({
        'async': directives.flag,
    })

    def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
        if 'async' in self.options:
            return [addnodes.desc_sig_keyword('', 'async'),
                    addnodes.desc_sig_space()]
        else:
            return []

    def needs_arglist(self) -> bool:
        return True

    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)
        if 'no-index-entry' 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 = f'built-in function; {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.
        return ''


class PyDecoratorFunction(PyFunction):
    """Description of a decorator."""

    def run(self) -> list[Node]:
        # a decorator function is a function after all
        self.name = 'py:function'
        return super().run()

    def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
        ret = super().handle_signature(sig, signode)
        signode.insert(0, addnodes.desc_addname('@', '@'))
        return ret

    def needs_arglist(self) -> bool:
        return False


class PyVariable(PyObject):
    """Description of a variable."""

    option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
    option_spec.update({
        'type': directives.unchanged,
        'value': directives.unchanged,
    })

    def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
        fullname, prefix = super().handle_signature(sig, signode)

        typ = self.options.get('type')
        if typ:
            annotations = _parse_annotation(typ, self.env)
            signode += addnodes.desc_annotation(typ, '',
                                                addnodes.desc_sig_punctuation('', ':'),
                                                addnodes.desc_sig_space(), *annotations)

        value = self.options.get('value')
        if value:
            signode += addnodes.desc_annotation(value, '',
                                                addnodes.desc_sig_space(),
                                                addnodes.desc_sig_punctuation('', '='),
                                                addnodes.desc_sig_space(),
                                                nodes.Text(value))

        return fullname, prefix

    def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
        name, cls = name_cls
        if modname:
            return _('%s (in module %s)') % (name, modname)
        else:
            return _('%s (built-in variable)') % name


class PyClasslike(PyObject):
    """
    Description of a class-like object (classes, interfaces, exceptions).
    """

    option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
    option_spec.update({
        'final': directives.flag,
    })

    allow_nesting = True

    def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
        if 'final' in self.options:
            return [nodes.Text('final'), addnodes.desc_sig_space(),
                    nodes.Text(self.objtype), addnodes.desc_sig_space()]
        else:
            return [nodes.Text(self.objtype), addnodes.desc_sig_space()]

    def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
        if self.objtype == 'class':
            if not modname:
                return _('%s (built-in class)') % name_cls[0]
            return _('%s (class in %s)') % (name_cls[0], modname)
        elif self.objtype == 'exception':
            return name_cls[0]
        else:
            return ''


class PyMethod(PyObject):
    """Description of a method."""

    option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
    option_spec.update({
        'abstractmethod': directives.flag,
        'async': directives.flag,
        'classmethod': directives.flag,
        'final': directives.flag,
        'staticmethod': directives.flag,
    })

    def needs_arglist(self) -> bool:
        return True

    def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
        prefix: list[nodes.Node] = []
        if 'final' in self.options:
            prefix.append(nodes.Text('final'))
            prefix.append(addnodes.desc_sig_space())
        if 'abstractmethod' in self.options:
            prefix.append(nodes.Text('abstract'))
            prefix.append(addnodes.desc_sig_space())
        if 'async' in self.options:
            prefix.append(nodes.Text('async'))
            prefix.append(addnodes.desc_sig_space())
        if 'classmethod' in self.options:
            prefix.append(nodes.Text('classmethod'))
            prefix.append(addnodes.desc_sig_space())
        if 'staticmethod' in self.options:
            prefix.append(nodes.Text('static'))
            prefix.append(addnodes.desc_sig_space())
        return prefix

    def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
        name, cls = name_cls
        try:
            clsname, methname = name.rsplit('.', 1)
            if modname and self.env.config.add_module_names:
                clsname = f'{modname}.{clsname}'
        except ValueError:
            if modname:
                return _('%s() (in module %s)') % (name, modname)
            else:
                return '%s()' % name

        if 'classmethod' in self.options:
            return _('%s() (%s class method)') % (methname, clsname)
        elif 'staticmethod' in self.options:
            return _('%s() (%s static method)') % (methname, clsname)
        else:
            return _('%s() (%s method)') % (methname, clsname)


class PyClassMethod(PyMethod):
    """Description of a classmethod."""

    option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()

    def run(self) -> list[Node]:
        self.name = 'py:method'
        self.options['classmethod'] = True

        return super().run()


class PyStaticMethod(PyMethod):
    """Description of a staticmethod."""

    option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()

    def run(self) -> list[Node]:
        self.name = 'py:method'
        self.options['staticmethod'] = True

        return super().run()


class PyDecoratorMethod(PyMethod):
    """Description of a decoratormethod."""

    def run(self) -> list[Node]:
        self.name = 'py:method'
        return super().run()

    def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
        ret = super().handle_signature(sig, signode)
        signode.insert(0, addnodes.desc_addname('@', '@'))
        return ret

    def needs_arglist(self) -> bool:
        return False


class PyAttribute(PyObject):
    """Description of an attribute."""

    option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
    option_spec.update({
        'type': directives.unchanged,
        'value': directives.unchanged,
    })

    def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
        fullname, prefix = super().handle_signature(sig, signode)

        typ = self.options.get('type')
        if typ:
            annotations = _parse_annotation(typ, self.env)
            signode += addnodes.desc_annotation(typ, '',
                                                addnodes.desc_sig_punctuation('', ':'),
                                                addnodes.desc_sig_space(),
                                                *annotations)

        value = self.options.get('value')
        if value:
            signode += addnodes.desc_annotation(value, '',
                                                addnodes.desc_sig_space(),
                                                addnodes.desc_sig_punctuation('', '='),
                                                addnodes.desc_sig_space(),
                                                nodes.Text(value))

        return fullname, prefix

    def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
        name, cls = name_cls
        try:
            clsname, attrname = name.rsplit('.', 1)
            if modname and self.env.config.add_module_names:
                clsname = f'{modname}.{clsname}'
        except ValueError:
            if modname:
                return _('%s (in module %s)') % (name, modname)
            else:
                return name

        return _('%s (%s attribute)') % (attrname, clsname)


class PyProperty(PyObject):
    """Description of an attribute."""

    option_spec = PyObject.option_spec.copy()
    option_spec.update({
        'abstractmethod': directives.flag,
        'classmethod': directives.flag,
        'type': directives.unchanged,
    })

    def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
        fullname, prefix = super().handle_signature(sig, signode)

        typ = self.options.get('type')
        if typ:
            annotations = _parse_annotation(typ, self.env)
            signode += addnodes.desc_annotation(typ, '',
                                                addnodes.desc_sig_punctuation('', ':'),
                                                addnodes.desc_sig_space(),
                                                *annotations)

        return fullname, prefix

    def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
        prefix: list[nodes.Node] = []
        if 'abstractmethod' in self.options:
            prefix.append(nodes.Text('abstract'))
            prefix.append(addnodes.desc_sig_space())
        if 'classmethod' in self.options:
            prefix.append(nodes.Text('class'))
            prefix.append(addnodes.desc_sig_space())

        prefix.append(nodes.Text('property'))
        prefix.append(addnodes.desc_sig_space())
        return prefix

    def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
        name, cls = name_cls
        try:
            clsname, attrname = name.rsplit('.', 1)
            if modname and self.env.config.add_module_names:
                clsname = f'{modname}.{clsname}'
        except ValueError:
            if modname:
                return _('%s (in module %s)') % (name, modname)
            else:
                return name

        return _('%s (%s property)') % (attrname, clsname)


class PyModule(SphinxDirective):
    """
    Directive to mark description of a new module.
    """

    has_content = True
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = False
    option_spec: ClassVar[OptionSpec] = {
        'platform': lambda x: x,
        'synopsis': lambda x: x,
        'no-index': directives.flag,
        'no-contents-entry': directives.flag,
        'no-typesetting': directives.flag,
        'noindex': directives.flag,
        'nocontentsentry': directives.flag,
        'deprecated': directives.flag,
    }

    def run(self) -> list[Node]:
        domain = cast(PythonDomain, self.env.get_domain('py'))

        modname = self.arguments[0].strip()
        no_index = 'no-index' in self.options or 'noindex' in self.options
        self.env.ref_context['py:module'] = modname

        content_node: Element = nodes.section()
        # necessary so that the child nodes get the right source/line set
        content_node.document = self.state.document
        nested_parse_with_titles(self.state, self.content, content_node, self.content_offset)

        ret: list[Node] = []
        if not no_index:
            # note module to the domain
            node_id = make_id(self.env, self.state.document, 'module', modname)
            target = nodes.target('', '', ids=[node_id], ismod=True)
            self.set_source_info(target)
            self.state.document.note_explicit_target(target)

            domain.note_module(modname,
                               node_id,
                               self.options.get('synopsis', ''),
                               self.options.get('platform', ''),
                               'deprecated' in self.options)
            domain.note_object(modname, 'module', node_id, location=target)

            # the platform and synopsis aren't printed; in fact, they are only
            # used in the modindex currently
            indextext = f'module; {modname}'
            inode = addnodes.index(entries=[('pair', indextext, node_id, '', None)])
            # The node order is: index node first, then target node.
            ret.append(inode)
            ret.append(target)
        ret.extend(content_node.children)
        return ret


class PyCurrentModule(SphinxDirective):
    """
    This directive is just to tell Sphinx that we're documenting
    stuff in module foo, but links to module foo won't lead here.
    """

    has_content = False
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = False
    option_spec: ClassVar[OptionSpec] = {}

    def run(self) -> list[Node]:
        modname = self.arguments[0].strip()
        if modname == 'None':
            self.env.ref_context.pop('py:module', None)
        else:
            self.env.ref_context['py:module'] = modname
        return []


class PyXRefRole(XRefRole):
    def process_link(self, env: BuildEnvironment, refnode: Element,
                     has_explicit_title: bool, title: str, target: str) -> tuple[str, str]:
        refnode['py:module'] = env.ref_context.get('py:module')
        refnode['py:class'] = env.ref_context.get('py:class')
        if not has_explicit_title:
            title = title.lstrip('.')    # only has a meaning for the target
            target = target.lstrip('~')  # only has a meaning for the title
            # if the first character is a tilde, don't display the module/class
            # parts of the contents
            if title[0:1] == '~':
                title = title[1:]
                dot = title.rfind('.')
                if dot != -1:
                    title = title[dot + 1:]
        # if the first character is a dot, search more specific namespaces first
        # else search builtins first
        if target[0:1] == '.':
            target = target[1:]
            refnode['refspecific'] = True
        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)
            # removing list items while iterating the list needs reversed()
            for field in reversed(fields):
                field_name = cast(nodes.field_body, field[0]).astext().strip()
                if field_name == 'meta' or field_name.startswith('meta '):
                    node.remove(field)


class PythonModuleIndex(Index):
    """
    Index subclass to provide the Python module index.
    """

    name = 'modindex'
    localname = _('Python Module Index')
    shortname = _('modules')

    def generate(self, docnames: Iterable[str] | None = None,
                 ) -> tuple[list[tuple[str, list[IndexEntry]]], bool]:
        content: dict[str, list[IndexEntry]] = {}
        # list of prefixes to ignore
        ignores: list[str] = self.domain.env.config['modindex_common_prefix']
        ignores = sorted(ignores, key=len, reverse=True)
        # list of all modules, sorted by module name
        modules = sorted(self.domain.data['modules'].items(),
                         key=lambda x: x[0].lower())
        # sort out collapsible modules
        prev_modname = ''
        num_toplevels = 0
        for modname, (docname, node_id, synopsis, platforms, deprecated) in modules:
            if docnames and docname not in docnames:
                continue

            for ignore in ignores:
                if modname.startswith(ignore):
                    modname = modname[len(ignore):]
                    stripped = ignore
                    break
            else:
                stripped = ''

            # we stripped the whole module name?
            if not modname:
                modname, stripped = stripped, ''

            entries = content.setdefault(modname[0].lower(), [])

            package = modname.split('.')[0]
            if package != modname:
                # it's a submodule
                if prev_modname == package:
                    # first submodule - make parent a group head
                    if entries:
                        last = entries[-1]
                        entries[-1] = IndexEntry(last[0], 1, last[2], last[3],
                                                 last[4], last[5], last[6])
                elif not prev_modname.startswith(package):
                    # submodule without parent in list, add dummy entry
                    entries.append(IndexEntry(stripped + package, 1, '', '', '', '', ''))
                subtype = 2
            else:
                num_toplevels += 1
                subtype = 0

            qualifier = _('Deprecated') if deprecated else ''
            entries.append(IndexEntry(stripped + modname, subtype, docname,
                                      node_id, platforms, qualifier, synopsis))
            prev_modname = modname

        # apply heuristics when to collapse modindex at page load:
        # only collapse if number of toplevel modules is larger than
        # number of submodules
        collapse = len(modules) - num_toplevels < num_toplevels

        # sort by first letter
        sorted_content = sorted(content.items())

        return sorted_content, collapse


[docs] class PythonDomain(Domain): """Python language domain.""" name = 'py' label = 'Python' object_types: dict[str, ObjType] = { 'function': ObjType(_('function'), 'func', 'obj'), 'data': ObjType(_('data'), 'data', 'obj'), 'class': ObjType(_('class'), 'class', 'exc', 'obj'), 'exception': ObjType(_('exception'), 'exc', 'class', 'obj'), 'method': ObjType(_('method'), 'meth', 'obj'), 'classmethod': ObjType(_('class method'), 'meth', 'obj'), 'staticmethod': ObjType(_('static method'), 'meth', 'obj'), 'attribute': ObjType(_('attribute'), 'attr', 'obj'), 'property': ObjType(_('property'), 'attr', '_prop', 'obj'), 'module': ObjType(_('module'), 'mod', 'obj'), } directives = { 'function': PyFunction, 'data': PyVariable, 'class': PyClasslike, 'exception': PyClasslike, 'method': PyMethod, 'classmethod': PyClassMethod, 'staticmethod': PyStaticMethod, 'attribute': PyAttribute, 'property': PyProperty, 'module': PyModule, 'currentmodule': PyCurrentModule, 'decorator': PyDecoratorFunction, 'decoratormethod': PyDecoratorMethod, } roles = { 'data': PyXRefRole(), 'exc': PyXRefRole(), 'func': PyXRefRole(fix_parens=True), 'class': PyXRefRole(), 'const': PyXRefRole(), 'attr': PyXRefRole(), 'meth': PyXRefRole(fix_parens=True), 'mod': PyXRefRole(), 'obj': PyXRefRole(), } initial_data: dict[str, dict[str, tuple[Any]]] = { 'objects': {}, # fullname -> docname, objtype 'modules': {}, # modname -> docname, synopsis, platform, deprecated } indices = [ PythonModuleIndex, ] @property def objects(self) -> dict[str, ObjectEntry]: return self.data.setdefault('objects', {}) # fullname -> ObjectEntry
[docs] def note_object(self, name: str, objtype: str, node_id: str, aliased: bool = False, location: Any = None) -> None: """Note a python object for cross reference. .. versionadded:: 2.1 """ if name in self.objects: other = self.objects[name] if other.aliased and aliased is False: # The original definition found. Override it! pass elif other.aliased is False and aliased: # The original definition is already registered. return else: # duplicated logger.warning(__('duplicate object description of %s, ' 'other instance in %s, use :no-index: for one of them'), name, other.docname, location=location) self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype, aliased)
@property def modules(self) -> dict[str, ModuleEntry]: return self.data.setdefault('modules', {}) # modname -> ModuleEntry
[docs] def note_module(self, name: str, node_id: str, synopsis: str, platform: str, deprecated: bool) -> None: """Note a python module for cross reference. .. versionadded:: 2.1 """ self.modules[name] = ModuleEntry(self.env.docname, node_id, synopsis, platform, deprecated)
def clear_doc(self, docname: str) -> None: for fullname, obj in list(self.objects.items()): if obj.docname == docname: del self.objects[fullname] 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[str, Any]) -> None: # XXX check duplicates? 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 | None, searchmode: int = 0, ) -> 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. """ # skip parens if name[-2:] == '()': name = name[:-2] if not name: return [] matches: list[tuple[str, ObjectEntry]] = [] newname = None if searchmode == 1: if type is None: objtypes: list[str] | None = list(self.object_types) else: objtypes = self.objtypes_for_role(type) if objtypes is not None: if modname and classname: fullname = modname + '.' + classname + '.' + name 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].objtype in objtypes: newname = modname + '.' + name 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].objtype in objtypes] else: # NOTE: searching for exact match, object type is not considered if name in self.objects: newname = name elif type == 'mod': # only exact matches allowed for modules return [] elif classname and classname + '.' + name in self.objects: newname = classname + '.' + name elif modname and modname + '.' + name in self.objects: newname = modname + '.' + name elif modname and classname and \ modname + '.' + classname + '.' + name in self.objects: newname = modname + '.' + classname + '.' + name if newname is not None: matches.append((newname, self.objects[newname])) return matches def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, type: str, target: str, node: pending_xref, contnode: Element, ) -> Element | None: modname = node.get('py:module') clsname = node.get('py:class') searchmode = 1 if node.hasattr('refspecific') else 0 matches = self.find_obj(env, modname, clsname, target, type, searchmode) if not matches and type == 'attr': # fallback to meth (for property; Sphinx 2.4.x) # this ensures that `:attr:` role continues to refer to the old property entry # that defined by ``method`` directive in old reST files. matches = self.find_obj(env, modname, clsname, target, 'meth', searchmode) if not matches and type == 'meth': # fallback to attr (for property) # this ensures that `:meth:` in the old reST files can refer to the property # entry that defined by ``property`` directive. # # Note: _prop is a secret role only for internal look-up. matches = self.find_obj(env, modname, clsname, target, '_prop', searchmode) if not matches: return None elif len(matches) > 1: canonicals = [m for m in matches if not m[1].aliased] if len(canonicals) == 1: matches = canonicals else: logger.warning(__('more than one target found for cross-reference %r: %s'), target, ', '.join(match[0] for match in matches), type='ref', subtype='python', location=node) name, obj = matches[0] if obj[2] == 'module': return self._make_module_refnode(builder, fromdocname, name, contnode) else: # determine the content of the reference by conditions content = find_pending_xref_condition(node, 'resolved') if content: children = content.children else: # if not found, use contnode children = [contnode] return make_refnode(builder, fromdocname, obj[0], obj[1], children, name) def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element, ) -> list[tuple[str, Element]]: modname = node.get('py:module') clsname = node.get('py:class') results: list[tuple[str, Element]] = [] # always search in "refspecific" mode with the :any: role matches = self.find_obj(env, modname, clsname, target, None, 1) multiple_matches = len(matches) > 1 for name, obj in matches: if multiple_matches and obj.aliased: # Skip duplicated matches continue if obj[2] == 'module': results.append(('py:mod', self._make_module_refnode(builder, fromdocname, name, contnode))) else: # determine the content of the reference by conditions content = find_pending_xref_condition(node, 'resolved') if content: children = content.children else: # if not found, use contnode children = [contnode] role = 'py:' + self.role_for_objtype(obj[2]) # type: ignore[operator] results.append((role, make_refnode(builder, fromdocname, obj[0], obj[1], children, name))) return results def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, contnode: Node) -> Element: # get additional info for modules module = self.modules[name] title = name if module.synopsis: title += ': ' + module.synopsis if module.deprecated: title += _(' (deprecated)') 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, 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 if obj.aliased: # aliased names are not full-text searchable. yield (refname, refname, obj.objtype, obj.docname, obj.node_id, -1) else: yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1) def get_full_qualified_name(self, node: Element) -> str | None: modname = node.get('py:module') clsname = node.get('py:class') target = node.get('reftarget') if target is None: return None else: return '.'.join(filter(None, [modname, clsname, target]))
def builtin_resolver(app: Sphinx, env: BuildEnvironment, node: pending_xref, contnode: Element) -> Element | None: """Do not emit nitpicky warnings for built-in types.""" def istyping(s: str) -> bool: if s.startswith('typing.'): s = s.split('.', 1)[1] return s in typing.__all__ if node.get('refdomain') != 'py': return None elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None': return contnode elif node.get('reftype') in ('class', 'obj', 'exc'): reftarget = node.get('reftarget') if inspect.isclass(getattr(builtins, reftarget, None)): # built-in class return contnode if istyping(reftarget): # typing class return contnode return None def setup(app: Sphinx) -> ExtensionMetadata: app.setup_extension('sphinx.directives') app.add_domain(PythonDomain) app.add_config_value('python_use_unqualified_type_names', False, 'env') app.add_config_value( 'python_maximum_signature_line_length', None, 'env', {int, type(None)}, ) app.add_config_value('python_display_short_literal_types', False, 'env') app.connect('object-description-transform', filter_meta_fields) app.connect('missing-reference', builtin_resolver, priority=900) return { 'version': 'builtin', 'env_version': 4, 'parallel_read_safe': True, 'parallel_write_safe': True, }