mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Fix getting callback params on py3.14+ (#1741)
* Add test to reproduce `TypeError: unsupported callable` on `python >=3.14` * Fix getting callback params on py3.14+ Add 1741.bugfix.rst * Code optimization
This commit is contained in:
parent
79ee135331
commit
b27ca9a45d
4 changed files with 63 additions and 5 deletions
3
CHANGES/1741.bugfix.rst
Normal file
3
CHANGES/1741.bugfix.rst
Normal 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.
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
@ -16,6 +17,12 @@ from aiogram.utils.warnings import Recommendation
|
||||||
|
|
||||||
CallbackType = Callable[..., Any]
|
CallbackType = Callable[..., Any]
|
||||||
|
|
||||||
|
_ACCEPTED_PARAM_KINDS = {
|
||||||
|
inspect.Parameter.POSITIONAL_ONLY,
|
||||||
|
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||||
|
inspect.Parameter.KEYWORD_ONLY,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CallableObject:
|
class CallableObject:
|
||||||
|
|
@ -27,9 +34,30 @@ class CallableObject:
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
callback = inspect.unwrap(self.callback)
|
callback = inspect.unwrap(self.callback)
|
||||||
self.awaitable = inspect.isawaitable(callback) or inspect.iscoroutinefunction(callback)
|
self.awaitable = inspect.isawaitable(callback) or inspect.iscoroutinefunction(callback)
|
||||||
spec = inspect.getfullargspec(callback)
|
|
||||||
self.params = {*spec.args, *spec.kwonlyargs}
|
kwargs: dict[str, Any] = {}
|
||||||
self.varkw = spec.varkw is not None
|
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
|
||||||
|
|
||||||
|
params: set[str] = set()
|
||||||
|
varkw: bool = False
|
||||||
|
|
||||||
|
for p in signature.parameters.values():
|
||||||
|
if p.kind in _ACCEPTED_PARAM_KINDS:
|
||||||
|
params.add(p.name)
|
||||||
|
elif p.kind == inspect.Parameter.VAR_KEYWORD:
|
||||||
|
varkw = True
|
||||||
|
self.params = params
|
||||||
|
self.varkw = varkw
|
||||||
|
|
||||||
def _prepare_kwargs(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
def _prepare_kwargs(self, kwargs: dict[str, Any]) -> dict[str, Any]:
|
||||||
if self.varkw:
|
if self.varkw:
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ class TestCallableObject:
|
||||||
pytest.param(callback1, {"foo", "bar", "baz"}),
|
pytest.param(callback1, {"foo", "bar", "baz"}),
|
||||||
pytest.param(callback2, {"foo", "bar", "baz"}),
|
pytest.param(callback2, {"foo", "bar", "baz"}),
|
||||||
pytest.param(callback3, {"foo"}),
|
pytest.param(callback3, {"foo"}),
|
||||||
pytest.param(TestFilter(), {"self", "foo", "bar", "baz"}),
|
pytest.param(TestFilter(), {"foo", "bar", "baz"}),
|
||||||
pytest.param(SyncCallable(), {"self", "foo", "bar", "baz"}),
|
pytest.param(SyncCallable(), {"foo", "bar", "baz"}),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_init_args_spec(self, callback: Callable, args: Set[str]):
|
def test_init_args_spec(self, callback: Callable, args: Set[str]):
|
||||||
|
|
|
||||||
27
tests/test_issues/test_1741_forward_ref_in_callbacks.py
Normal file
27
tests/test_issues/test_1741_forward_ref_in_callbacks.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from sys import version_info
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aiogram.dispatcher.event.handler import HandlerObject
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
version_info < (3, 14), reason="Requires Python >=3.14 for TypeError on unresolved ForwardRef"
|
||||||
|
)
|
||||||
|
def test_forward_ref_in_callback():
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from aiogram.types import Message
|
||||||
|
|
||||||
|
def my_handler(message: Message):
|
||||||
|
pass
|
||||||
|
|
||||||
|
HandlerObject(callback=my_handler)
|
||||||
|
|
||||||
|
|
||||||
|
def test_forward_ref_in_callback_with_str_annotation():
|
||||||
|
def my_handler(message: "Message"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
handler = HandlerObject(callback=my_handler)
|
||||||
|
assert "message" in handler.params
|
||||||
Loading…
Add table
Add a link
Reference in a new issue