Skip to content

Commit

Permalink
Merge branch '2.4.x' into 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
tk0miya committed Apr 5, 2020
2 parents c99db32 + 4ba1243 commit 223f92b
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 37 deletions.
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Dependencies
Incompatible changes
--------------------

* #7222: ``sphinx.util.inspect.unwrap()`` is renamed to ``unwrap_all()``

Deprecated
----------

Expand All @@ -17,6 +19,7 @@ Bugs fixed
----------

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

Testing
--------
Expand Down
48 changes: 25 additions & 23 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,39 +976,40 @@ def format_args(self, **kwargs: Any) -> str:
if self.env.config.autodoc_typehints == 'none':
kwargs.setdefault('show_annotation', False)

if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
unwrapped = inspect.unwrap(self.object)
if inspect.isbuiltin(unwrapped) or inspect.ismethoddescriptor(unwrapped):
# cannot introspect arguments of a C function or method
return None
try:
if (not inspect.isfunction(self.object) and
not inspect.ismethod(self.object) and
not inspect.isbuiltin(self.object) and
not inspect.isclass(self.object) and
hasattr(self.object, '__call__')):
if (not inspect.isfunction(unwrapped) and
not inspect.ismethod(unwrapped) and
not inspect.isbuiltin(unwrapped) and
not inspect.isclass(unwrapped) and
hasattr(unwrapped, '__call__')):
self.env.app.emit('autodoc-before-process-signature',
self.object.__call__, False)
sig = inspect.signature(self.object.__call__)
unwrapped.__call__, False)
sig = inspect.signature(unwrapped.__call__)
else:
self.env.app.emit('autodoc-before-process-signature', self.object, False)
sig = inspect.signature(self.object)
self.env.app.emit('autodoc-before-process-signature', unwrapped, False)
sig = inspect.signature(unwrapped)
args = stringify_signature(sig, **kwargs)
except TypeError:
if (inspect.is_builtin_class_method(self.object, '__new__') and
inspect.is_builtin_class_method(self.object, '__init__')):
raise TypeError('%r is a builtin class' % self.object)
if (inspect.is_builtin_class_method(unwrapped, '__new__') and
inspect.is_builtin_class_method(unwrapped, '__init__')):
raise TypeError('%r is a builtin class' % unwrapped)

# if a class should be documented as function (yay duck
# typing) we try to use the constructor signature as function
# signature without the first argument.
try:
self.env.app.emit('autodoc-before-process-signature',
self.object.__new__, True)
sig = inspect.signature(self.object.__new__, bound_method=True)
unwrapped.__new__, True)
sig = inspect.signature(unwrapped.__new__, bound_method=True)
args = stringify_signature(sig, show_return_annotation=False, **kwargs)
except TypeError:
self.env.app.emit('autodoc-before-process-signature',
self.object.__init__, True)
sig = inspect.signature(self.object.__init__, bound_method=True)
unwrapped.__init__, True)
sig = inspect.signature(unwrapped.__init__, bound_method=True)
args = stringify_signature(sig, show_return_annotation=False, **kwargs)

# escape backslashes for reST
Expand Down Expand Up @@ -1337,15 +1338,16 @@ def format_args(self, **kwargs: Any) -> str:
if self.env.config.autodoc_typehints == 'none':
kwargs.setdefault('show_annotation', False)

if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
unwrapped = inspect.unwrap(self.object)
if inspect.isbuiltin(unwrapped) or inspect.ismethoddescriptor(unwrapped):
# can never get arguments of a C function or method
return None
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
self.env.app.emit('autodoc-before-process-signature', self.object, False)
sig = inspect.signature(self.object, bound_method=False)
if inspect.isstaticmethod(unwrapped, cls=self.parent, name=self.object_name):
self.env.app.emit('autodoc-before-process-signature', unwrapped, False)
sig = inspect.signature(unwrapped, bound_method=False)
else:
self.env.app.emit('autodoc-before-process-signature', self.object, True)
sig = inspect.signature(self.object, bound_method=True)
self.env.app.emit('autodoc-before-process-signature', unwrapped, True)
sig = inspect.signature(unwrapped, bound_method=True)
args = stringify_signature(sig, **kwargs)

# escape backslashes for reST
Expand Down
39 changes: 25 additions & 14 deletions sphinx/util/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import warnings
from functools import partial, partialmethod
from inspect import ( # NOQA
isclass, ismethod, ismethoddescriptor, isroutine
isclass, ismethod, ismethoddescriptor, unwrap
)
from io import StringIO
from typing import Any, Callable, Mapping, List, Tuple
Expand Down Expand Up @@ -111,11 +111,16 @@ def getargspec(func):
kwonlyargs, kwdefaults, annotations)


def unwrap(obj: Any) -> Any:
"""Get an original object from wrapped object."""
def unwrap_all(obj: Any) -> Any:
"""
Get an original object from wrapped object (unwrapping partials, wrapped
functions, and other decorators).
"""
while True:
if ispartial(obj):
obj = unpartial(obj)
obj = obj.func
elif inspect.isroutine(obj) and hasattr(obj, '__wrapped__'):
obj = obj.__wrapped__
elif isclassmethod(obj):
obj = obj.__func__
elif isstaticmethod(obj):
Expand Down Expand Up @@ -194,23 +199,24 @@ def isabstractmethod(obj: Any) -> bool:

def isattributedescriptor(obj: Any) -> bool:
"""Check if the object is an attribute like descriptor."""
if inspect.isdatadescriptor(object):
if inspect.isdatadescriptor(obj):
# data descriptor is kind of attribute
return True
elif isdescriptor(obj):
# non data descriptor
if isfunction(obj) or isbuiltin(obj) or inspect.ismethod(obj):
unwrapped = inspect.unwrap(obj)
if isfunction(unwrapped) or isbuiltin(unwrapped) or inspect.ismethod(unwrapped):
# attribute must not be either function, builtin and method
return False
elif inspect.isclass(obj):
elif inspect.isclass(unwrapped):
# attribute must not be a class
return False
elif isinstance(obj, (ClassMethodDescriptorType,
MethodDescriptorType,
WrapperDescriptorType)):
elif isinstance(unwrapped, (ClassMethodDescriptorType,
MethodDescriptorType,
WrapperDescriptorType)):
# attribute must not be a method descriptor
return False
elif type(obj).__name__ == "instancemethod":
elif type(unwrapped).__name__ == "instancemethod":
# attribute must not be an instancemethod (C-API)
return False
else:
Expand All @@ -221,17 +227,22 @@ def isattributedescriptor(obj: Any) -> bool:

def isfunction(obj: Any) -> bool:
"""Check if the object is function."""
return inspect.isfunction(unwrap(obj))
return inspect.isfunction(unwrap_all(obj))


def isbuiltin(obj: Any) -> bool:
"""Check if the object is builtin."""
return inspect.isbuiltin(unwrap(obj))
return inspect.isbuiltin(unwrap_all(obj))


def isroutine(obj: Any) -> bool:
"""Check is any kind of function or method."""
return inspect.isroutine(unwrap_all(obj))


def iscoroutinefunction(obj: Any) -> bool:
"""Check if the object is coroutine-function."""
obj = unwrap(obj)
obj = unwrap_all(obj)
if hasattr(obj, '__code__') and inspect.iscoroutinefunction(obj):
# check obj.__code__ because iscoroutinefunction() crashes for custom method-like
# objects (see https://github.com/sphinx-doc/sphinx/issues/6605)
Expand Down
8 changes: 8 additions & 0 deletions tests/roots/test-ext-autodoc/target/wrappedfunction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# for py32 or above
from functools import lru_cache


@lru_cache(maxsize=None)
def slow_function(message, timeout):
"""This function is slow."""
print(message)
13 changes: 13 additions & 0 deletions tests/test_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1369,6 +1369,19 @@ def test_partialmethod(app):
assert list(actual) == expected


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_wrappedfunction(app):
actual = do_autodoc(app, 'function', 'target.wrappedfunction.slow_function')
assert list(actual) == [
'',
'.. py:function:: slow_function(message, timeout)',
' :module: target.wrappedfunction',
'',
' This function is slow.',
' ',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_partialmethod_undoc_members(app):
expected = [
Expand Down

0 comments on commit 223f92b

Please sign in to comment.