Skip to content

Commit 08c7ee3

Browse files
authored
Use parent_modname and canonical_module_name (#14017)
1 parent bb6b79b commit 08c7ee3

File tree

6 files changed

+80
-100
lines changed

6 files changed

+80
-100
lines changed

sphinx/ext/autodoc/_docstrings.py

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -32,31 +32,11 @@ def _docstring_lines_for_props(
3232
/,
3333
*,
3434
props: _ItemProperties,
35-
real_modname: str | None,
35+
parent_modname: str | None,
3636
events: EventManager,
3737
options: _AutoDocumenterOptions,
3838
) -> tuple[str, ...]:
39-
guess_modname = props._obj___module__ or props.module_name
40-
if props.obj_type in {'class', 'exception'}:
41-
# Do not pass real_modname and use the name from the __module__
42-
# attribute of the class.
43-
# If a class gets imported into the module real_modname
44-
# the analyzer won't find the source of the class, if
45-
# it looks in real_modname.
46-
real_modname = guess_modname
47-
else:
48-
real_modname = real_modname or guess_modname
49-
try:
50-
analyzer = ModuleAnalyzer.for_module(real_modname)
51-
# parse right now, to get PycodeErrors on parsing (results will
52-
# be cached anyway)
53-
analyzer.analyze()
54-
except PycodeError as exc:
55-
logger.debug('[autodoc] module analyzer failed: %s', exc)
56-
# no source file -- e.g. for builtin and C modules
57-
analyzer = None
58-
59-
attr_docs = {} if analyzer is None else analyzer.attr_docs
39+
attr_docs = _attr_docs_for_props(props, parent_modname=parent_modname)
6040
prepared_docstrings = _prepare_docstrings(
6141
docstrings, props=props, attr_docs=attr_docs
6242
)
@@ -69,41 +49,57 @@ def _docstring_lines_for_props(
6949
return tuple(docstring_lines)
7050

7151

52+
def _attr_docs_for_props(
53+
props: _ItemProperties, *, parent_modname: str | None
54+
) -> Mapping[tuple[str, str], list[str]]:
55+
if props.obj_type in {'class', 'exception'}:
56+
# If a class gets imported into the module ``parent_modname``
57+
# the analyzer won't find the source of the class,
58+
# if it looks in ``parent_modname``.
59+
real_modname = props.module_name
60+
elif parent_modname is None:
61+
real_modname = props.canonical_module_name
62+
else:
63+
real_modname = parent_modname
64+
65+
try:
66+
analyzer = ModuleAnalyzer.for_module(real_modname)
67+
# parse right now, to get PycodeErrors on parsing (results will
68+
# be cached anyway)
69+
analyzer.analyze()
70+
except PycodeError as exc:
71+
logger.debug('[autodoc] module analyzer failed: %s', exc)
72+
# no source file -- e.g. for builtin and C modules
73+
attr_docs = {}
74+
else:
75+
attr_docs = analyzer.attr_docs
76+
return attr_docs
77+
78+
7279
def _prepare_docstrings(
7380
docstrings: list[list[str]] | None,
7481
*,
7582
props: _ItemProperties,
7683
attr_docs: Mapping[tuple[str, str], list[str]],
7784
) -> list[list[str]] | None:
7885
"""Add content from docstrings, attribute documentation and user."""
79-
if docstrings is not None and not docstrings:
80-
# append at least a dummy docstring, so that the event
81-
# autodoc-process-docstring is fired and can add some
82-
# content if desired
83-
docstrings = [[]]
84-
85-
if props.obj_type in {'data', 'attribute'}:
86-
return docstrings
87-
88-
if props.obj_type in {'class', 'exception'}:
89-
real_module = props._obj___module__ or props.module_name
90-
if props.module_name != real_module:
91-
try:
92-
# override analyzer to obtain doc-comment around its definition.
93-
ma = ModuleAnalyzer.for_module(props.module_name)
94-
ma.analyze()
95-
attr_docs = ma.attr_docs
96-
except PycodeError:
97-
pass
98-
9986
# add content from attribute documentation
100-
if attr_docs and props.parts:
87+
if props.obj_type not in {'data', 'attribute'} and props.parts:
10188
key = ('.'.join(props.parent_names), props.name)
102-
if key in attr_docs:
89+
try:
10390
# make a copy of docstring for attributes to avoid cache
10491
# the change of autodoc-process-docstring event.
10592
return [list(attr_docs[key])]
93+
except KeyError:
94+
pass
10695

96+
if docstrings is None:
97+
return None
98+
if not docstrings:
99+
# append at least a dummy docstring, so that the event
100+
# autodoc-process-docstring is fired and can add some
101+
# content if desired
102+
docstrings.append([])
107103
return docstrings
108104

109105

sphinx/ext/autodoc/_generate.py

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from sphinx.util.typing import restify, stringify_annotation
1717

1818
if TYPE_CHECKING:
19-
from collections.abc import Iterator, Mapping
19+
from collections.abc import Iterator
2020
from typing import Literal
2121

2222
from sphinx.config import Config
@@ -32,7 +32,7 @@
3232

3333
def _generate_directives(
3434
more_content: StringList | None = None,
35-
real_modname: str | None = None,
35+
parent_modname: str | None = None,
3636
check_module: bool = False,
3737
all_members: bool = False,
3838
*,
@@ -49,26 +49,23 @@ def _generate_directives(
4949
) -> None:
5050
"""Generate reST for the object given by *props*, and possibly for its members.
5151
52-
If *more_content* is given, include that content. If *real_modname* is
52+
If *more_content* is given, include that content. If *parent_modname* is
5353
given, use that module name to find attribute docs. If *check_module* is
5454
True, only generate if the object is defined in the module name it is
5555
imported from. If *all_members* is True, document all members.
5656
"""
57-
# If there is no real module defined, figure out which to use.
57+
# If there is no parent module specified, figure out which to use.
5858
# The real module is used in the module analyzer to look up the module
5959
# where the attribute documentation would actually be found in.
6060
# This is used for situations where you have a module that collects the
6161
# functions and classes of internal submodules.
62-
guess_modname = props._obj___module__ or props.module_name
63-
if props.obj_type in {'class', 'exception'}:
64-
# Do not pass real_modname and use the name from the __module__
65-
# attribute of the class.
66-
# If a class gets imported into the module real_modname
67-
# the analyzer won't find the source of the class, if
68-
# it looks in real_modname.
69-
real_modname = guess_modname
62+
if parent_modname is None or props.obj_type in {'class', 'exception'}:
63+
# If a class gets imported into the module ``parent_modname``
64+
# the analyzer won't find the source of the class,
65+
# if it looks in ``parent_modname``.
66+
real_modname = props.canonical_module_name
7067
else:
71-
real_modname = real_modname or guess_modname
68+
real_modname = parent_modname
7269

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

98-
if real_modname != guess_modname:
95+
if real_modname != props.canonical_module_name:
9996
# Add module to dependency list if target object is defined in other module.
10097
try:
101-
srcname, _ = ModuleAnalyzer.get_module_source(guess_modname)
98+
srcname, _ = ModuleAnalyzer.get_module_source(props.canonical_module_name)
10299
record_dependencies.add(str(srcname))
103100
except PycodeError:
104101
pass
@@ -123,10 +120,8 @@ def _generate_directives(
123120
analyzer_source = '' if analyzer is None else analyzer.srcname
124121
_add_directive_lines(
125122
more_content=more_content,
126-
attr_docs={} if analyzer is None else analyzer.attr_docs,
127123
is_final=analyzer is not None and props.dotted_parts in analyzer.finals,
128124
config=config,
129-
events=events,
130125
indent=indent,
131126
options=options,
132127
props=props,
@@ -156,10 +151,8 @@ def _generate_directives(
156151
def _add_directive_lines(
157152
*,
158153
more_content: StringList | None,
159-
attr_docs: Mapping[tuple[str, str], list[str]],
160154
is_final: bool,
161155
config: Config,
162-
events: EventManager,
163156
indent: str,
164157
options: _AutoDocumenterOptions,
165158
props: _ItemProperties,
@@ -244,7 +237,7 @@ def _document_members(
244237
events=events,
245238
get_attr=get_attr,
246239
options=options,
247-
real_modname=real_modname,
240+
parent_modname=real_modname,
248241
props=props,
249242
)
250243

@@ -261,7 +254,7 @@ def _document_members(
261254
# whatever objects we deduced should not have changed.
262255
_generate_directives(
263256
more_content=None,
264-
real_modname=real_modname,
257+
parent_modname=real_modname,
265258
check_module=members_check_module and not is_attr,
266259
all_members=True,
267260
config=config,

sphinx/ext/autodoc/_member_finder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def _gather_members(
100100
events: EventManager,
101101
get_attr: _AttrGetter,
102102
options: _AutoDocumenterOptions,
103-
real_modname: str,
103+
parent_modname: str,
104104
props: _ItemProperties,
105105
) -> list[tuple[_ItemProperties, bool, str]]:
106106
"""Generate reST for member documentation.
@@ -183,7 +183,7 @@ def _gather_members(
183183
events=events,
184184
get_attr=get_attr,
185185
options=options,
186-
real_modname=real_modname,
186+
parent_modname=parent_modname,
187187
)
188188
if member_props is None:
189189
continue

sphinx/ext/autodoc/_property_types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ def dotted_parts(self) -> str:
8282
def _groupwise_order_key(self) -> int:
8383
return 0
8484

85+
@property
86+
def canonical_module_name(self) -> str:
87+
if self._obj___module__ is not None:
88+
return self._obj___module__
89+
return self.module_name
90+
8591

8692
@dataclasses.dataclass(frozen=False, kw_only=True, slots=True)
8793
class _ModuleProperties(_ItemProperties):

sphinx/ext/autodoc/importer.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ def _load_object_by_name(
497497
events: EventManager,
498498
get_attr: _AttrGetter,
499499
options: _AutoDocumenterOptions,
500-
real_modname: str | None = None,
500+
parent_modname: str | None = None,
501501
) -> _ItemProperties | None:
502502
"""Import and load the object given by *name*."""
503503
parsed = _parse_name(
@@ -926,7 +926,7 @@ def _load_object_by_name(
926926
props.docstring_lines = _docstring_lines_for_props(
927927
docstrings,
928928
props=props,
929-
real_modname=real_modname,
929+
parent_modname=parent_modname,
930930
events=events,
931931
options=options,
932932
)
@@ -1250,23 +1250,21 @@ def _format_signatures(
12501250
if props.obj_type in {'module', 'data', 'type'}:
12511251
signatures[1:] = () # discard all signatures save the first
12521252

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

12661265
if props.obj_type in {'function', 'decorator'}:
12671266
overloaded = (
1268-
analyzer is not None
1269-
and props.dotted_parts in analyzer.overloads
1267+
props.dotted_parts in analyzer_overloads
12701268
and config.autodoc_typehints != 'none'
12711269
)
12721270
is_singledispatch = inspect.is_singledispatch_function(props._obj)
@@ -1311,12 +1309,12 @@ def _format_signatures(
13111309
options=options,
13121310
props=dispatch_props,
13131311
)
1314-
if overloaded and analyzer is not None:
1312+
if overloaded:
13151313
actual = inspect.signature(
13161314
props._obj, type_aliases=config.autodoc_type_aliases
13171315
)
13181316
obj_globals = safe_getattr(props._obj, '__globals__', {})
1319-
overloads = analyzer.overloads[props.dotted_parts]
1317+
overloads = analyzer_overloads[props.dotted_parts]
13201318
for overload in overloads:
13211319
overload = _merge_default_value(actual, overload)
13221320
overload = evaluate_signature(
@@ -1374,8 +1372,7 @@ def _format_signatures(
13741372

13751373
if props.obj_type == 'method':
13761374
overloaded = (
1377-
analyzer is not None
1378-
and props.dotted_parts in analyzer.overloads
1375+
props.dotted_parts in analyzer_overloads
13791376
and config.autodoc_typehints != 'none'
13801377
)
13811378
meth = parent.__dict__.get(props.name)
@@ -1423,7 +1420,7 @@ def _format_signatures(
14231420
options=options,
14241421
props=dispatch_props,
14251422
)
1426-
if overloaded and analyzer is not None:
1423+
if overloaded:
14271424
from sphinx.ext.autodoc._property_types import _FunctionDefProperties
14281425

14291426
assert isinstance(props, _FunctionDefProperties)
@@ -1434,7 +1431,7 @@ def _format_signatures(
14341431
)
14351432

14361433
obj_globals = safe_getattr(props._obj, '__globals__', {})
1437-
overloads = analyzer.overloads[props.dotted_parts]
1434+
overloads = analyzer_overloads[props.dotted_parts]
14381435
for overload in overloads:
14391436
overload = _merge_default_value(actual, overload)
14401437
overload = evaluate_signature(

sphinx/ext/autosummary/__init__.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -360,18 +360,6 @@ def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]:
360360
items.append((display_name, '', '', real_name))
361361
continue
362362

363-
# try to also get a source code analyzer for attribute docs
364-
real_module = props._obj___module__ or props.module_name
365-
try:
366-
analyzer = ModuleAnalyzer.for_module(real_module)
367-
# parse right now, to get PycodeErrors on parsing (results will
368-
# be cached anyway)
369-
analyzer.analyze()
370-
except PycodeError as err:
371-
logger.debug('[autodoc] module analyzer failed: %s', err)
372-
# no source file -- e.g. for builtin and C modules
373-
analyzer = None
374-
375363
# -- Grab the signature
376364

377365
if signatures_option == 'none':

0 commit comments

Comments
 (0)