Skip to content

Commit f62ca90

Browse files
committed
Extract most docstring-related logic
1 parent eb99d45 commit f62ca90

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

0 commit comments

Comments
 (0)