Fix getting callback params on py3.14+

Add 1741.bugfix.rst
This commit is contained in:
andrew000 2025-12-23 09:50:24 +02:00
parent 1e4a650171
commit 37a5eee31c
No known key found for this signature in database
GPG key ID: FE06E474CBF9C85D
3 changed files with 33 additions and 5 deletions

3
CHANGES/1741.bugfix.rst Normal file
View file

@ -0,0 +1,3 @@
`inspect.getfullargspec(callback)` can't process callback if it's arguments have "ForwardRef" annotations in Py3.14+
This PR replaces the old way with `inspect.signature(callback)` and add `annotation_format = annotationlib.Format.FORWARDREF` argument to it if runtime python version >=3.14.

View file

@ -1,5 +1,6 @@
import asyncio
import inspect
import sys
import warnings
from collections.abc import Callable
from dataclasses import dataclass, field
@ -27,9 +28,33 @@ class CallableObject:
def __post_init__(self) -> None:
callback = inspect.unwrap(self.callback)
self.awaitable = inspect.isawaitable(callback) or inspect.iscoroutinefunction(callback)
spec = inspect.getfullargspec(callback)
self.params = {*spec.args, *spec.kwonlyargs}
self.varkw = spec.varkw is not None
kwargs: dict[str, Any] = {}
if sys.version_info >= (3, 14):
import annotationlib
kwargs["annotation_format"] = annotationlib.Format.FORWARDREF
try:
signature = inspect.signature(callback, **kwargs)
except (ValueError, TypeError): # pragma: no cover
self.params = set()
self.varkw = False
return
self.params = {
p.name
for p in signature.parameters.values()
if p.kind
in (
inspect.Parameter.POSITIONAL_ONLY,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
inspect.Parameter.KEYWORD_ONLY,
)
}
self.varkw = any(
p.kind == inspect.Parameter.VAR_KEYWORD for p in signature.parameters.values()
)
def _prepare_kwargs(self, kwargs: dict[str, Any]) -> dict[str, Any]:
if self.varkw:

View file

@ -57,8 +57,8 @@ class TestCallableObject:
pytest.param(callback1, {"foo", "bar", "baz"}),
pytest.param(callback2, {"foo", "bar", "baz"}),
pytest.param(callback3, {"foo"}),
pytest.param(TestFilter(), {"self", "foo", "bar", "baz"}),
pytest.param(SyncCallable(), {"self", "foo", "bar", "baz"}),
pytest.param(TestFilter(), {"foo", "bar", "baz"}),
pytest.param(SyncCallable(), {"foo", "bar", "baz"}),
],
)
def test_init_args_spec(self, callback: Callable, args: Set[str]):