Skip to content

Commit

Permalink
Fix sphinx-doc#7650: autodoc: undecorated signature is shown for deco…
Browse files Browse the repository at this point in the history
…rated functions
  • Loading branch information
tk0miya committed May 10, 2020
1 parent caddd3a commit 570da84
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGES
Expand Up @@ -88,6 +88,7 @@ Bugs fixed
autodoc_typehints='description' mode
* #7551: autodoc: failed to import nested class
* #7362: autodoc: does not render correct signatures for built-in functions
* #7650: autodoc: undecorated signature is shown for decorated functions
* #7551: autosummary: a nested class is indexed as non-nested class
* #7535: sphinx-autogen: crashes when custom template uses inheritance
* #7536: sphinx-autogen: crashes when template uses i18n feature
Expand Down
27 changes: 18 additions & 9 deletions sphinx/ext/autodoc/__init__.py
Expand Up @@ -1051,10 +1051,12 @@ def format_args(self, **kwargs: Any) -> str:
if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)

unwrapped = inspect.unwrap(self.object)
try:
self.env.app.emit('autodoc-before-process-signature', unwrapped, False)
sig = inspect.signature(unwrapped)
self.env.app.emit('autodoc-before-process-signature', self.object, False)
if inspect.is_singledispatch_function(self.object):
sig = inspect.signature(self.object, follow_wrapped=True)
else:
sig = inspect.signature(self.object)
args = stringify_signature(sig, **kwargs)
except TypeError:
args = ''
Expand Down Expand Up @@ -1447,7 +1449,6 @@ def format_args(self, **kwargs: Any) -> str:
if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)

unwrapped = inspect.unwrap(self.object)
try:
if self.object == object.__init__ and self.parent != object:
# Classes not having own __init__() method are shown as no arguments.
Expand All @@ -1456,12 +1457,18 @@ def format_args(self, **kwargs: Any) -> str:
# But it makes users confused.
args = '()'
else:
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)
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)
else:
self.env.app.emit('autodoc-before-process-signature', unwrapped, True)
sig = inspect.signature(unwrapped, bound_method=True)
self.env.app.emit('autodoc-before-process-signature', self.object, True)

meth = self.parent.__dict__.get(self.objpath[-1], None)
if meth and inspect.is_singledispatch_method(meth):
sig = inspect.signature(self.object, bound_method=True,
follow_wrapped=True)
else:
sig = inspect.signature(self.object, bound_method=True)
args = stringify_signature(sig, **kwargs)
except ValueError:
args = ''
Expand Down Expand Up @@ -1514,7 +1521,9 @@ def add_singledispatch_directive_header(self, sig: str) -> None:
self.annotate_to_first_argument(func, typ)

documenter = MethodDocumenter(self.directive, '')
documenter.parent = self.parent
documenter.object = func
documenter.objpath = self.objpath
self.add_line(' %s%s' % (self.format_name(),
documenter.format_signature()),
sourcename)
Expand Down
11 changes: 9 additions & 2 deletions sphinx/util/inspect.py
Expand Up @@ -408,13 +408,20 @@ def is_builtin_class_method(obj: Any, attr_name: str) -> bool:
return getattr(builtins, name, None) is cls


def signature(subject: Callable, bound_method: bool = False) -> inspect.Signature:
def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = False
) -> inspect.Signature:
"""Return a Signature object for the given *subject*.
:param bound_method: Specify *subject* is a bound method or not
:param follow_wrapped: Same as ``inspect.signature()``.
Defaults to ``False`` (get a signature of *subject*).
"""
try:
signature = inspect.signature(subject)
try:
signature = inspect.signature(subject, follow_wrapped=follow_wrapped)
except ValueError:
# follow built-in wrappers up (ex. functools.lru_cache)
signature = inspect.signature(subject)
parameters = list(signature.parameters.values())
return_annotation = signature.return_annotation
except IndexError:
Expand Down
15 changes: 15 additions & 0 deletions tests/roots/test-ext-autodoc/target/decorator.py
@@ -1,5 +1,9 @@
from functools import wraps


def deco1(func):
"""docstring for deco1"""
@wraps(func)
def wrapper():
return func()

Expand All @@ -14,3 +18,14 @@ def wrapper():

return wrapper
return decorator


@deco1
def foo(name=None, age=None):
pass


class Bar:
@deco1
def meth(self, name=None, age=None):
pass
25 changes: 24 additions & 1 deletion tests/test_ext_autodoc.py
Expand Up @@ -142,6 +142,7 @@ def formatsig(objtype, name, obj, args, retann):
inst = app.registry.documenters[objtype](directive, name)
inst.fullname = name
inst.doc_as_attr = False # for class objtype
inst.parent = object # dummy
inst.object = obj
inst.objpath = [name]
inst.args = args
Expand Down Expand Up @@ -1243,6 +1244,17 @@ def test_autofunction_for_methoddescriptor(app):
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autofunction_for_decorated(app):
actual = do_autodoc(app, 'function', 'target.decorator.foo')
assert list(actual) == [
'',
'.. py:function:: foo()',
' :module: target.decorator',
'',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_automethod_for_builtin(app):
actual = do_autodoc(app, 'method', 'builtins.int.__add__')
Expand All @@ -1256,6 +1268,17 @@ def test_automethod_for_builtin(app):
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_automethod_for_decorated(app):
actual = do_autodoc(app, 'method', 'target.decorator.Bar.meth')
assert list(actual) == [
'',
'.. py:method:: Bar.meth()',
' :module: target.decorator',
'',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_abstractmethods(app):
options = {"members": None,
Expand Down Expand Up @@ -1415,7 +1438,7 @@ def test_coroutine(app):
actual = do_autodoc(app, 'function', 'target.coroutine.sync_func')
assert list(actual) == [
'',
'.. py:function:: sync_func()',
'.. py:function:: sync_func(*args, **kwargs)',
' :module: target.coroutine',
'',
]
Expand Down
2 changes: 1 addition & 1 deletion tests/test_util_inspect.py
Expand Up @@ -97,7 +97,7 @@ def wrapped_bound_method(*args, **kwargs):

# wrapped bound method
sig = inspect.signature(wrapped_bound_method)
assert stringify_signature(sig) == '(arg1, **kwargs)'
assert stringify_signature(sig) == '(*args, **kwargs)'


def test_signature_partialmethod():
Expand Down

0 comments on commit 570da84

Please sign in to comment.