Merge pull request #7651 from tk0miya/7650_undecorated_signature

Fix #7650: autodoc: undecorated signature is shown for decorated functions
This commit is contained in:
Takeshi KOMIYA 2020-05-16 15:15:20 +09:00 committed by GitHub
commit 5ae622d48a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 68 additions and 13 deletions

View File

@ -92,6 +92,7 @@ Bugs fixed
``Union[foo, bar, None]``
* #7629: autodoc: autofunction emits an unfriendly warning if an invalid object
specified
* #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

View File

@ -1051,10 +1051,12 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
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 as exc:
logger.warning(__("Failed to get a function signature for %s: %s"),
@ -1451,7 +1453,6 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
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.
@ -1460,12 +1461,18 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
# 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 TypeError as exc:
logger.warning(__("Failed to get a method signature for %s: %s"),
@ -1522,7 +1529,9 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
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)

View File

@ -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:

View File

@ -1,5 +1,9 @@
from functools import wraps
def deco1(func):
"""docstring for deco1"""
@wraps(func)
def wrapper():
return func()
@ -14,3 +18,14 @@ def deco2(condition, message):
return wrapper
return decorator
@deco1
def foo(name=None, age=None):
pass
class Bar:
@deco1
def meth(self, name=None, age=None):
pass

View File

@ -142,6 +142,7 @@ def test_format_signature(app):
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
@ -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__')
@ -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,
@ -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',
'',
]

View File

@ -97,7 +97,7 @@ def test_signature_methods():
# 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():