diff --git a/src/pluggy/_hooks.py b/src/pluggy/_hooks.py index 3d232870..8bef18b0 100644 --- a/src/pluggy/_hooks.py +++ b/src/pluggy/_hooks.py @@ -36,6 +36,16 @@ ] _HookImplFunction: TypeAlias = Callable[..., _T | Generator[None, Result[_T], None]] +if sys.version_info >= (3, 14): + from annotationlib import Format + + def _signature(func: Callable[..., object]) -> inspect.Signature: + return inspect.signature(func, annotation_format=Format.STRING) +else: + + def _signature(func: Callable[..., object]) -> inspect.Signature: + return inspect.signature(func) + class HookspecOpts(TypedDict): """Options for a hook specification.""" @@ -310,7 +320,7 @@ def varnames(func: object) -> tuple[tuple[str, ...], tuple[str, ...]]: try: # func MUST be a function or method here or we won't parse any args. - sig = inspect.signature( + sig = _signature( func.__func__ if inspect.ismethod(func) else func # type:ignore[arg-type] ) except TypeError: # pragma: no cover diff --git a/src/pluggy/_manager.py b/src/pluggy/_manager.py index c20105c8..88f1d2cb 100644 --- a/src/pluggy/_manager.py +++ b/src/pluggy/_manager.py @@ -18,6 +18,7 @@ from ._hooks import _HookImplFunction from ._hooks import _Namespace from ._hooks import _Plugin +from ._hooks import _signature from ._hooks import _SubsetHookCaller from ._hooks import HookCaller from ._hooks import HookImpl @@ -522,4 +523,4 @@ def subset_hook_caller( def _formatdef(func: Callable[..., object]) -> str: - return f"{func.__name__}{inspect.signature(func)}" + return f"{func.__name__}{_signature(func)}" diff --git a/testing/test_helpers.py b/testing/test_helpers.py index a08e3d7a..17fdd243 100644 --- a/testing/test_helpers.py +++ b/testing/test_helpers.py @@ -1,13 +1,20 @@ from collections.abc import Callable from functools import wraps +import sys from typing import Any from typing import cast +from typing import TYPE_CHECKING from typing import TypeVar from pluggy._hooks import varnames from pluggy._manager import _formatdef +if TYPE_CHECKING: + # Cannot use typing.Tuple due to pyupgrade replacing it with tuple + from non_existent_module import Tuple + + def test_varnames() -> None: def f(x) -> None: i = 3 # noqa #pragma: no cover @@ -90,6 +97,32 @@ def function4(arg1, *args, **kwargs): assert _formatdef(function4) == "function4(arg1, *args, **kwargs)" +def test_varnames_with_annotations() -> None: + if sys.version_info >= (3, 14): + + def hook(arg1: Tuple[int]) -> None: # type: ignore[no-any-unimported] + pass + else: + + def hook(arg1: "Tuple[int]") -> "None": # type: ignore[no-any-unimported] + pass + + assert varnames(hook) == (("arg1",), ()) + + +def test_formatdef_with_annotations() -> None: + if sys.version_info >= (3, 14): + + def hook(arg1: Tuple[int]) -> None: # type: ignore[no-any-unimported] + pass + else: + + def hook(arg1: "Tuple[int]") -> "None": # type: ignore[no-any-unimported] + pass + + assert _formatdef(hook) == "hook(arg1: 'Tuple[int]') -> 'None'" + + def test_varnames_decorator() -> None: F = TypeVar("F", bound=Callable[..., Any])