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.
This commit is contained in:
JRoot Junior 2025-02-16 23:35:24 +02:00
parent e622ada2fc
commit d36a10d428
No known key found for this signature in database
GPG key ID: 738964250D5FF6E2
3 changed files with 37 additions and 1 deletions

5
CHANGES/1641.bugfix.rst Normal file
View file

@ -0,0 +1,5 @@
Resolved incorrect ordering of registered handlers in the :class:`aiogram.fsm.scene.Scene`
object caused by :code:`inspect.getmembers` returning sorted members.
Handlers are now registered in the order of their definition within the class,
ensuring proper execution sequence, especially when handling filters with different
levels of specificity.

View file

@ -323,7 +323,10 @@ class Scene:
if callback_query_without_state is None:
callback_query_without_state = parent_scene_config.callback_query_without_state
for name, value in inspect.getmembers(cls):
members: dict[str, Any] = {}
for base in reversed(inspect.getmro(cls)):
members.update(base.__dict__)
for name, value in members.items():
if scene_handlers := getattr(value, "__aiogram_handler__", None):
handlers.extend(scene_handlers)
if isinstance(value, ObserverDecorator):

View file

@ -1680,3 +1680,31 @@ 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"]