mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Fix handler registration order in Scene (#1642)
Some checks are pending
Tests / tests (macos-latest, 3.10) (push) Waiting to run
Tests / tests (macos-latest, 3.11) (push) Waiting to run
Tests / tests (macos-latest, 3.12) (push) Waiting to run
Tests / tests (macos-latest, 3.13) (push) Waiting to run
Tests / tests (macos-latest, 3.9) (push) Waiting to run
Tests / tests (ubuntu-latest, 3.10) (push) Waiting to run
Tests / tests (ubuntu-latest, 3.11) (push) Waiting to run
Tests / tests (ubuntu-latest, 3.12) (push) Waiting to run
Tests / tests (ubuntu-latest, 3.13) (push) Waiting to run
Tests / tests (ubuntu-latest, 3.9) (push) Waiting to run
Tests / tests (windows-latest, 3.10) (push) Waiting to run
Tests / tests (windows-latest, 3.11) (push) Waiting to run
Tests / tests (windows-latest, 3.12) (push) Waiting to run
Tests / tests (windows-latest, 3.13) (push) Waiting to run
Tests / tests (windows-latest, 3.9) (push) Waiting to run
Tests / pypy-tests (macos-latest, pypy3.10) (push) Waiting to run
Tests / pypy-tests (macos-latest, pypy3.9) (push) Waiting to run
Tests / pypy-tests (ubuntu-latest, pypy3.10) (push) Waiting to run
Tests / pypy-tests (ubuntu-latest, pypy3.9) (push) Waiting to run
Some checks are pending
Tests / tests (macos-latest, 3.10) (push) Waiting to run
Tests / tests (macos-latest, 3.11) (push) Waiting to run
Tests / tests (macos-latest, 3.12) (push) Waiting to run
Tests / tests (macos-latest, 3.13) (push) Waiting to run
Tests / tests (macos-latest, 3.9) (push) Waiting to run
Tests / tests (ubuntu-latest, 3.10) (push) Waiting to run
Tests / tests (ubuntu-latest, 3.11) (push) Waiting to run
Tests / tests (ubuntu-latest, 3.12) (push) Waiting to run
Tests / tests (ubuntu-latest, 3.13) (push) Waiting to run
Tests / tests (ubuntu-latest, 3.9) (push) Waiting to run
Tests / tests (windows-latest, 3.10) (push) Waiting to run
Tests / tests (windows-latest, 3.11) (push) Waiting to run
Tests / tests (windows-latest, 3.12) (push) Waiting to run
Tests / tests (windows-latest, 3.13) (push) Waiting to run
Tests / tests (windows-latest, 3.9) (push) Waiting to run
Tests / pypy-tests (macos-latest, pypy3.10) (push) Waiting to run
Tests / pypy-tests (macos-latest, pypy3.9) (push) Waiting to run
Tests / pypy-tests (ubuntu-latest, pypy3.10) (push) Waiting to run
Tests / pypy-tests (ubuntu-latest, pypy3.9) (push) Waiting to run
* Fix handler registration order in `Scene` Previously, `Scene` handlers were registered based on the sorted output of `inspect.getmembers`, causing incorrect execution order. Now, handlers are registered in the order they are defined in the class, ensuring reliable behavior and proper sequence when handling filters with varying specificity. Added test cases to validate the correct handler ordering. * Add dynamic dataclass and class attribute resolvers Introduced `dataclass_kwargs` to ensure compatibility with different Python versions and modular attribute handling. Added utilities for resolving class attributes dynamically, enhancing flexibility with MRO-based resolvers. Updated tests to verify new features and ensure proper functionality across various scenarios. * Update changelog
This commit is contained in:
parent
e622ada2fc
commit
8b4976b3de
8 changed files with 397 additions and 10 deletions
|
|
@ -1680,3 +1680,73 @@ class TestSceneInheritance:
|
|||
assert child_2_handler.handler is _empty_handler
|
||||
|
||||
assert child_3_handler.handler is not _empty_handler
|
||||
|
||||
|
||||
def collect_handler_names(scene):
|
||||
return [handler.handler.__name__ for handler in scene.__scene_config__.handlers]
|
||||
|
||||
|
||||
class TestSceneHandlersOrdering:
|
||||
def test_correct_ordering(self):
|
||||
class Scene1(Scene):
|
||||
@on.message()
|
||||
async def handler1(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
@on.message()
|
||||
async def handler2(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
class Scene2(Scene):
|
||||
@on.message()
|
||||
async def handler2(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
@on.message()
|
||||
async def handler1(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
assert collect_handler_names(Scene1) == ["handler1", "handler2"]
|
||||
assert collect_handler_names(Scene2) == ["handler2", "handler1"]
|
||||
|
||||
def test_inherited_handlers_follow_mro_order(self):
|
||||
class Scene1(Scene):
|
||||
@on.message()
|
||||
async def handler1(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
@on.message()
|
||||
async def handler2(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
class Scene2(Scene1):
|
||||
@on.message()
|
||||
async def handler3(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
@on.message()
|
||||
async def handler4(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
assert collect_handler_names(Scene2) == ["handler3", "handler4", "handler1", "handler2"]
|
||||
|
||||
def test_inherited_overwrite_follow_mro_order(self):
|
||||
class Scene1(Scene):
|
||||
@on.message()
|
||||
async def handler1(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
@on.message()
|
||||
async def handler2(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
class Scene2(Scene1):
|
||||
@on.message()
|
||||
async def handler2(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
@on.message()
|
||||
async def handler3(self, message: Message) -> None:
|
||||
pass
|
||||
|
||||
assert collect_handler_names(Scene2) == ["handler3", "handler1", "handler2"]
|
||||
|
|
|
|||
81
tests/test_utils/test_class_attrs_resolver.py
Normal file
81
tests/test_utils/test_class_attrs_resolver.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.utils.class_attrs_resolver import (
|
||||
get_reversed_mro_unique_attrs_resolver,
|
||||
get_sorted_mro_attrs_resolver,
|
||||
inspect_members_resolver,
|
||||
)
|
||||
|
||||
|
||||
class SimpleClass1:
|
||||
def method1(self):
|
||||
pass
|
||||
|
||||
def method2(self):
|
||||
pass
|
||||
|
||||
|
||||
class SimpleClass2:
|
||||
def method2(self):
|
||||
pass
|
||||
|
||||
def method1(self):
|
||||
pass
|
||||
|
||||
|
||||
class InheritedClass1(SimpleClass1):
|
||||
def method3(self):
|
||||
pass
|
||||
|
||||
def method4(self):
|
||||
pass
|
||||
|
||||
|
||||
class InheritedClass2(SimpleClass1):
|
||||
def method2(self):
|
||||
pass
|
||||
|
||||
def method3(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestClassAttrsResolver:
|
||||
@pytest.mark.parametrize(
|
||||
"cls, resolver, expected",
|
||||
[
|
||||
# inspect_members_resolver
|
||||
(SimpleClass1, inspect_members_resolver, ["method1", "method2"]),
|
||||
(SimpleClass2, inspect_members_resolver, ["method1", "method2"]),
|
||||
(
|
||||
InheritedClass1,
|
||||
inspect_members_resolver,
|
||||
["method1", "method2", "method3", "method4"],
|
||||
),
|
||||
(InheritedClass2, inspect_members_resolver, ["method1", "method2", "method3"]),
|
||||
# get_reversed_mro_unique_attrs_resolver
|
||||
(SimpleClass1, get_reversed_mro_unique_attrs_resolver, ["method1", "method2"]),
|
||||
(SimpleClass2, get_reversed_mro_unique_attrs_resolver, ["method2", "method1"]),
|
||||
(
|
||||
InheritedClass1,
|
||||
get_reversed_mro_unique_attrs_resolver,
|
||||
["method1", "method2", "method3", "method4"],
|
||||
),
|
||||
(
|
||||
InheritedClass2,
|
||||
get_reversed_mro_unique_attrs_resolver,
|
||||
["method1", "method2", "method3"],
|
||||
),
|
||||
# get_sorted_mro_attrs_resolver
|
||||
(SimpleClass1, get_sorted_mro_attrs_resolver, ["method1", "method2"]),
|
||||
(SimpleClass2, get_sorted_mro_attrs_resolver, ["method2", "method1"]),
|
||||
(
|
||||
InheritedClass1,
|
||||
get_sorted_mro_attrs_resolver,
|
||||
["method3", "method4", "method1", "method2"],
|
||||
),
|
||||
(InheritedClass2, get_sorted_mro_attrs_resolver, ["method3", "method1", "method2"]),
|
||||
],
|
||||
)
|
||||
def test_resolve_class_attrs(self, cls, resolver, expected):
|
||||
names = [name for name, _ in resolver(cls) if not name.startswith("__")]
|
||||
assert names == expected
|
||||
51
tests/test_utils/test_dataclass.py
Normal file
51
tests/test_utils/test_dataclass.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from aiogram.utils.dataclass import dataclass_kwargs
|
||||
|
||||
ALL_VERSIONS = {
|
||||
"init": True,
|
||||
"repr": True,
|
||||
"eq": True,
|
||||
"order": True,
|
||||
"unsafe_hash": True,
|
||||
"frozen": True,
|
||||
}
|
||||
ADDED_IN_3_10 = {"match_args": True, "kw_only": True, "slots": True}
|
||||
ADDED_IN_3_11 = {"weakref_slot": True}
|
||||
|
||||
PY_310 = {**ALL_VERSIONS, **ADDED_IN_3_10}
|
||||
PY_311 = {**PY_310, **ADDED_IN_3_11}
|
||||
LATEST_PY = PY_311
|
||||
|
||||
|
||||
class TestDataclassKwargs:
|
||||
@pytest.mark.parametrize(
|
||||
"py_version,expected",
|
||||
[
|
||||
((3, 9, 0), ALL_VERSIONS),
|
||||
((3, 9, 2), ALL_VERSIONS),
|
||||
((3, 10, 2), PY_310),
|
||||
((3, 11, 0), PY_311),
|
||||
((4, 13, 0), LATEST_PY),
|
||||
],
|
||||
)
|
||||
def test_dataclass_kwargs(self, py_version, expected):
|
||||
with patch("sys.version_info", py_version):
|
||||
|
||||
assert (
|
||||
dataclass_kwargs(
|
||||
init=True,
|
||||
repr=True,
|
||||
eq=True,
|
||||
order=True,
|
||||
unsafe_hash=True,
|
||||
frozen=True,
|
||||
match_args=True,
|
||||
kw_only=True,
|
||||
slots=True,
|
||||
weakref_slot=True,
|
||||
)
|
||||
== expected
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue