From 6d05b1aeb3725a1fbd727e7bca2832462f7d8bee Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 4 Nov 2020 02:26:13 +0900 Subject: [PATCH] Fix #8105: autodoc: the signature of decorated class is incorrect In #7651, autodoc stops to undecorate the functions on getting the signature from the callables. But some kinds of decorators conceals the correct signature because they pass through their arguments via `(*args, **kwargs)`. This restarts to undecorate the functions again as before #7651. --- CHANGES | 7 ++++++ doc/extdev/deprecated.rst | 6 +++++ sphinx/ext/autodoc/__init__.py | 3 +-- sphinx/util/inspect.py | 10 ++++++-- .../test-ext-autodoc/target/decorator.py | 22 ++++++++++++++++ tests/test_ext_autodoc_autoclass.py | 25 +++++++++++++++++++ tests/test_util_inspect.py | 2 +- 7 files changed, 70 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 4d412799a7..37cb75a9cf 100644 --- a/CHANGES +++ b/CHANGES @@ -7,9 +7,14 @@ Dependencies Incompatible changes -------------------- +* #8105: autodoc: the signature of class constructor will be shown for decorated + classes, not a signature of decorator + Deprecated ---------- +* The ``follow_wrapped`` argument of ``sphinx.util.inspect.signature()`` + Features added -------------- @@ -21,6 +26,8 @@ Bugs fixed ---------- * #7613: autodoc: autodoc does not respect __signature__ of the class +* #8105: autodoc: the signature of class constructor is incorrect if the class + is decorated Testing -------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 2bb8aebfd6..797648a1ac 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -26,6 +26,12 @@ The following is a list of deprecated interfaces. - (will be) Removed - Alternatives + + * - The ``follow_wrapped`` argument of ``sphinx.util.inspect.signature()`` + - 3.4 + - 5.0 + - N/A + * - ``sphinx.builders.latex.LaTeXBuilder.usepackages`` - 3.3 - 5.0 diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 3f24958822..0d26a4f2ce 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1213,7 +1213,7 @@ def format_args(self, **kwargs: Any) -> str: try: self.env.app.emit('autodoc-before-process-signature', self.object, False) - sig = inspect.signature(self.object, follow_wrapped=True, + sig = inspect.signature(self.object, type_aliases=self.env.config.autodoc_type_aliases) args = stringify_signature(sig, **kwargs) except TypeError as exc: @@ -1853,7 +1853,6 @@ def format_args(self, **kwargs: Any) -> str: else: self.env.app.emit('autodoc-before-process-signature', self.object, True) sig = inspect.signature(self.object, bound_method=True, - follow_wrapped=True, type_aliases=self.env.config.autodoc_type_aliases) args = stringify_signature(sig, **kwargs) except TypeError as exc: diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index f2cd8070bb..4f8e15c046 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -439,14 +439,20 @@ def _should_unwrap(subject: Callable) -> bool: return False -def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = False, +def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = None, type_aliases: Dict = {}) -> 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*). """ + + if follow_wrapped is None: + follow_wrapped = True + else: + warnings.warn('The follow_wrapped argument of sphinx.util.inspect.signature() is ' + 'deprecated', RemovedInSphinx50Warning, stacklevel=2) + try: try: if _should_unwrap(subject): diff --git a/tests/roots/test-ext-autodoc/target/decorator.py b/tests/roots/test-ext-autodoc/target/decorator.py index 61398b324e..faad3fff95 100644 --- a/tests/roots/test-ext-autodoc/target/decorator.py +++ b/tests/roots/test-ext-autodoc/target/decorator.py @@ -29,3 +29,25 @@ class Bar: @deco1 def meth(self, name=None, age=None): pass + + +class Baz: + @deco1 + def __init__(self, name=None, age=None): + pass + + +class Qux: + @deco1 + def __new__(self, name=None, age=None): + pass + + +class _Metaclass(type): + @deco1 + def __call__(self, name=None, age=None): + pass + + +class Quux(metaclass=_Metaclass): + pass diff --git a/tests/test_ext_autodoc_autoclass.py b/tests/test_ext_autodoc_autoclass.py index 89a79c2c7c..76f01f5b36 100644 --- a/tests/test_ext_autodoc_autoclass.py +++ b/tests/test_ext_autodoc_autoclass.py @@ -48,3 +48,28 @@ def test_classes(app): '', ] + +def test_decorators(app): + actual = do_autodoc(app, 'class', 'target.decorator.Baz') + assert list(actual) == [ + '', + '.. py:class:: Baz(name=None, age=None)', + ' :module: target.decorator', + '', + ] + + actual = do_autodoc(app, 'class', 'target.decorator.Qux') + assert list(actual) == [ + '', + '.. py:class:: Qux(name=None, age=None)', + ' :module: target.decorator', + '', + ] + + actual = do_autodoc(app, 'class', 'target.decorator.Quux') + assert list(actual) == [ + '', + '.. py:class:: Quux(name=None, age=None)', + ' :module: target.decorator', + '', + ] diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py index c21eaaa167..204fa65b7e 100644 --- a/tests/test_util_inspect.py +++ b/tests/test_util_inspect.py @@ -100,7 +100,7 @@ def wrapped_bound_method(*args, **kwargs): # wrapped bound method sig = inspect.signature(wrapped_bound_method) - assert stringify_signature(sig) == '(*args, **kwargs)' + assert stringify_signature(sig) == '(arg1, **kwargs)' def test_signature_partialmethod():