from __future__ import annotations
from typing import TYPE_CHECKING
from docutils import nodes
from sphinx.ext.autodoc._directive_options import _process_documenter_options
from sphinx.ext.autodoc._generate import _auto_document_object
from sphinx.ext.autodoc._shared import LOGGER, _AutodocAttrGetter, _AutodocConfig
from sphinx.util.docutils import SphinxDirective, switch_source_input
from sphinx.util.parsing import nested_parse_to_nodes
if TYPE_CHECKING:
from collections.abc import Callable
from docutils.nodes import Node
from docutils.parsers.rst.states import RSTState
from docutils.statemachine import StringList
from sphinx.ext.autodoc._property_types import _AutodocObjType
class DummyOptionSpec(dict[str, 'Callable[[str], str]']): # NoQA: FURB189
"""An option_spec allows any options."""
def __bool__(self) -> bool:
"""Behaves like some options are defined."""
return True
def __getitem__(self, _key: str) -> Callable[[str], str]:
return lambda x: x
def parse_generated_content(
state: RSTState, content: StringList, titles_allowed: bool
) -> list[Node]:
"""Parse an item of content generated by _auto_document_object()."""
with switch_source_input(state, content):
if titles_allowed:
return nested_parse_to_nodes(state, content)
node = nodes.paragraph()
# necessary so that the child nodes get the right source/line set
node.document = state.document
state.nested_parse(content, 0, node, match_titles=False)
return node.children
class AutodocDirective(SphinxDirective):
"""A directive class for all autodoc directives.
It generates the directive lines for the given object,
then parses and returns the generated content.
"""
option_spec = DummyOptionSpec()
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
def run(self) -> list[Node]:
source, lineno = self.get_source_info()
LOGGER.debug('[autodoc] %s:%s: input:\n%s', source, lineno, self.block_text)
# get target object type / strip prefix (auto-)
assert self.name.startswith('auto')
objtype: _AutodocObjType = self.name[4:] # type: ignore[assignment]
env = self.env
#: true if the generated content may contain titles
titles_allowed = True
# process the options with the selected object type's option_spec
try:
documenter_options = _process_documenter_options(
obj_type=objtype,
default_options=self.config.autodoc_default_options,
options=self.options,
)
except (KeyError, ValueError, TypeError) as exc:
# an option is either unknown or has a wrong type
LOGGER.error( # NoQA: TRY400
'An option to %s is either unknown or has an invalid value: %s',
self.name,
exc,
exc_info=exc,
location=(env.current_document.docname, lineno),
)
return []
documenter_options._tab_width = self.state.document.settings.tab_width
# record all filenames as dependencies -- this will at least
# partially make automatic invalidation possible
record_dependencies = self.state.document.settings.record_dependencies
# generate the output
content = _auto_document_object(
name=self.arguments[0],
obj_type=objtype,
current_document=env.current_document,
config=_AutodocConfig.from_config(env.config),
events=env.events,
get_attr=_AutodocAttrGetter(env._registry.autodoc_attrgetters),
more_content=self.content,
options=documenter_options,
# TODO: TYPING: Upstream docutils should expose DependencyList as a MutableSet[str]
record_dependencies=record_dependencies, # type: ignore[arg-type]
ref_context=env.ref_context,
reread_always=env.reread_always,
)
if not content:
return []
LOGGER.debug('[autodoc] output:\n%s', '\n'.join(content))
return parse_generated_content(self.state, content, titles_allowed)