diff --git a/CHANGES b/CHANGES index 5f8d82d4a..e4467d7f1 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ Bugs fixed * #7808: autodoc: Warnings raised on variable and attribute type annotations * #7802: autodoc: EOFError is raised on parallel build +* #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 * #7811: sphinx.util.inspect causes circular import problem 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!", + '', + ]