From 37a5eee31cb71f1837a87071fcf65d614d4a2408 Mon Sep 17 00:00:00 2001 From: andrew000 <11490628+andrew000@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:50:24 +0200 Subject: [PATCH] Fix getting callback params on py3.14+ Add 1741.bugfix.rst --- CHANGES/1741.bugfix.rst | 3 ++ aiogram/dispatcher/event/handler.py | 31 +++++++++++++++++-- .../test_event/test_handler.py | 4 +-- 3 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 CHANGES/1741.bugfix.rst diff --git a/CHANGES/1741.bugfix.rst b/CHANGES/1741.bugfix.rst new file mode 100644 index 00000000..5ca46ee5 --- /dev/null +++ b/CHANGES/1741.bugfix.rst @@ -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. diff --git a/aiogram/dispatcher/event/handler.py b/aiogram/dispatcher/event/handler.py index 7aeaab96..12c41d13 100644 --- a/aiogram/dispatcher/event/handler.py +++ b/aiogram/dispatcher/event/handler.py @@ -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: diff --git a/tests/test_dispatcher/test_event/test_handler.py b/tests/test_dispatcher/test_event/test_handler.py index 1f8be4af..7105a058 100644 --- a/tests/test_dispatcher/test_event/test_handler.py +++ b/tests/test_dispatcher/test_event/test_handler.py @@ -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]):