diff --git a/CHANGES b/CHANGES index a5aa1ed31..21c8cf1a1 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,9 @@ Deprecated Features added -------------- +* #7807: autodoc: Show detailed warning when type_comment is mismatched with its + signature + Bugs fixed ---------- @@ -22,6 +25,7 @@ Bugs fixed * #7802: autodoc: EOFError is raised on parallel build * #7821: autodoc: TypeError is raised for overloaded C-ext function * #7805: autodoc: an object which descriptors returns is unexpectedly documented +* #7807: autodoc: wrong signature is shown for the function using contextmanager * #7812: autosummary: generates broken stub files if the target code contains an attribute and module that are same name * #7808: napoleon: Warnings raised on variable and attribute type annotations diff --git a/sphinx/ext/autodoc/type_comment.py b/sphinx/ext/autodoc/type_comment.py index e6a77f24d..7f11e3d12 100644 --- a/sphinx/ext/autodoc/type_comment.py +++ b/sphinx/ext/autodoc/type_comment.py @@ -128,6 +128,9 @@ def update_annotations_using_type_comments(app: Sphinx, obj: Any, bound_method: if 'return' not in obj.__annotations__: obj.__annotations__['return'] = type_sig.return_annotation + except KeyError as exc: + logger.warning(__("Failed to update signature for %r: parameter not found: %s"), + obj, exc) except NotImplementedError as exc: # failed to ast.unparse() logger.warning(__("Failed to parse type_comment for %r: %s"), obj, exc) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 206be1f9b..956dcd49e 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -9,6 +9,7 @@ """ import builtins +import contextlib import enum import inspect import re @@ -421,6 +422,17 @@ def is_builtin_class_method(obj: Any, attr_name: str) -> bool: return getattr(builtins, name, None) is cls +def _should_unwrap(subject: Callable) -> bool: + """Check the function should be unwrapped on getting signature.""" + if (safe_getattr(subject, '__globals__', None) and + subject.__globals__.get('__name__') == 'contextlib' and # type: ignore + subject.__globals__.get('__file__') == contextlib.__file__): # type: ignore + # contextmanger should be unwrapped + return True + + return False + + def signature(subject: Callable, bound_method: bool = False, follow_wrapped: bool = False ) -> inspect.Signature: """Return a Signature object for the given *subject*. @@ -431,7 +443,10 @@ def signature(subject: Callable, bound_method: bool = False, follow_wrapped: boo """ try: try: - signature = inspect.signature(subject, follow_wrapped=follow_wrapped) + if _should_unwrap(subject): + signature = inspect.signature(subject) + else: + signature = inspect.signature(subject, follow_wrapped=follow_wrapped) except ValueError: # follow built-in wrappers up (ex. functools.lru_cache) signature = inspect.signature(subject) diff --git a/tests/roots/test-ext-autodoc/target/wrappedfunction.py b/tests/roots/test-ext-autodoc/target/wrappedfunction.py index ea872f086..0bd2d2069 100644 --- a/tests/roots/test-ext-autodoc/target/wrappedfunction.py +++ b/tests/roots/test-ext-autodoc/target/wrappedfunction.py @@ -1,8 +1,15 @@ -# for py32 or above +from contextlib import contextmanager from functools import lru_cache +from typing import Generator @lru_cache(maxsize=None) def slow_function(message, timeout): """This function is slow.""" print(message) + + +@contextmanager +def feeling_good(x: int, y: int) -> Generator: + """You'll feel better in this context!""" + yield diff --git a/tests/test_ext_autodoc_autofunction.py b/tests/test_ext_autodoc_autofunction.py index b4be85019..579ad9f48 100644 --- a/tests/test_ext_autodoc_autofunction.py +++ b/tests/test_ext_autodoc_autofunction.py @@ -146,3 +146,16 @@ def test_wrapped_function(app): ' This function is slow.', '', ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_wrapped_function_contextmanager(app): + actual = do_autodoc(app, 'function', 'target.wrappedfunction.feeling_good') + assert list(actual) == [ + '', + '.. py:function:: feeling_good(x: int, y: int) -> Generator', + ' :module: target.wrappedfunction', + '', + " You'll feel better in this context!", + '', + ]