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
84 changes: 40 additions & 44 deletions sphinx/ext/autodoc/_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,11 @@ def _docstring_lines_for_props(
/,
*,
props: _ItemProperties,
real_modname: str | None,
parent_modname: str | None,
events: EventManager,
options: _AutoDocumenterOptions,
) -> tuple[str, ...]:
guess_modname = props._obj___module__ or props.module_name
if props.obj_type in {'class', 'exception'}:
# Do not pass real_modname and use the name from the __module__
# attribute of the class.
# If a class gets imported into the module real_modname
# the analyzer won't find the source of the class, if
# it looks in real_modname.
real_modname = guess_modname
else:
real_modname = real_modname or guess_modname
try:
analyzer = ModuleAnalyzer.for_module(real_modname)
# parse right now, to get PycodeErrors on parsing (results will
# be cached anyway)
analyzer.analyze()
except PycodeError as exc:
logger.debug('[autodoc] module analyzer failed: %s', exc)
# no source file -- e.g. for builtin and C modules
analyzer = None

attr_docs = {} if analyzer is None else analyzer.attr_docs
attr_docs = _attr_docs_for_props(props, parent_modname=parent_modname)
prepared_docstrings = _prepare_docstrings(
docstrings, props=props, attr_docs=attr_docs
)
Expand All @@ -69,41 +49,57 @@ def _docstring_lines_for_props(
return tuple(docstring_lines)


def _attr_docs_for_props(
props: _ItemProperties, *, parent_modname: str | None
) -> Mapping[tuple[str, str], list[str]]:
if props.obj_type in {'class', 'exception'}:
# If a class gets imported into the module ``parent_modname``
# the analyzer won't find the source of the class,
# if it looks in ``parent_modname``.
real_modname = props.module_name
elif parent_modname is None:
real_modname = props.canonical_module_name
else:
real_modname = parent_modname

try:
analyzer = ModuleAnalyzer.for_module(real_modname)
# parse right now, to get PycodeErrors on parsing (results will
# be cached anyway)
analyzer.analyze()
except PycodeError as exc:
logger.debug('[autodoc] module analyzer failed: %s', exc)
# no source file -- e.g. for builtin and C modules
attr_docs = {}
else:
attr_docs = analyzer.attr_docs
return attr_docs


def _prepare_docstrings(
docstrings: list[list[str]] | None,
*,
props: _ItemProperties,
attr_docs: Mapping[tuple[str, str], list[str]],
) -> list[list[str]] | None:
"""Add content from docstrings, attribute documentation and user."""
if docstrings is not None and not docstrings:
# append at least a dummy docstring, so that the event
# autodoc-process-docstring is fired and can add some
# content if desired
docstrings = [[]]

if props.obj_type in {'data', 'attribute'}:
return docstrings

if props.obj_type in {'class', 'exception'}:
real_module = props._obj___module__ or props.module_name
if props.module_name != real_module:
try:
# override analyzer to obtain doc-comment around its definition.
ma = ModuleAnalyzer.for_module(props.module_name)
ma.analyze()
attr_docs = ma.attr_docs
except PycodeError:
pass

# add content from attribute documentation
if attr_docs and props.parts:
if props.obj_type not in {'data', 'attribute'} and props.parts:
key = ('.'.join(props.parent_names), props.name)
if key in attr_docs:
try:
# make a copy of docstring for attributes to avoid cache
# the change of autodoc-process-docstring event.
return [list(attr_docs[key])]
except KeyError:
pass

if docstrings is None:
return None
if not docstrings:
# append at least a dummy docstring, so that the event
# autodoc-process-docstring is fired and can add some
# content if desired
docstrings.append([])
return docstrings


Expand Down
35 changes: 14 additions & 21 deletions sphinx/ext/autodoc/_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from sphinx.util.typing import restify, stringify_annotation

if TYPE_CHECKING:
from collections.abc import Iterator, Mapping
from collections.abc import Iterator
from typing import Literal

from sphinx.config import Config
Expand All @@ -32,7 +32,7 @@

def _generate_directives(
more_content: StringList | None = None,
real_modname: str | None = None,
parent_modname: str | None = None,
check_module: bool = False,
all_members: bool = False,
*,
Expand All @@ -49,26 +49,23 @@ def _generate_directives(
) -> None:
"""Generate reST for the object given by *props*, and possibly for its members.

If *more_content* is given, include that content. If *real_modname* is
If *more_content* is given, include that content. If *parent_modname* is
given, use that module name to find attribute docs. If *check_module* is
True, only generate if the object is defined in the module name it is
imported from. If *all_members* is True, document all members.
"""
# If there is no real module defined, figure out which to use.
# If there is no parent module specified, figure out which to use.
# The real module is used in the module analyzer to look up the module
# where the attribute documentation would actually be found in.
# This is used for situations where you have a module that collects the
# functions and classes of internal submodules.
guess_modname = props._obj___module__ or props.module_name
if props.obj_type in {'class', 'exception'}:
# Do not pass real_modname and use the name from the __module__
# attribute of the class.
# If a class gets imported into the module real_modname
# the analyzer won't find the source of the class, if
# it looks in real_modname.
real_modname = guess_modname
if parent_modname is None or props.obj_type in {'class', 'exception'}:
# If a class gets imported into the module ``parent_modname``
# the analyzer won't find the source of the class,
# if it looks in ``parent_modname``.
real_modname = props.canonical_module_name
else:
real_modname = real_modname or guess_modname
real_modname = parent_modname

# try to also get a source code analyzer for attribute docs
try:
Expand All @@ -95,10 +92,10 @@ def _generate_directives(
):
record_dependencies.add(module_spec.origin)

if real_modname != guess_modname:
if real_modname != props.canonical_module_name:
# Add module to dependency list if target object is defined in other module.
try:
srcname, _ = ModuleAnalyzer.get_module_source(guess_modname)
srcname, _ = ModuleAnalyzer.get_module_source(props.canonical_module_name)
record_dependencies.add(str(srcname))
except PycodeError:
pass
Expand All @@ -123,10 +120,8 @@ def _generate_directives(
analyzer_source = '' if analyzer is None else analyzer.srcname
_add_directive_lines(
more_content=more_content,
attr_docs={} if analyzer is None else analyzer.attr_docs,
is_final=analyzer is not None and props.dotted_parts in analyzer.finals,
config=config,
events=events,
indent=indent,
options=options,
props=props,
Expand Down Expand Up @@ -156,10 +151,8 @@ def _generate_directives(
def _add_directive_lines(
*,
more_content: StringList | None,
attr_docs: Mapping[tuple[str, str], list[str]],
is_final: bool,
config: Config,
events: EventManager,
indent: str,
options: _AutoDocumenterOptions,
props: _ItemProperties,
Expand Down Expand Up @@ -244,7 +237,7 @@ def _document_members(
events=events,
get_attr=get_attr,
options=options,
real_modname=real_modname,
parent_modname=real_modname,
props=props,
)

Expand All @@ -261,7 +254,7 @@ def _document_members(
# whatever objects we deduced should not have changed.
_generate_directives(
more_content=None,
real_modname=real_modname,
parent_modname=real_modname,
check_module=members_check_module and not is_attr,
all_members=True,
config=config,
Expand Down
4 changes: 2 additions & 2 deletions sphinx/ext/autodoc/_member_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def _gather_members(
events: EventManager,
get_attr: _AttrGetter,
options: _AutoDocumenterOptions,
real_modname: str,
parent_modname: str,
props: _ItemProperties,
) -> list[tuple[_ItemProperties, bool, str]]:
"""Generate reST for member documentation.
Expand Down Expand Up @@ -183,7 +183,7 @@ def _gather_members(
events=events,
get_attr=get_attr,
options=options,
real_modname=real_modname,
parent_modname=parent_modname,
)
if member_props is None:
continue
Expand Down
6 changes: 6 additions & 0 deletions sphinx/ext/autodoc/_property_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ def dotted_parts(self) -> str:
def _groupwise_order_key(self) -> int:
return 0

@property
def canonical_module_name(self) -> str:
if self._obj___module__ is not None:
return self._obj___module__
return self.module_name


@dataclasses.dataclass(frozen=False, kw_only=True, slots=True)
class _ModuleProperties(_ItemProperties):
Expand Down
39 changes: 18 additions & 21 deletions sphinx/ext/autodoc/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ def _load_object_by_name(
events: EventManager,
get_attr: _AttrGetter,
options: _AutoDocumenterOptions,
real_modname: str | None = None,
parent_modname: str | None = None,
) -> _ItemProperties | None:
"""Import and load the object given by *name*."""
parsed = _parse_name(
Expand Down Expand Up @@ -926,7 +926,7 @@ def _load_object_by_name(
props.docstring_lines = _docstring_lines_for_props(
docstrings,
props=props,
real_modname=real_modname,
parent_modname=parent_modname,
events=events,
options=options,
)
Expand Down Expand Up @@ -1250,23 +1250,21 @@ def _format_signatures(
if props.obj_type in {'module', 'data', 'type'}:
signatures[1:] = () # discard all signatures save the first

if real_modname := props._obj___module__ or props.module_name:
try:
analyzer = ModuleAnalyzer.for_module(real_modname)
# parse right now, to get PycodeErrors on parsing (results will
# be cached anyway)
analyzer.analyze()
except PycodeError as exc:
logger.debug('[autodoc] module analyzer failed: %s', exc)
# no source file -- e.g. for builtin and C modules
analyzer = None
analyzer_overloads: dict[str, list[Signature]] = {}
try:
analyzer = ModuleAnalyzer.for_module(props.canonical_module_name)
# parse right now, to get PycodeErrors on parsing (results will
# be cached anyway)
analyzer.analyze()
except PycodeError as exc:
logger.debug('[autodoc] module analyzer failed: %s', exc)
# no source file -- e.g. for builtin and C modules
else:
analyzer = None
analyzer_overloads = analyzer.overloads

if props.obj_type in {'function', 'decorator'}:
overloaded = (
analyzer is not None
and props.dotted_parts in analyzer.overloads
props.dotted_parts in analyzer_overloads
and config.autodoc_typehints != 'none'
)
is_singledispatch = inspect.is_singledispatch_function(props._obj)
Expand Down Expand Up @@ -1311,12 +1309,12 @@ def _format_signatures(
options=options,
props=dispatch_props,
)
if overloaded and analyzer is not None:
if overloaded:
actual = inspect.signature(
props._obj, type_aliases=config.autodoc_type_aliases
)
obj_globals = safe_getattr(props._obj, '__globals__', {})
overloads = analyzer.overloads[props.dotted_parts]
overloads = analyzer_overloads[props.dotted_parts]
for overload in overloads:
overload = _merge_default_value(actual, overload)
overload = evaluate_signature(
Expand Down Expand Up @@ -1374,8 +1372,7 @@ def _format_signatures(

if props.obj_type == 'method':
overloaded = (
analyzer is not None
and props.dotted_parts in analyzer.overloads
props.dotted_parts in analyzer_overloads
and config.autodoc_typehints != 'none'
)
meth = parent.__dict__.get(props.name)
Expand Down Expand Up @@ -1423,7 +1420,7 @@ def _format_signatures(
options=options,
props=dispatch_props,
)
if overloaded and analyzer is not None:
if overloaded:
from sphinx.ext.autodoc._property_types import _FunctionDefProperties

assert isinstance(props, _FunctionDefProperties)
Expand All @@ -1434,7 +1431,7 @@ def _format_signatures(
)

obj_globals = safe_getattr(props._obj, '__globals__', {})
overloads = analyzer.overloads[props.dotted_parts]
overloads = analyzer_overloads[props.dotted_parts]
for overload in overloads:
overload = _merge_default_value(actual, overload)
overload = evaluate_signature(
Expand Down
12 changes: 0 additions & 12 deletions sphinx/ext/autosummary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,18 +360,6 @@ def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]:
items.append((display_name, '', '', real_name))
continue

# try to also get a source code analyzer for attribute docs
real_module = props._obj___module__ or props.module_name
try:
analyzer = ModuleAnalyzer.for_module(real_module)
# parse right now, to get PycodeErrors on parsing (results will
# be cached anyway)
analyzer.analyze()
except PycodeError as err:
logger.debug('[autodoc] module analyzer failed: %s', err)
# no source file -- e.g. for builtin and C modules
analyzer = None

# -- Grab the signature

if signatures_option == 'none':
Expand Down
Loading