Skip to content

Commit 54e80f7

Browse files
authored
Calculate and use the docstring_lines attribute (#14015)
1 parent 48f6739 commit 54e80f7

File tree

7 files changed

+137
-120
lines changed

7 files changed

+137
-120
lines changed

sphinx/ext/autodoc/_docstrings.py

Lines changed: 71 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from sphinx.util.inspect import getdoc
1717

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

2222
from sphinx.events import EventManager
@@ -26,26 +26,64 @@
2626

2727
logger = logging.getLogger('sphinx.ext.autodoc')
2828

29-
_OBJECT_INIT_DOCSTRING = (tuple(prepare_docstring(object.__init__.__doc__ or '')),)
30-
_OBJECT_NEW_DOCSTRING = (tuple(prepare_docstring(object.__new__.__doc__ or '')),)
29+
_OBJECT_INIT_DOCSTRING = [prepare_docstring(object.__init__.__doc__ or '')]
30+
_OBJECT_NEW_DOCSTRING = [prepare_docstring(object.__new__.__doc__ or '')]
31+
32+
33+
def _docstring_lines_for_props(
34+
docstrings: list[list[str]] | None,
35+
/,
36+
*,
37+
props: _ItemProperties,
38+
real_modname: str | None,
39+
events: EventManager,
40+
options: _AutoDocumenterOptions,
41+
) -> tuple[str, ...]:
42+
guess_modname = props._obj___module__ or props.module_name
43+
if props.obj_type in {'class', 'exception'}:
44+
# Do not pass real_modname and use the name from the __module__
45+
# attribute of the class.
46+
# If a class gets imported into the module real_modname
47+
# the analyzer won't find the source of the class, if
48+
# it looks in real_modname.
49+
real_modname = guess_modname
50+
else:
51+
real_modname = real_modname or guess_modname
52+
try:
53+
analyzer = ModuleAnalyzer.for_module(real_modname)
54+
# parse right now, to get PycodeErrors on parsing (results will
55+
# be cached anyway)
56+
analyzer.analyze()
57+
except PycodeError as exc:
58+
logger.debug('[autodoc] module analyzer failed: %s', exc)
59+
# no source file -- e.g. for builtin and C modules
60+
analyzer = None
61+
62+
attr_docs = {} if analyzer is None else analyzer.attr_docs
63+
prepared_docstrings = _prepare_docstrings(
64+
docstrings, props=props, attr_docs=attr_docs
65+
)
66+
docstring_lines = _process_docstrings(
67+
prepared_docstrings,
68+
events=events,
69+
props=props,
70+
options=options,
71+
)
72+
return tuple(docstring_lines)
3173

3274

3375
def _prepare_docstrings(
76+
docstrings: list[list[str]] | None,
3477
*,
3578
props: _ItemProperties,
3679
attr_docs: Mapping[tuple[str, str], list[str]],
3780
) -> list[list[str]] | None:
3881
"""Add content from docstrings, attribute documentation and user."""
39-
if props._docstrings is not None:
40-
if props._docstrings:
41-
docstrings = [list(doc) for doc in props._docstrings]
42-
else:
43-
# append at least a dummy docstring, so that the event
44-
# autodoc-process-docstring is fired and can add some
45-
# content if desired
46-
docstrings = [[]]
47-
else:
48-
docstrings = None
82+
if docstrings is not None and not docstrings:
83+
# append at least a dummy docstring, so that the event
84+
# autodoc-process-docstring is fired and can add some
85+
# content if desired
86+
docstrings = [[]]
4987

5088
if props.obj_type in {'data', 'attribute'}:
5189
return docstrings
@@ -107,29 +145,24 @@ def _get_docstring_lines(
107145
inherit_docstrings: bool,
108146
parent: Any,
109147
tab_width: int,
110-
_new_docstrings: Sequence[Sequence[str]] | None = None,
111-
) -> Sequence[Sequence[str]] | None:
148+
) -> list[list[str]] | None:
112149
obj = props._obj
113150

114151
if props.obj_type in {'class', 'exception'}:
115152
assert isinstance(props, _ClassDefProperties)
116153

117154
if isinstance(obj, TypeVar):
118155
if obj.__doc__ == TypeVar.__doc__:
119-
return ()
156+
return []
120157
if props.doc_as_attr:
121158
# Don't show the docstring of the class when it is an alias.
122159
if _class_variable_comment(props):
123-
return ()
160+
return []
124161
return None
125162

126-
if _new_docstrings is not None:
127-
return tuple(tuple(doc) for doc in _new_docstrings)
128-
129163
docstrings = []
130-
attrdocstring = getdoc(obj)
131-
if attrdocstring:
132-
docstrings.append(attrdocstring)
164+
if attr_docstring := getdoc(obj):
165+
docstrings.append(attr_docstring)
133166

134167
# for classes, what the "docstring" is can be controlled via a
135168
# config value; the default is only the class docstring
@@ -162,25 +195,20 @@ def _get_docstring_lines(
162195
else:
163196
docstrings.append(init_docstring)
164197

165-
return tuple(
166-
tuple(prepare_docstring(docstring, tab_width)) for docstring in docstrings
167-
)
198+
return [prepare_docstring(docstring, tab_width) for docstring in docstrings]
168199

169200
if props.obj_type == 'method':
170201
docstring = _get_doc(
171202
obj,
172203
props=props,
173204
inherit_docstrings=inherit_docstrings,
174-
_new_docstrings=_new_docstrings,
175205
parent=parent,
176206
tab_width=tab_width,
177207
)
178-
if props.name == '__init__':
179-
if docstring == _OBJECT_INIT_DOCSTRING:
180-
return ()
181-
if props.name == '__new__':
182-
if docstring == _OBJECT_NEW_DOCSTRING:
183-
return ()
208+
if props.name == '__init__' and docstring == _OBJECT_INIT_DOCSTRING:
209+
return []
210+
if props.name == '__new__' and docstring == _OBJECT_NEW_DOCSTRING:
211+
return []
184212
return docstring
185213

186214
if props.obj_type == 'data':
@@ -193,17 +221,16 @@ def _get_docstring_lines(
193221
analyzer.analyze()
194222
key = ('', props.name)
195223
if key in analyzer.attr_docs:
196-
comment = tuple(analyzer.attr_docs[key])
224+
comment = list(analyzer.attr_docs[key])
197225
except PycodeError:
198226
pass
199227

200228
if comment:
201-
return (comment,)
229+
return [comment]
202230
return _get_doc(
203231
obj,
204232
props=props,
205233
inherit_docstrings=inherit_docstrings,
206-
_new_docstrings=_new_docstrings,
207234
parent=parent,
208235
tab_width=tab_width,
209236
)
@@ -219,7 +246,7 @@ def _get_docstring_lines(
219246
parent=parent, obj_path=props.parts, attrname=props.parts[-1]
220247
)
221248
if comment:
222-
return (comment,)
249+
return [comment]
223250

224251
# Disable `autodoc_inherit_docstring` to avoid to obtain
225252
# a docstring from the value which descriptor returns unexpectedly.
@@ -231,16 +258,15 @@ def _get_docstring_lines(
231258
try:
232259
parent___slots__ = inspect.getslots(parent)
233260
if parent___slots__ and (docstring := parent___slots__.get(props.name)):
234-
docstring = tuple(prepare_docstring(docstring))
235-
return (docstring,)
236-
return ()
261+
return [prepare_docstring(docstring)]
262+
return []
237263
except ValueError as exc:
238264
logger.warning(
239265
__('Invalid __slots__ found on %s. Ignored.'),
240266
(parent.__qualname__, exc),
241267
type='autodoc',
242268
)
243-
return ()
269+
return []
244270

245271
if (
246272
obj is RUNTIME_INSTANCE_ATTRIBUTE
@@ -262,7 +288,6 @@ def _get_docstring_lines(
262288
obj,
263289
props=props,
264290
inherit_docstrings=inherit_docstrings,
265-
_new_docstrings=_new_docstrings,
266291
parent=parent,
267292
tab_width=tab_width,
268293
)
@@ -271,7 +296,6 @@ def _get_docstring_lines(
271296
obj,
272297
props=props,
273298
inherit_docstrings=inherit_docstrings,
274-
_new_docstrings=_new_docstrings,
275299
parent=parent,
276300
tab_width=tab_width,
277301
)
@@ -283,23 +307,16 @@ def _get_doc(
283307
*,
284308
props: _ItemProperties,
285309
inherit_docstrings: bool,
286-
_new_docstrings: Sequence[Sequence[str]] | None,
287310
parent: Any,
288311
tab_width: int,
289-
) -> Sequence[Sequence[str]] | None:
312+
) -> list[list[str]] | None:
290313
"""Decode and return lines of the docstring(s) for the object.
291314
292315
When it returns None, autodoc-process-docstring will not be called for this
293316
object.
294317
"""
295318
if obj is UNINITIALIZED_ATTR:
296-
return ()
297-
298-
if props.obj_type not in {'module', 'data'} and _new_docstrings is not None:
299-
# docstring already returned previously, then modified due to
300-
# ``_extract_signatures_from_docstring()``. Just return the
301-
# previously-computed result, so that we don't lose the processing.
302-
return _new_docstrings
319+
return []
303320

304321
docstring = getdoc(
305322
obj,
@@ -308,8 +325,8 @@ def _get_doc(
308325
name=props.object_name,
309326
)
310327
if docstring:
311-
return (tuple(prepare_docstring(docstring, tab_width)),)
312-
return ()
328+
return [prepare_docstring(docstring, tab_width)]
329+
return []
313330

314331

315332
def _class_variable_comment(props: _ItemProperties) -> bool:

sphinx/ext/autodoc/_generate.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from docutils.statemachine import StringList
77

88
from sphinx.errors import PycodeError
9-
from sphinx.ext.autodoc._docstrings import _prepare_docstrings, _process_docstrings
109
from sphinx.ext.autodoc._member_finder import _gather_members
1110
from sphinx.ext.autodoc._renderer import _add_content, _directive_header_lines
1211
from sphinx.ext.autodoc._sentinels import ALL
@@ -104,7 +103,7 @@ def _generate_directives(
104103
except PycodeError:
105104
pass
106105

107-
has_docstring = any(props._docstrings or ())
106+
has_docstring = bool(props.docstring_lines)
108107
if ismock(props._obj) and not has_docstring:
109108
logger.warning(
110109
__('A mocked object is detected: %r'),
@@ -182,14 +181,7 @@ def _add_directive_lines(
182181
header_lines = StringList(list(lines), source='')
183182

184183
# add content from docstrings or attribute documentation
185-
docstrings = _prepare_docstrings(props=props, attr_docs=attr_docs)
186-
lines = _process_docstrings(
187-
docstrings,
188-
events=events,
189-
props=props,
190-
options=options,
191-
)
192-
docstring_lines = StringList(list(lines), source=source_name)
184+
docstring_lines = StringList(props.docstring_lines, source=source_name)
193185

194186
# add alias information, if applicable
195187
lines = _body_alias_lines(
@@ -252,6 +244,7 @@ def _document_members(
252244
events=events,
253245
get_attr=get_attr,
254246
options=options,
247+
real_modname=real_modname,
255248
props=props,
256249
)
257250

sphinx/ext/autodoc/_member_finder.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ def _gather_members(
100100
events: EventManager,
101101
get_attr: _AttrGetter,
102102
options: _AutoDocumenterOptions,
103+
real_modname: str,
103104
props: _ItemProperties,
104105
) -> list[tuple[_ItemProperties, bool, str]]:
105106
"""Generate reST for member documentation.
@@ -182,6 +183,7 @@ def _gather_members(
182183
events=events,
183184
get_attr=get_attr,
184185
options=options,
186+
real_modname=real_modname,
185187
)
186188
if member_props is None:
187189
continue

sphinx/ext/autodoc/_property_types.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ class _ItemProperties:
5151
#: The item's signature lines, for use in the directive
5252
signatures: tuple[str, ...] = ()
5353

54-
_docstrings: Sequence[Sequence[str]] | None = None
5554
_docstrings_has_hide_value: bool = False
5655
_obj: Any
5756
_obj___module__: str | None

0 commit comments

Comments
 (0)