Skip to content

Commit 8923f08

Browse files
authored
Extract most docstring-related logic (#13941)
1 parent eb99d45 commit 8923f08

File tree

11 files changed

+341
-340
lines changed

11 files changed

+341
-340
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ dependencies = [
8585
"roman-numerals-py>=1.0.0",
8686
"packaging>=23.0",
8787
"colorama>=0.4.6; sys_platform == 'win32'",
88+
"ipython>=9.6.0",
8889
]
8990
dynamic = ["version"]
9091

sphinx/ext/autodoc/_directive_options.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class _AutoDocumenterOptions:
4646

4747
no_index: Literal[True] | None = None
4848
no_index_entry: Literal[True] | None = None
49+
_tab_width: int = 8
4950

5051
# module-like options
5152
members: ALL_T | list[str] | None = None

sphinx/ext/autodoc/_docstrings.py

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, TypeVar
4+
5+
from sphinx.errors import PycodeError
6+
from sphinx.ext.autodoc._property_types import _ClassDefProperties
7+
from sphinx.ext.autodoc._sentinels import (
8+
RUNTIME_INSTANCE_ATTRIBUTE,
9+
SLOTS_ATTR,
10+
UNINITIALIZED_ATTR,
11+
)
12+
from sphinx.locale import __
13+
from sphinx.pycode import ModuleAnalyzer
14+
from sphinx.util import inspect, logging
15+
from sphinx.util.docstrings import prepare_docstring
16+
from sphinx.util.inspect import getdoc
17+
18+
if TYPE_CHECKING:
19+
from collections.abc import Sequence
20+
from typing import Any, Literal
21+
22+
from sphinx.ext.autodoc._property_types import _ItemProperties
23+
from sphinx.ext.autodoc.importer import _AttrGetter
24+
25+
logger = logging.getLogger('sphinx.ext.autodoc')
26+
27+
_OBJECT_INIT_DOCSTRING = (tuple(prepare_docstring(object.__init__.__doc__ or '')),)
28+
_OBJECT_NEW_DOCSTRING = (tuple(prepare_docstring(object.__new__.__doc__ or '')),)
29+
30+
31+
def _get_docstring_lines(
32+
props: _ItemProperties,
33+
*,
34+
class_doc_from: Literal['both', 'class', 'init'],
35+
get_attr: _AttrGetter,
36+
inherit_docstrings: bool,
37+
parent: Any,
38+
tab_width: int,
39+
_new_docstrings: Sequence[Sequence[str]] | None = None,
40+
) -> Sequence[Sequence[str]] | None:
41+
obj = props._obj
42+
43+
if props.obj_type in {'class', 'exception'}:
44+
assert isinstance(props, _ClassDefProperties)
45+
46+
if isinstance(obj, TypeVar):
47+
if obj.__doc__ == TypeVar.__doc__:
48+
return ()
49+
if props.doc_as_attr:
50+
# Don't show the docstring of the class when it is an alias.
51+
if _class_variable_comment(props):
52+
return ()
53+
return None
54+
55+
if _new_docstrings is not None:
56+
return tuple(tuple(doc) for doc in _new_docstrings)
57+
58+
docstrings = []
59+
attrdocstring = getdoc(obj)
60+
if attrdocstring:
61+
docstrings.append(attrdocstring)
62+
63+
# for classes, what the "docstring" is can be controlled via a
64+
# config value; the default is only the class docstring
65+
if class_doc_from in {'both', 'init'}:
66+
__init__ = get_attr(obj, '__init__', None)
67+
init_docstring = getdoc(
68+
__init__,
69+
allow_inherited=inherit_docstrings,
70+
cls=obj, # TODO: object or obj?
71+
name='__init__',
72+
)
73+
# no __init__ means default __init__
74+
if init_docstring == object.__init__.__doc__:
75+
init_docstring = None
76+
if not init_docstring:
77+
# try __new__
78+
__new__ = get_attr(obj, '__new__', None)
79+
init_docstring = getdoc(
80+
__new__,
81+
allow_inherited=inherit_docstrings,
82+
cls=object, # TODO: object or obj?
83+
name='__new__',
84+
)
85+
# no __new__ means default __new__
86+
if init_docstring == object.__new__.__doc__:
87+
init_docstring = None
88+
if init_docstring:
89+
if class_doc_from == 'init':
90+
docstrings = [init_docstring]
91+
else:
92+
docstrings.append(init_docstring)
93+
94+
return tuple(
95+
tuple(prepare_docstring(docstring, tab_width)) for docstring in docstrings
96+
)
97+
98+
if props.obj_type == 'method':
99+
docstring = _get_doc(
100+
obj,
101+
props=props,
102+
inherit_docstrings=inherit_docstrings,
103+
_new_docstrings=_new_docstrings,
104+
parent=parent,
105+
tab_width=tab_width,
106+
)
107+
if props.name == '__init__':
108+
if docstring == _OBJECT_INIT_DOCSTRING:
109+
return ()
110+
if props.name == '__new__':
111+
if docstring == _OBJECT_NEW_DOCSTRING:
112+
return ()
113+
return docstring
114+
115+
if props.obj_type == 'data':
116+
# Check the variable has a docstring-comment
117+
118+
# get_module_comment()
119+
comment = None
120+
try:
121+
analyzer = ModuleAnalyzer.for_module(props.module_name)
122+
analyzer.analyze()
123+
key = ('', props.name)
124+
if key in analyzer.attr_docs:
125+
comment = tuple(analyzer.attr_docs[key])
126+
except PycodeError:
127+
pass
128+
129+
if comment:
130+
return (comment,)
131+
return _get_doc(
132+
obj,
133+
props=props,
134+
inherit_docstrings=inherit_docstrings,
135+
_new_docstrings=_new_docstrings,
136+
parent=parent,
137+
tab_width=tab_width,
138+
)
139+
140+
if props.obj_type == 'attribute':
141+
from sphinx.ext.autodoc.importer import (
142+
_get_attribute_comment,
143+
_is_runtime_instance_attribute_not_commented,
144+
)
145+
146+
# Check the attribute has a docstring-comment
147+
comment = _get_attribute_comment(
148+
parent=parent, obj_path=props.parts, attrname=props.parts[-1]
149+
)
150+
if comment:
151+
return (comment,)
152+
153+
# Disable `autodoc_inherit_docstring` to avoid to obtain
154+
# a docstring from the value which descriptor returns unexpectedly.
155+
# See: https://github.com/sphinx-doc/sphinx/issues/7805
156+
inherit_docstrings = False
157+
158+
if obj is SLOTS_ATTR:
159+
# support for __slots__
160+
try:
161+
parent___slots__ = inspect.getslots(parent)
162+
if parent___slots__ and (docstring := parent___slots__.get(props.name)):
163+
docstring = tuple(prepare_docstring(docstring))
164+
return (docstring,)
165+
return ()
166+
except ValueError as exc:
167+
logger.warning(
168+
__('Invalid __slots__ found on %s. Ignored.'),
169+
(parent.__qualname__, exc),
170+
type='autodoc',
171+
)
172+
return ()
173+
174+
if (
175+
obj is RUNTIME_INSTANCE_ATTRIBUTE
176+
and _is_runtime_instance_attribute_not_commented(
177+
parent=parent, obj_path=props.parts
178+
)
179+
):
180+
return None
181+
182+
if obj is UNINITIALIZED_ATTR:
183+
return None
184+
185+
if not inspect.isattributedescriptor(obj):
186+
# the docstring of non-data descriptor is very probably
187+
# the wrong thing to display
188+
return None
189+
190+
return _get_doc(
191+
obj,
192+
props=props,
193+
inherit_docstrings=inherit_docstrings,
194+
_new_docstrings=_new_docstrings,
195+
parent=parent,
196+
tab_width=tab_width,
197+
)
198+
199+
docstring = _get_doc(
200+
obj,
201+
props=props,
202+
inherit_docstrings=inherit_docstrings,
203+
_new_docstrings=_new_docstrings,
204+
parent=parent,
205+
tab_width=tab_width,
206+
)
207+
return docstring
208+
209+
210+
def _get_doc(
211+
obj: Any,
212+
*,
213+
props: _ItemProperties,
214+
inherit_docstrings: bool,
215+
_new_docstrings: Sequence[Sequence[str]] | None,
216+
parent: Any,
217+
tab_width: int,
218+
) -> Sequence[Sequence[str]] | None:
219+
"""Decode and return lines of the docstring(s) for the object.
220+
221+
When it returns None, autodoc-process-docstring will not be called for this
222+
object.
223+
"""
224+
if obj is UNINITIALIZED_ATTR:
225+
return ()
226+
227+
if props.obj_type not in {'module', 'data'} and _new_docstrings is not None:
228+
# docstring already returned previously, then modified due to
229+
# ``Documenter._find_signature()``. Just return the
230+
# previously-computed result, so that we don't lose the processing.
231+
return _new_docstrings
232+
233+
docstring = getdoc(
234+
obj,
235+
allow_inherited=inherit_docstrings,
236+
cls=parent,
237+
name=props.object_name,
238+
)
239+
if docstring:
240+
return (tuple(prepare_docstring(docstring, tab_width)),)
241+
return ()
242+
243+
244+
def _class_variable_comment(props: _ItemProperties) -> bool:
245+
try:
246+
analyzer = ModuleAnalyzer.for_module(props.module_name)
247+
analyzer.analyze()
248+
key = ('', props.dotted_parts)
249+
return bool(analyzer.attr_docs.get(key, False))
250+
except PycodeError:
251+
return False

0 commit comments

Comments
 (0)