Skip to content

Commit 4ba30e2

Browse files
authored
Restore the legacy implementation of autodoc for compatibility (#14091)
1 parent 14694d4 commit 4ba30e2

File tree

19 files changed

+3833
-70
lines changed

19 files changed

+3833
-70
lines changed

CHANGES.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ Incompatible changes
1414
* #13639: :py:meth:`!SphinxComponentRegistry.create_source_parser` no longer
1515
has an *app* parameter, instead taking *config* and *env*.
1616
Patch by Adam Turner.
17+
* #13751, #14089: :mod:`sphinx.ext.autodoc` has been substantially rewritten,
18+
and there may be some incompatible changes in edge cases, especially when
19+
extensions interact with autodoc internals.
20+
The :confval:`autodoc_use_legacy_class_based` option has been added to
21+
use the legacy (pre-8.3) implementation of autodoc.
22+
Patches by Adam Turner.
1723

1824
Deprecated
1925
----------

doc/development/tutorials/examples/autodoc_intenum.py

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,21 @@
33
from enum import IntEnum
44
from typing import TYPE_CHECKING
55

6-
from sphinx.ext.autodoc import ClassDocumenter, bool_option
7-
from sphinx.ext.autodoc._generate import _docstring_source_name
6+
from sphinx.ext.autodoc import ClassDocumenter, Documenter, bool_option
87

98
if TYPE_CHECKING:
109
from typing import Any
1110

1211
from docutils.statemachine import StringList
1312

1413
from sphinx.application import Sphinx
15-
from sphinx.ext.autodoc import Documenter
1614
from sphinx.util.typing import ExtensionMetadata
1715

1816

1917
class IntEnumDocumenter(ClassDocumenter):
2018
objtype = 'intenum'
2119
directivetype = ClassDocumenter.objtype
22-
priority = 25
20+
priority = 10 + ClassDocumenter.priority
2321
option_spec = dict(ClassDocumenter.option_spec)
2422
option_spec['hex'] = bool_option
2523

@@ -32,33 +30,28 @@ def can_document_member(
3230
except TypeError:
3331
return False
3432

35-
def add_line(self, line: str, source: str = '', *lineno: int, indent: str) -> None:
36-
"""Append one line of generated reST to the output."""
37-
analyzer_source = '' if self.analyzer is None else self.analyzer.srcname
38-
source_name = _docstring_source_name(props=self.props, source=analyzer_source)
39-
if line.strip(): # not a blank line
40-
self.result.append(indent + line, source_name, *lineno)
41-
else:
42-
self.result.append('', source_name, *lineno)
33+
def add_directive_header(self, sig: str) -> None:
34+
super().add_directive_header(sig)
35+
self.add_line(' :final:', self.get_sourcename())
4336

44-
def add_directive_header(self, *, indent: str) -> None:
45-
super().add_directive_header(indent=indent)
46-
self.add_line(' :final:', indent=indent)
37+
def add_content(
38+
self,
39+
more_content: StringList | None,
40+
) -> None:
41+
super().add_content(more_content)
4742

48-
def add_content(self, more_content: StringList | None, *, indent: str) -> None:
49-
super().add_content(more_content, indent=indent)
50-
51-
enum_object: IntEnum = self.props._obj
43+
source_name = self.get_sourcename()
44+
enum_object: IntEnum = self.object
5245
use_hex = self.options.hex
53-
self.add_line('', indent=indent)
46+
self.add_line('', source_name)
5447

5548
for the_member_name, enum_member in enum_object.__members__.items(): # type: ignore[attr-defined]
5649
the_member_value = enum_member.value
5750
if use_hex:
5851
the_member_value = hex(the_member_value)
5952

60-
self.add_line(f'**{the_member_name}**: {the_member_value}', indent=indent)
61-
self.add_line('', indent=indent)
53+
self.add_line(f'**{the_member_name}**: {the_member_value}', source_name)
54+
self.add_line('', source_name)
6255

6356

6457
def setup(app: Sphinx) -> ExtensionMetadata:

doc/usage/extensions/autodoc.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,26 @@ Configuration
10031003

10041004
There are also config values that you can set:
10051005

1006+
.. confval:: autodoc_use_legacy_class_based
1007+
:type: :code-py:`bool`
1008+
:default: :code-py:`False`
1009+
1010+
If true, autodoc will use the legacy class-based implementation.
1011+
This is the behaviour prior to Sphinx 8.3.
1012+
It is based on the ``Documenter`` class hierarchy.
1013+
1014+
This setting is provided for backwards compatibility if your documentation
1015+
or an extension you use uses or monkeypatches the legacy class-based API
1016+
in Python code.
1017+
If this is the case, set ``autodoc_use_legacy_class_based = True``
1018+
in your :file:`conf.py`.
1019+
Please also add a comment to `the tracking issue on GitHub
1020+
<https://github.com/sphinx-doc/sphinx/issues/14089>`__ so that the maintainers
1021+
are aware of your use case, for possible future improvements.
1022+
1023+
.. note:: The legacy class-based implementation does not support
1024+
PEP 695 type aliases.
1025+
10061026
.. confval:: autoclass_content
10071027
:type: :code-py:`str`
10081028
:default: :code-py:`'class'`

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ exclude = [
173173
[tool.mypy]
174174
files = [
175175
"doc/conf.py",
176-
# "doc/development/tutorials/examples/autodoc_intenum.py",
176+
"doc/development/tutorials/examples/autodoc_intenum.py",
177177
"doc/development/tutorials/examples/helloworld.py",
178178
"sphinx",
179179
"tests",

sphinx/application.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@
5252
from sphinx.config import ENUM, _ConfigRebuild
5353
from sphinx.domains import Domain, Index
5454
from sphinx.environment.collectors import EnvironmentCollector
55-
from sphinx.ext.autodoc._documenters import Documenter
5655
from sphinx.ext.autodoc._event_listeners import (
5756
_AutodocProcessDocstringListener,
5857
_AutodocProcessSignatureListener,
5958
_AutodocSkipMemberListener,
6059
)
60+
from sphinx.ext.autodoc._legacy_class_based._documenters import Documenter
6161
from sphinx.ext.todo import todo_node
6262
from sphinx.extension import Extension
6363
from sphinx.registry import (
@@ -1627,9 +1627,9 @@ def add_autodocumenter(self, cls: type[Documenter], override: bool = False) -> N
16271627
Add *override* keyword.
16281628
"""
16291629
logger.debug('[app] adding autodocumenter: %r', cls)
1630-
from sphinx.ext.autodoc._directive import AutodocDirective
1630+
from sphinx.ext.autodoc.directive import AutodocDirective
16311631

1632-
objtype = cls.objtype # type: ignore[attr-defined]
1632+
objtype = cls.objtype
16331633
self.registry.add_documenter(objtype, cls)
16341634
self.add_directive('auto' + objtype, AutodocDirective, override=override)
16351635

sphinx/ext/autodoc/__init__.py

Lines changed: 109 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
import sphinx
1313
from sphinx.config import ENUM
1414
from sphinx.ext.autodoc._directive import AutodocDirective
15-
from sphinx.ext.autodoc._directive_options import (
15+
from sphinx.ext.autodoc._event_listeners import between, cut_lines
16+
from sphinx.ext.autodoc._legacy_class_based._directive_options import (
17+
Options,
1618
annotation_option,
1719
bool_option,
1820
class_doc_from_option,
@@ -23,27 +25,67 @@
2325
members_option,
2426
merge_members_option,
2527
)
26-
from sphinx.ext.autodoc._dynamic._member_finder import ObjectMember, special_member_re
27-
from sphinx.ext.autodoc._event_listeners import between, cut_lines
28-
from sphinx.ext.autodoc._names import py_ext_sig_re
29-
from sphinx.ext.autodoc._sentinels import ALL, EMPTY, SUPPRESS, UNINITIALIZED_ATTR
30-
from sphinx.ext.autodoc._sentinels import INSTANCE_ATTR as INSTANCEATTR
31-
from sphinx.ext.autodoc._sentinels import SLOTS_ATTR as SLOTSATTR
28+
from sphinx.ext.autodoc._legacy_class_based._documenters import (
29+
AttributeDocumenter,
30+
ClassDocumenter,
31+
ClassLevelDocumenter,
32+
DataDocumenter,
33+
DataDocumenterMixinBase,
34+
DecoratorDocumenter,
35+
DocstringSignatureMixin,
36+
DocstringStripSignatureMixin,
37+
Documenter,
38+
ExceptionDocumenter,
39+
FunctionDocumenter,
40+
GenericAliasMixin,
41+
MethodDocumenter,
42+
ModuleDocumenter,
43+
ModuleLevelDocumenter,
44+
NonDataDescriptorMixin,
45+
ObjectMember,
46+
PropertyDocumenter,
47+
RuntimeInstanceAttributeMixin,
48+
SlotsMixin,
49+
UninitializedGlobalVariableMixin,
50+
UninitializedInstanceAttributeMixin,
51+
autodoc_attrgetter,
52+
py_ext_sig_re,
53+
special_member_re,
54+
)
55+
from sphinx.ext.autodoc._legacy_class_based._sentinels import (
56+
ALL,
57+
EMPTY,
58+
INSTANCEATTR,
59+
SLOTSATTR,
60+
SUPPRESS,
61+
UNINITIALIZED_ATTR,
62+
)
3263
from sphinx.ext.autodoc.typehints import _merge_typehints
3364

3465
if TYPE_CHECKING:
3566
from sphinx.application import Sphinx
67+
from sphinx.config import Config
3668
from sphinx.ext.autodoc._property_types import _AutodocObjType
3769
from sphinx.util.typing import ExtensionMetadata
3870

3971
__all__ = (
4072
# Useful event listener factories for autodoc-process-docstring
4173
'cut_lines',
4274
'between',
75+
# Documenters
76+
'AttributeDocumenter',
77+
'ClassDocumenter',
78+
'DataDocumenter',
79+
'DecoratorDocumenter',
80+
'ExceptionDocumenter',
81+
'FunctionDocumenter',
82+
'MethodDocumenter',
83+
'ModuleDocumenter',
84+
'PropertyDocumenter',
4385
# This class is only used in ``sphinx.ext.autodoc.directive``,
4486
# but we export it here for compatibility.
4587
# See: https://github.com/sphinx-doc/sphinx/issues/4538
46-
# 'Options',
88+
'Options',
4789
# Option spec functions.
4890
# Exported for compatibility.
4991
'annotation_option',
@@ -68,26 +110,23 @@
68110
'ObjectMember',
69111
'py_ext_sig_re',
70112
'special_member_re',
113+
'ModuleLevelDocumenter',
114+
'ClassLevelDocumenter',
115+
'DocstringSignatureMixin',
116+
'DocstringStripSignatureMixin',
117+
'DataDocumenterMixinBase',
118+
'GenericAliasMixin',
119+
'UninitializedGlobalVariableMixin',
120+
'NonDataDescriptorMixin',
121+
'SlotsMixin',
122+
'RuntimeInstanceAttributeMixin',
123+
'UninitializedInstanceAttributeMixin',
124+
'autodoc_attrgetter',
125+
'Documenter',
71126
)
72127

73128

74129
def setup(app: Sphinx) -> ExtensionMetadata:
75-
obj_type: _AutodocObjType
76-
for obj_type in (
77-
'module',
78-
'class',
79-
'exception',
80-
'function',
81-
'decorator',
82-
'method',
83-
'property',
84-
'attribute',
85-
'data',
86-
'type',
87-
):
88-
# register the automodule, autoclass, etc. directives
89-
app.add_directive(f'auto{obj_type}', AutodocDirective)
90-
91130
app.add_config_value(
92131
'autoclass_content',
93132
'class',
@@ -142,6 +181,9 @@ def setup(app: Sphinx) -> ExtensionMetadata:
142181
app.add_config_value(
143182
'autodoc_use_type_comments', True, 'env', types=frozenset({bool})
144183
)
184+
app.add_config_value(
185+
'autodoc_use_legacy_class_based', False, 'env', types=frozenset({bool})
186+
)
145187

146188
app.add_event('autodoc-before-process-signature')
147189
app.add_event('autodoc-process-docstring')
@@ -151,7 +193,50 @@ def setup(app: Sphinx) -> ExtensionMetadata:
151193

152194
app.connect('object-description-transform', _merge_typehints)
153195

196+
app.connect('config-inited', _register_directives)
197+
154198
return {
155199
'version': sphinx.__display_version__,
156200
'parallel_read_safe': True,
157201
}
202+
203+
204+
def _register_directives(app: Sphinx, config: Config) -> None:
205+
if not config.autodoc_use_legacy_class_based:
206+
obj_type: _AutodocObjType
207+
for obj_type in (
208+
'module',
209+
'class',
210+
'exception',
211+
'function',
212+
'decorator',
213+
'method',
214+
'property',
215+
'attribute',
216+
'data',
217+
'type',
218+
):
219+
# register the automodule, autoclass, etc. directives
220+
app.add_directive(f'auto{obj_type}', AutodocDirective)
221+
else:
222+
from sphinx.ext.autodoc.preserve_defaults import update_defvalue
223+
from sphinx.ext.autodoc.type_comment import (
224+
update_annotations_using_type_comments,
225+
)
226+
from sphinx.ext.autodoc.typehints import record_typehints
227+
228+
app.add_autodocumenter(ModuleDocumenter)
229+
app.add_autodocumenter(ClassDocumenter)
230+
app.add_autodocumenter(ExceptionDocumenter)
231+
app.add_autodocumenter(DataDocumenter)
232+
app.add_autodocumenter(FunctionDocumenter)
233+
app.add_autodocumenter(DecoratorDocumenter)
234+
app.add_autodocumenter(MethodDocumenter)
235+
app.add_autodocumenter(AttributeDocumenter)
236+
app.add_autodocumenter(PropertyDocumenter)
237+
238+
app.connect('autodoc-before-process-signature', update_defvalue)
239+
app.connect(
240+
'autodoc-before-process-signature', update_annotations_using_type_comments
241+
)
242+
app.connect('autodoc-process-signature', record_typehints)

sphinx/ext/autodoc/_documenters.py

Lines changed: 0 additions & 17 deletions
This file was deleted.

sphinx/ext/autodoc/_dynamic/_preserve_defaults.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
if TYPE_CHECKING:
2121
from typing import Any
2222

23-
2423
logger = logging.getLogger(__name__)
2524
_LAMBDA_NAME = (lambda: None).__name__
2625

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""The legacy (class-based) implementation of autodoc."""

0 commit comments

Comments
 (0)