From 49cc306ff97d583db3091fe501d93440ec2344ae Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Wed, 12 Nov 2025 03:27:34 +0000 Subject: [PATCH] Introduce ``_auto_document_object()`` --- sphinx/ext/autodoc/_generate.py | 49 ++++++++++++- sphinx/ext/autodoc/directive.py | 85 ++++++++-------------- tests/test_ext_autodoc/autodoc_util.py | 33 +++------ tests/test_ext_autodoc/test_ext_autodoc.py | 28 ++----- 4 files changed, 93 insertions(+), 102 deletions(-) diff --git a/sphinx/ext/autodoc/_generate.py b/sphinx/ext/autodoc/_generate.py index 6c47e811b9b..d416c694b7b 100644 --- a/sphinx/ext/autodoc/_generate.py +++ b/sphinx/ext/autodoc/_generate.py @@ -6,6 +6,7 @@ from docutils.statemachine import StringList from sphinx.errors import PycodeError +from sphinx.ext.autodoc._loader import _load_object_by_name from sphinx.ext.autodoc._member_finder import _gather_members from sphinx.ext.autodoc._renderer import _add_content, _directive_header_lines from sphinx.ext.autodoc._sentinels import ALL @@ -22,13 +23,59 @@ from sphinx.environment import _CurrentDocument from sphinx.events import EventManager from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions - from sphinx.ext.autodoc._property_types import _ItemProperties + from sphinx.ext.autodoc._property_types import _AutodocObjType, _ItemProperties from sphinx.ext.autodoc._shared import _AttrGetter, _AutodocConfig from sphinx.util.typing import _RestifyMode logger = logging.getLogger('sphinx.ext.autodoc') +def _auto_document_object( + *, + config: _AutodocConfig, + current_document: _CurrentDocument, + events: EventManager, + get_attr: _AttrGetter, + more_content: StringList | None, + name: str, + obj_type: _AutodocObjType, + options: _AutoDocumenterOptions, + record_dependencies: set[str], + ref_context: Mapping[str, str | None], + reread_always: MutableSet[str], +) -> StringList | None: + props = _load_object_by_name( + name=name, + objtype=obj_type, + current_document=current_document, + config=config, + events=events, + get_attr=get_attr, + options=options, + ref_context=ref_context, + reread_always=reread_always, + ) + if props is None: + return None + + result = StringList() + _generate_directives( + more_content=more_content, + config=config, + current_document=current_document, + events=events, + get_attr=get_attr, + indent='', + options=options, + props=props, + record_dependencies=record_dependencies, + ref_context=ref_context, + reread_always=reread_always, + result=result, + ) + return result + + def _generate_directives( more_content: StringList | None = None, parent_modname: str | None = None, diff --git a/sphinx/ext/autodoc/directive.py b/sphinx/ext/autodoc/directive.py index d1d80f74b2b..127d06fd530 100644 --- a/sphinx/ext/autodoc/directive.py +++ b/sphinx/ext/autodoc/directive.py @@ -3,13 +3,9 @@ from typing import TYPE_CHECKING from docutils import nodes -from docutils.statemachine import StringList -from sphinx.ext.autodoc._directive_options import ( - _process_documenter_options, -) -from sphinx.ext.autodoc._generate import _generate_directives -from sphinx.ext.autodoc._loader import _load_object_by_name +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 _AutodocAttrGetter, _AutodocConfig from sphinx.util import logging from sphinx.util.docutils import SphinxDirective, switch_source_input @@ -20,6 +16,9 @@ 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 logger = logging.getLogger(__name__) @@ -39,7 +38,7 @@ def __getitem__(self, _key: str) -> Callable[[str], str]: def parse_generated_content( state: RSTState, content: StringList, titles_allowed: bool ) -> list[Node]: - """Parse an item of content generated by _generate_directives().""" + """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) @@ -75,18 +74,19 @@ def run(self) -> list[Node]: source, lineno = (None, None) logger.debug('[autodoc] %s:%s: input:\n%s', source, lineno, self.block_text) - registry = self.env._registry + # get target object type / strip prefix (auto-) + assert self.name.startswith('auto') + objtype: _AutodocObjType = self.name[4:] # type: ignore[assignment] - # look up target object type - objtype = self.name[4:] # strip prefix (auto-). + env = self.env #: true if the generated content may contain titles titles_allowed = True - # process the options with the selected object types's option_spec + # process the options with the selected object type's option_spec try: documenter_options = _process_documenter_options( - obj_type=objtype, # type: ignore[arg-type] + obj_type=objtype, default_options=self.config.autodoc_default_options, options=self.options, ) @@ -96,59 +96,32 @@ def run(self) -> list[Node]: 'An option to %s is either unknown or has an invalid value: %s', self.name, exc, - location=(self.env.current_document.docname, lineno), + location=(env.current_document.docname, lineno), ) return [] documenter_options._tab_width = self.state.document.settings.tab_width - # generate the output - get_attr = _AutodocAttrGetter(registry.autodoc_attrgetters) - name = self.arguments[0] - env = self.env - config = _AutodocConfig.from_config(env.config) - current_document = env.current_document - events = env.events - ref_context = env.ref_context - reread_always = env.reread_always - - props = _load_object_by_name( - name=name, - objtype=objtype, # type: ignore[arg-type] - current_document=current_document, - config=config, - events=events, - get_attr=get_attr, - options=documenter_options, - ref_context=ref_context, - reread_always=reread_always, - ) - if props is None: - return [] + # record all filenames as dependencies -- this will at least + # partially make automatic invalidation possible + record_dependencies = self.state.document.settings.record_dependencies - record_dependencies: set[str] = set() - result = StringList() - _generate_directives( + # 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, - config=config, - current_document=current_document, - events=events, - get_attr=get_attr, - indent='', options=documenter_options, - props=props, record_dependencies=record_dependencies, - ref_context=ref_context, - reread_always=reread_always, - result=result, + ref_context=env.ref_context, + reread_always=env.reread_always, ) - if not result: + if not content: return [] - logger.debug('[autodoc] output:\n%s', '\n'.join(result)) - - # record all filenames as dependencies -- this will at least - # partially make automatic invalidation possible - for fn in record_dependencies: - self.state.document.settings.record_dependencies.add(fn) + logger.debug('[autodoc] output:\n%s', '\n'.join(content)) - return parse_generated_content(self.state, result, titles_allowed) + return parse_generated_content(self.state, content, titles_allowed) diff --git a/tests/test_ext_autodoc/autodoc_util.py b/tests/test_ext_autodoc/autodoc_util.py index 64b31825a49..f5eb2d44eef 100644 --- a/tests/test_ext_autodoc/autodoc_util.py +++ b/tests/test_ext_autodoc/autodoc_util.py @@ -3,13 +3,10 @@ from types import SimpleNamespace from typing import TYPE_CHECKING -from docutils.statemachine import StringList - from sphinx.environment import _CurrentDocument from sphinx.events import EventManager from sphinx.ext.autodoc._directive_options import _process_documenter_options -from sphinx.ext.autodoc._generate import _generate_directives -from sphinx.ext.autodoc._loader import _load_object_by_name +from sphinx.ext.autodoc._generate import _auto_document_object from sphinx.ext.autodoc._shared import _AutodocConfig from sphinx.util.inspect import safe_getattr @@ -65,34 +62,22 @@ def do_autodoc( options=options, ) - props = _load_object_by_name( + content = _auto_document_object( name=name, - objtype=obj_type, - current_document=current_document, + obj_type=obj_type, config=config, + current_document=current_document, events=events, get_attr=safe_getattr, + more_content=None, options=doc_options, + record_dependencies=set(), ref_context=ref_context, reread_always=reread_always, ) if expect_import_error: - assert props is None + assert content is None return [] - assert props is not None - result = StringList() - _generate_directives( - config=config, - current_document=current_document, - events=events, - get_attr=safe_getattr, - indent='', - options=doc_options, - props=props, - record_dependencies=set(), - ref_context=ref_context, - reread_always=reread_always, - result=result, - ) - return result.data + assert content is not None + return content.data diff --git a/tests/test_ext_autodoc/test_ext_autodoc.py b/tests/test_ext_autodoc/test_ext_autodoc.py index 1d037288038..3e4691c9978 100644 --- a/tests/test_ext_autodoc/test_ext_autodoc.py +++ b/tests/test_ext_autodoc/test_ext_autodoc.py @@ -14,7 +14,6 @@ from warnings import catch_warnings import pytest -from docutils.statemachine import StringList from sphinx.environment import _CurrentDocument from sphinx.ext.autodoc._directive_options import ( @@ -23,8 +22,7 @@ ) from sphinx.ext.autodoc._docstrings import _get_docstring_lines from sphinx.ext.autodoc._documenters import Documenter -from sphinx.ext.autodoc._generate import _generate_directives -from sphinx.ext.autodoc._loader import _load_object_by_name +from sphinx.ext.autodoc._generate import _auto_document_object from sphinx.ext.autodoc._property_types import _ItemProperties from sphinx.ext.autodoc._sentinels import ALL from sphinx.ext.autodoc._shared import _AutodocAttrGetter, _AutodocConfig @@ -217,31 +215,19 @@ def _assert_getter_works( current_document = _CurrentDocument() events = FakeEvents() - props = _load_object_by_name( - name=name, - objtype=objtype, - current_document=current_document, + _auto_document_object( config=config, + current_document=current_document, events=events, get_attr=get_attr, + more_content=None, + name=name, + obj_type=objtype, options=options, + record_dependencies=set(), ref_context={}, reread_always=set(), ) - if props is not None: - _generate_directives( - config=config, - current_document=current_document, - events=events, - get_attr=get_attr, - indent='', - options=options, - props=props, - record_dependencies=set(), - ref_context={}, - reread_always=set(), - result=StringList(), - ) hooked_members = {s[1] for s in getattr_spy} documented_members = {s[1] for s in processed_signatures}