Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion sphinx/ext/autodoc/_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand 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,
Expand Down
85 changes: 29 additions & 56 deletions sphinx/ext/autodoc/directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__)
Expand All @@ -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)
Expand Down Expand Up @@ -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,
)
Expand All @@ -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)
33 changes: 9 additions & 24 deletions tests/test_ext_autodoc/autodoc_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
28 changes: 7 additions & 21 deletions tests/test_ext_autodoc/test_ext_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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}
Expand Down
Loading