Skip to content

Commit 231d75b

Browse files
committed
Merge branch '2.x' into 3.0.x
2 parents 44db2b5 + 223f92b commit 231d75b

File tree

5 files changed

+78
-41
lines changed

5 files changed

+78
-41
lines changed

CHANGES

+3
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ Dependencies
199199
Incompatible changes
200200
--------------------
201201

202+
* #7222: ``sphinx.util.inspect.unwrap()`` is renamed to ``unwrap_all()``
203+
202204
Deprecated
203205
----------
204206

@@ -209,6 +211,7 @@ Bugs fixed
209211
----------
210212

211213
* #7343: Sphinx builds has been slower since 2.4.0 on debug mode
214+
* #7222: autodoc: ``__wrapped__`` functions are not documented correctly
212215

213216
Testing
214217
--------

sphinx/ext/autodoc/__init__.py

+28-26
Original file line numberDiff line numberDiff line change
@@ -1011,41 +1011,42 @@ def format_args(self, **kwargs: Any) -> str:
10111011
if self.env.config.autodoc_typehints in ('none', 'description'):
10121012
kwargs.setdefault('show_annotation', False)
10131013

1014-
if ((inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object)) and
1015-
not inspect.is_cython_function_or_method(self.object)):
1014+
unwrapped = inspect.unwrap(self.object)
1015+
if ((inspect.isbuiltin(unwrapped) or inspect.ismethoddescriptor(unwrapped)) and
1016+
not inspect.is_cython_function_or_method(unwrapped)):
10161017
# cannot introspect arguments of a C function or method
10171018
return None
10181019
try:
1019-
if (not inspect.isfunction(self.object) and
1020-
not inspect.ismethod(self.object) and
1021-
not inspect.isbuiltin(self.object) and
1022-
not inspect.is_cython_function_or_method(self.object) and
1023-
not inspect.isclass(self.object) and
1024-
hasattr(self.object, '__call__')):
1020+
if (not inspect.isfunction(unwrapped) and
1021+
not inspect.ismethod(unwrapped) and
1022+
not inspect.isbuiltin(unwrapped) and
1023+
not inspect.is_cython_function_or_method(unwrapped) and
1024+
not inspect.isclass(unwrapped) and
1025+
hasattr(unwrapped, '__call__')):
10251026
self.env.app.emit('autodoc-before-process-signature',
1026-
self.object.__call__, False)
1027-
sig = inspect.signature(self.object.__call__)
1027+
unwrapped.__call__, False)
1028+
sig = inspect.signature(unwrapped.__call__)
10281029
else:
1029-
self.env.app.emit('autodoc-before-process-signature', self.object, False)
1030-
sig = inspect.signature(self.object)
1030+
self.env.app.emit('autodoc-before-process-signature', unwrapped, False)
1031+
sig = inspect.signature(unwrapped)
10311032
args = stringify_signature(sig, **kwargs)
10321033
except TypeError:
1033-
if (inspect.is_builtin_class_method(self.object, '__new__') and
1034-
inspect.is_builtin_class_method(self.object, '__init__')):
1035-
raise TypeError('%r is a builtin class' % self.object)
1034+
if (inspect.is_builtin_class_method(unwrapped, '__new__') and
1035+
inspect.is_builtin_class_method(unwrapped, '__init__')):
1036+
raise TypeError('%r is a builtin class' % unwrapped)
10361037

10371038
# if a class should be documented as function (yay duck
10381039
# typing) we try to use the constructor signature as function
10391040
# signature without the first argument.
10401041
try:
10411042
self.env.app.emit('autodoc-before-process-signature',
1042-
self.object.__new__, True)
1043-
sig = inspect.signature(self.object.__new__, bound_method=True)
1043+
unwrapped.__new__, True)
1044+
sig = inspect.signature(unwrapped.__new__, bound_method=True)
10441045
args = stringify_signature(sig, show_return_annotation=False, **kwargs)
10451046
except TypeError:
10461047
self.env.app.emit('autodoc-before-process-signature',
1047-
self.object.__init__, True)
1048-
sig = inspect.signature(self.object.__init__, bound_method=True)
1048+
unwrapped.__init__, True)
1049+
sig = inspect.signature(unwrapped.__init__, bound_method=True)
10491050
args = stringify_signature(sig, show_return_annotation=False, **kwargs)
10501051

10511052
if self.env.config.strip_signature_backslash:
@@ -1432,16 +1433,17 @@ def format_args(self, **kwargs: Any) -> str:
14321433
if self.env.config.autodoc_typehints == 'none':
14331434
kwargs.setdefault('show_annotation', False)
14341435

1435-
if ((inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object)) and
1436-
not inspect.is_cython_function_or_method(self.object)):
1436+
unwrapped = inspect.unwrap(self.object)
1437+
if ((inspect.isbuiltin(unwrapped) or inspect.ismethoddescriptor(unwrapped)) and
1438+
not inspect.is_cython_function_or_method(unwrapped)):
14371439
# can never get arguments of a C function or method
14381440
return None
1439-
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
1440-
self.env.app.emit('autodoc-before-process-signature', self.object, False)
1441-
sig = inspect.signature(self.object, bound_method=False)
1441+
if inspect.isstaticmethod(unwrapped, cls=self.parent, name=self.object_name):
1442+
self.env.app.emit('autodoc-before-process-signature', unwrapped, False)
1443+
sig = inspect.signature(unwrapped, bound_method=False)
14421444
else:
1443-
self.env.app.emit('autodoc-before-process-signature', self.object, True)
1444-
sig = inspect.signature(self.object, bound_method=True)
1445+
self.env.app.emit('autodoc-before-process-signature', unwrapped, True)
1446+
sig = inspect.signature(unwrapped, bound_method=True)
14451447
args = stringify_signature(sig, **kwargs)
14461448

14471449
if self.env.config.strip_signature_backslash:

sphinx/util/inspect.py

+26-15
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import warnings
1818
from functools import partial, partialmethod
1919
from inspect import ( # NOQA
20-
Parameter, isclass, ismethod, ismethoddescriptor, isroutine
20+
Parameter, isclass, ismethod, ismethoddescriptor, isroutine, unwrap
2121
)
2222
from io import StringIO
2323
from typing import Any, Callable, Mapping, List, Tuple
@@ -116,11 +116,16 @@ def getargspec(func: Callable) -> Any:
116116
kwonlyargs, kwdefaults, annotations)
117117

118118

119-
def unwrap(obj: Any) -> Any:
120-
"""Get an original object from wrapped object."""
119+
def unwrap_all(obj: Any) -> Any:
120+
"""
121+
Get an original object from wrapped object (unwrapping partials, wrapped
122+
functions, and other decorators).
123+
"""
121124
while True:
122125
if ispartial(obj):
123-
obj = unpartial(obj)
126+
obj = obj.func
127+
elif inspect.isroutine(obj) and hasattr(obj, '__wrapped__'):
128+
obj = obj.__wrapped__
124129
elif isclassmethod(obj):
125130
obj = obj.__func__
126131
elif isstaticmethod(obj):
@@ -207,26 +212,27 @@ def is_cython_function_or_method(obj: Any) -> bool:
207212

208213
def isattributedescriptor(obj: Any) -> bool:
209214
"""Check if the object is an attribute like descriptor."""
210-
if inspect.isdatadescriptor(object):
215+
if inspect.isdatadescriptor(obj):
211216
# data descriptor is kind of attribute
212217
return True
213218
elif isdescriptor(obj):
214219
# non data descriptor
215-
if isfunction(obj) or isbuiltin(obj) or inspect.ismethod(obj):
220+
unwrapped = inspect.unwrap(obj)
221+
if isfunction(unwrapped) or isbuiltin(unwrapped) or inspect.ismethod(unwrapped):
216222
# attribute must not be either function, builtin and method
217223
return False
218-
elif is_cython_function_or_method(obj):
224+
elif is_cython_function_or_method(unwrapped):
219225
# attribute must not be either function and method (for cython)
220226
return False
221-
elif inspect.isclass(obj):
227+
elif inspect.isclass(unwrapped):
222228
# attribute must not be a class
223229
return False
224-
elif isinstance(obj, (ClassMethodDescriptorType,
225-
MethodDescriptorType,
226-
WrapperDescriptorType)):
230+
elif isinstance(unwrapped, (ClassMethodDescriptorType,
231+
MethodDescriptorType,
232+
WrapperDescriptorType)):
227233
# attribute must not be a method descriptor
228234
return False
229-
elif type(obj).__name__ == "instancemethod":
235+
elif type(unwrapped).__name__ == "instancemethod":
230236
# attribute must not be an instancemethod (C-API)
231237
return False
232238
else:
@@ -257,17 +263,22 @@ def is_singledispatch_method(obj: Any) -> bool:
257263

258264
def isfunction(obj: Any) -> bool:
259265
"""Check if the object is function."""
260-
return inspect.isfunction(unwrap(obj))
266+
return inspect.isfunction(unwrap_all(obj))
261267

262268

263269
def isbuiltin(obj: Any) -> bool:
264270
"""Check if the object is builtin."""
265-
return inspect.isbuiltin(unwrap(obj))
271+
return inspect.isbuiltin(unwrap_all(obj))
272+
273+
274+
def isroutine(obj: Any) -> bool:
275+
"""Check is any kind of function or method."""
276+
return inspect.isroutine(unwrap_all(obj))
266277

267278

268279
def iscoroutinefunction(obj: Any) -> bool:
269280
"""Check if the object is coroutine-function."""
270-
obj = unwrap(obj)
281+
obj = unwrap_all(obj)
271282
if hasattr(obj, '__code__') and inspect.iscoroutinefunction(obj):
272283
# check obj.__code__ because iscoroutinefunction() crashes for custom method-like
273284
# objects (see https://github.com/sphinx-doc/sphinx/issues/6605)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# for py32 or above
2+
from functools import lru_cache
3+
4+
5+
@lru_cache(maxsize=None)
6+
def slow_function(message, timeout):
7+
"""This function is slow."""
8+
print(message)

tests/test_autodoc.py

+13
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,19 @@ def test_partialmethod(app):
14131413
assert list(actual) == expected
14141414

14151415

1416+
@pytest.mark.sphinx('html', testroot='ext-autodoc')
1417+
def test_wrappedfunction(app):
1418+
actual = do_autodoc(app, 'function', 'target.wrappedfunction.slow_function')
1419+
assert list(actual) == [
1420+
'',
1421+
'.. py:function:: slow_function(message, timeout)',
1422+
' :module: target.wrappedfunction',
1423+
'',
1424+
' This function is slow.',
1425+
' ',
1426+
]
1427+
1428+
14161429
@pytest.mark.sphinx('html', testroot='ext-autodoc')
14171430
def test_partialmethod_undoc_members(app):
14181431
expected = [

0 commit comments

Comments
 (0)