Skip to content

Commit 6127d76

Browse files
authored
Integrate autodoc event callbacks into importer (#14031)
1 parent 8ca7120 commit 6127d76

File tree

6 files changed

+101
-128
lines changed

6 files changed

+101
-128
lines changed

sphinx/ext/autodoc/__init__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
)
3535
from sphinx.ext.autodoc.directive import AutodocDirective
3636
from sphinx.ext.autodoc.importer import py_ext_sig_re
37+
from sphinx.ext.autodoc.typehints import _merge_typehints
3738

3839
if TYPE_CHECKING:
3940
from sphinx.application import Sphinx
@@ -140,15 +141,20 @@ def setup(app: Sphinx) -> ExtensionMetadata:
140141
app.add_config_value(
141142
'autodoc_inherit_docstrings', True, 'env', types=frozenset({bool})
142143
)
144+
app.add_config_value(
145+
'autodoc_preserve_defaults', False, 'env', types=frozenset({bool})
146+
)
147+
app.add_config_value(
148+
'autodoc_use_type_comments', True, 'env', types=frozenset({bool})
149+
)
150+
143151
app.add_event('autodoc-before-process-signature')
144152
app.add_event('autodoc-process-docstring')
145153
app.add_event('autodoc-process-signature')
146154
app.add_event('autodoc-skip-member')
147155
app.add_event('autodoc-process-bases')
148156

149-
app.setup_extension('sphinx.ext.autodoc.preserve_defaults')
150-
app.setup_extension('sphinx.ext.autodoc.type_comment')
151-
app.setup_extension('sphinx.ext.autodoc.typehints')
157+
app.connect('object-description-transform', _merge_typehints)
152158

153159
return {
154160
'version': sphinx.__display_version__,

sphinx/ext/autodoc/importer.py

Lines changed: 74 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737
UNINITIALIZED_ATTR,
3838
)
3939
from sphinx.ext.autodoc.mock import ismock, mock, undecorate
40-
from sphinx.ext.autodoc.type_comment import update_annotations_using_type_comments
40+
from sphinx.ext.autodoc.preserve_defaults import update_default_value
41+
from sphinx.ext.autodoc.type_comment import _update_annotations_using_type_comments
42+
from sphinx.ext.autodoc.typehints import _record_typehints
4143
from sphinx.locale import __
4244
from sphinx.pycode import ModuleAnalyzer
4345
from sphinx.util import inspect, logging
@@ -694,9 +696,9 @@ def _load_object_by_name(
694696
else:
695697
func = None
696698
if func is not None:
697-
app = SimpleNamespace(config=config)
698699
# update the annotations of the property getter
699-
update_annotations_using_type_comments(app, func, False) # type: ignore[arg-type]
700+
if config.autodoc_use_type_comments:
701+
_update_annotations_using_type_comments(func, False)
700702

701703
try:
702704
signature = inspect.signature(
@@ -909,6 +911,7 @@ def _load_object_by_name(
909911
signatures = _format_signatures(
910912
args=args,
911913
retann=retann,
914+
autodoc_annotations=current_document.autodoc_annotations,
912915
config=config,
913916
docstrings=docstrings,
914917
events=events,
@@ -1170,6 +1173,7 @@ def _update_module_annotations_from_type_comments(mod: ModuleType) -> None:
11701173

11711174
def _format_signatures(
11721175
*,
1176+
autodoc_annotations: dict[str, dict[str, str]],
11731177
config: Config,
11741178
docstrings: list[list[str]] | None,
11751179
events: EventManager,
@@ -1236,6 +1240,14 @@ def _format_signatures(
12361240
# Only keep the return annotation
12371241
signatures = [('', retann) for _args, retann in signatures]
12381242

1243+
_record_typehints(
1244+
autodoc_annotations=autodoc_annotations,
1245+
name=props.full_name,
1246+
obj=props._obj,
1247+
short_literals=config.python_display_short_literal_types,
1248+
type_aliases=config.autodoc_type_aliases,
1249+
typehints_format=config.autodoc_typehints_format,
1250+
)
12391251
if result := events.emit_firstresult(
12401252
'autodoc-process-signature',
12411253
props.obj_type,
@@ -1303,6 +1315,7 @@ def _format_signatures(
13031315
properties=frozenset(),
13041316
)
13051317
signatures += _format_signatures(
1318+
autodoc_annotations=autodoc_annotations,
13061319
config=config,
13071320
docstrings=None,
13081321
events=events,
@@ -1414,6 +1427,7 @@ def _format_signatures(
14141427
properties=frozenset(),
14151428
)
14161429
signatures += _format_signatures(
1430+
autodoc_annotations=autodoc_annotations,
14171431
config=config,
14181432
docstrings=None,
14191433
events=events,
@@ -1538,8 +1552,10 @@ def _extract_signature_from_object(
15381552
events=events,
15391553
get_attr=get_attr,
15401554
parent=parent,
1555+
preserve_defaults=config.autodoc_preserve_defaults,
15411556
props=props,
15421557
type_aliases=config.autodoc_type_aliases,
1558+
use_type_comments=config.autodoc_use_type_comments,
15431559
)
15441560
if sig is None:
15451561
return []
@@ -1584,38 +1600,75 @@ def _get_signature_object(
15841600
events: EventManager,
15851601
get_attr: _AttrGetter,
15861602
parent: Any,
1603+
preserve_defaults: bool,
15871604
props: _ItemProperties,
15881605
type_aliases: Mapping[str, str] | None,
1606+
use_type_comments: bool,
15891607
) -> Signature | None:
15901608
"""Return a Signature for *obj*, or None on failure."""
1591-
obj = props._obj
1592-
if props.obj_type in {'function', 'decorator'}:
1593-
events.emit('autodoc-before-process-signature', obj, False)
1609+
obj, is_bound_method = _get_object_for_signature(
1610+
props=props, get_attr=get_attr, parent=parent, type_aliases=type_aliases
1611+
)
1612+
if obj is None or isinstance(obj, Signature):
1613+
return obj
1614+
1615+
if preserve_defaults:
1616+
update_default_value(obj, bound_method=is_bound_method)
1617+
if use_type_comments:
1618+
_update_annotations_using_type_comments(obj, bound_method=is_bound_method)
1619+
events.emit('autodoc-before-process-signature', obj, is_bound_method)
1620+
1621+
if props.obj_type in {'class', 'exception', 'function', 'method', 'decorator'}:
15941622
try:
1595-
return inspect.signature(obj, type_aliases=type_aliases)
1623+
return inspect.signature(
1624+
obj, bound_method=is_bound_method, type_aliases=type_aliases
1625+
)
15961626
except TypeError as exc:
1597-
msg = __('Failed to get a function signature for %s: %s')
1627+
if props.obj_type in {'class', 'exception'}:
1628+
msg = __('Failed to get a constructor signature for %s: %s')
1629+
elif props.obj_type in {'function', 'decorator'}:
1630+
msg = __('Failed to get a function signature for %s: %s')
1631+
elif props.obj_type == 'method':
1632+
msg = __('Failed to get a method signature for %s: %s')
1633+
else:
1634+
msg = __('Failed to get a signature for %s: %s')
15981635
logger.warning(msg, props.full_name, exc)
15991636
return None
16001637
except ValueError:
1638+
# Still no signature: happens e.g. for old-style classes
1639+
# with __init__ in C and no `__text_signature__`.
16011640
return None
16021641

1642+
return None
1643+
1644+
1645+
def _get_object_for_signature(
1646+
props: _ItemProperties,
1647+
get_attr: _AttrGetter,
1648+
parent: Any,
1649+
type_aliases: Mapping[str, str] | None,
1650+
) -> tuple[Any, bool]:
1651+
"""Return the object from which we will obtain the signature."""
1652+
obj = props._obj
1653+
if props.obj_type in {'function', 'decorator'}:
1654+
return obj, False
1655+
16031656
if props.obj_type in {'class', 'exception'}:
16041657
if isinstance(obj, (NewType, TypeVar)):
16051658
# Suppress signature
1606-
return None
1659+
return None, False
16071660

16081661
try:
16091662
object_sig = obj.__signature__
16101663
except AttributeError:
16111664
pass
16121665
else:
16131666
if isinstance(object_sig, Signature):
1614-
return object_sig
1667+
return object_sig, False
16151668
if sys.version_info[:2] in {(3, 12), (3, 13)} and callable(object_sig):
16161669
# Support for enum.Enum.__signature__ in Python 3.12
16171670
if isinstance(object_sig_str := object_sig(), str):
1618-
return inspect.signature_from_str(object_sig_str)
1671+
return inspect.signature_from_str(object_sig_str), False
16191672

16201673
def get_user_defined_function_or_method(obj: Any, attr: str) -> Any:
16211674
"""Get the `attr` function or method from `obj`, if it is user-defined."""
@@ -1643,72 +1696,40 @@ def get_user_defined_function_or_method(obj: Any, attr: str) -> Any:
16431696
if f'{meth.__module__}.{meth.__qualname__}' in blacklist:
16441697
continue
16451698

1646-
events.emit('autodoc-before-process-signature', meth, True)
16471699
try:
1648-
object_sig = inspect.signature(
1649-
meth,
1650-
bound_method=True,
1651-
type_aliases=type_aliases,
1652-
)
1653-
except TypeError as exc:
1654-
msg = __('Failed to get a constructor signature for %s: %s')
1655-
logger.warning(msg, props.full_name, exc)
1656-
return None
1700+
inspect.signature(meth, bound_method=True, type_aliases=type_aliases)
1701+
except TypeError:
1702+
return meth, True # _get_signature_object() needs to log the failure
16571703
except ValueError:
16581704
continue
16591705
else:
16601706
from sphinx.ext.autodoc._property_types import _ClassDefProperties
16611707

16621708
assert isinstance(props, _ClassDefProperties)
16631709
props._signature_method_name = meth_name
1664-
return object_sig
1710+
return meth, True
16651711

16661712
# None of the attributes are user-defined, so fall back to let inspect
16671713
# handle it.
16681714
# We don't know the exact method that inspect.signature will read
1669-
# the signature from, so just pass the object itself to our hook.
1670-
events.emit('autodoc-before-process-signature', obj, False)
1671-
try:
1672-
return inspect.signature(
1673-
obj,
1674-
bound_method=False,
1675-
type_aliases=type_aliases,
1676-
)
1677-
except TypeError as exc:
1678-
msg = __('Failed to get a constructor signature for %s: %s')
1679-
logger.warning(msg, props.full_name, exc)
1680-
return None
1681-
except ValueError:
1682-
pass
1683-
1684-
# Still no signature: happens e.g. for old-style classes
1685-
# with __init__ in C and no `__text_signature__`.
1686-
return None
1715+
# the signature from, so just return the object itself to be passed
1716+
# to the ``autodoc-before-process-signature`` hook.
1717+
return obj, False
16871718

16881719
if props.obj_type == 'method':
16891720
if obj == object.__init__ and parent != object: # NoQA: E721
16901721
# Classes not having own __init__() method are shown as no arguments.
16911722
#
16921723
# Note: The signature of object.__init__() is (self, /, *args, **kwargs).
16931724
# But it makes users confused.
1694-
return Signature()
1725+
return Signature(), False
16951726

16961727
is_bound_method = not inspect.isstaticmethod(
16971728
obj, cls=parent, name=props.object_name
16981729
)
1699-
events.emit('autodoc-before-process-signature', obj, is_bound_method)
1700-
try:
1701-
return inspect.signature(
1702-
obj, bound_method=is_bound_method, type_aliases=type_aliases
1703-
)
1704-
except TypeError as exc:
1705-
msg = __('Failed to get a method signature for %s: %s')
1706-
logger.warning(msg, props.full_name, exc)
1707-
return None
1708-
except ValueError:
1709-
return None
1730+
return obj, is_bound_method
17101731

1711-
return None
1732+
return None, False
17121733

17131734

17141735
def _annotate_to_first_argument(

sphinx/ext/autodoc/preserve_defaults.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import warnings
1313
from typing import TYPE_CHECKING
1414

15-
import sphinx
1615
from sphinx.deprecation import RemovedInSphinx90Warning
1716
from sphinx.locale import __
1817
from sphinx.pycode.ast import unparse as ast_unparse
@@ -21,8 +20,6 @@
2120
if TYPE_CHECKING:
2221
from typing import Any
2322

24-
from sphinx.application import Sphinx
25-
from sphinx.util.typing import ExtensionMetadata
2623

2724
logger = logging.getLogger(__name__)
2825
_LAMBDA_NAME = (lambda: None).__name__
@@ -125,11 +122,8 @@ def get_default_value(lines: list[str], position: ast.expr) -> str | None:
125122
return None
126123

127124

128-
def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None:
129-
"""Update defvalue info of *obj* using type_comments."""
130-
if not app.config.autodoc_preserve_defaults:
131-
return
132-
125+
def update_default_value(obj: Any, bound_method: bool) -> None:
126+
"""Update default value info of *obj* using type_comments."""
133127
try:
134128
lines = inspect.getsource(obj).splitlines()
135129
if lines[0].startswith((' ', '\t')):
@@ -194,15 +188,3 @@ def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None:
194188
logger.warning(
195189
__('Failed to parse a default argument value for %r: %s'), obj, exc
196190
)
197-
198-
199-
def setup(app: Sphinx) -> ExtensionMetadata:
200-
app.add_config_value(
201-
'autodoc_preserve_defaults', False, 'env', types=frozenset({bool})
202-
)
203-
app.connect('autodoc-before-process-signature', update_defvalue)
204-
205-
return {
206-
'version': sphinx.__display_version__,
207-
'parallel_read_safe': True,
208-
}

sphinx/ext/autodoc/type_comment.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from inspect import Parameter, Signature, getsource
77
from typing import TYPE_CHECKING, cast
88

9-
import sphinx
109
from sphinx.locale import __
1110
from sphinx.pycode.ast import unparse as ast_unparse
1211
from sphinx.util import inspect, logging
@@ -15,8 +14,6 @@
1514
from collections.abc import Sequence
1615
from typing import Any
1716

18-
from sphinx.application import Sphinx
19-
from sphinx.util.typing import ExtensionMetadata
2017

2118
logger = logging.getLogger(__name__)
2219

@@ -127,13 +124,8 @@ def get_type_comment(obj: Any, bound_method: bool = False) -> Signature | None:
127124
return None
128125

129126

130-
def update_annotations_using_type_comments(
131-
app: Sphinx, obj: Any, bound_method: bool
132-
) -> None:
127+
def _update_annotations_using_type_comments(obj: Any, bound_method: bool) -> None:
133128
"""Update annotations info of *obj* using type_comments."""
134-
if not app.config.autodoc_use_type_comments:
135-
return
136-
137129
try:
138130
type_sig = get_type_comment(obj, bound_method)
139131
if type_sig:
@@ -152,17 +144,3 @@ def update_annotations_using_type_comments(
152144
)
153145
except NotImplementedError as exc: # failed to ast.unparse()
154146
logger.warning(__('Failed to parse type_comment for %r: %s'), obj, exc)
155-
156-
157-
def setup(app: Sphinx) -> ExtensionMetadata:
158-
app.add_config_value(
159-
'autodoc_use_type_comments', True, 'env', types=frozenset({bool})
160-
)
161-
app.connect(
162-
'autodoc-before-process-signature', update_annotations_using_type_comments
163-
)
164-
165-
return {
166-
'version': sphinx.__display_version__,
167-
'parallel_read_safe': True,
168-
}

0 commit comments

Comments
 (0)