From d36a10d428b1e256c147642481a093e0c0149d73 Mon Sep 17 00:00:00 2001 From: JRoot Junior Date: Sun, 16 Feb 2025 23:35:24 +0200 Subject: [PATCH] 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. --- CHANGES/1641.bugfix.rst | 5 +++++ aiogram/fsm/scene.py | 5 ++++- tests/test_fsm/test_scene.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 CHANGES/1641.bugfix.rst diff --git a/CHANGES/1641.bugfix.rst b/CHANGES/1641.bugfix.rst new file mode 100644 index 00000000..eb4d2332 --- /dev/null +++ b/CHANGES/1641.bugfix.rst @@ -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. diff --git a/aiogram/fsm/scene.py b/aiogram/fsm/scene.py index 0cbd0e8b..80bcbb47 100644 --- a/aiogram/fsm/scene.py +++ b/aiogram/fsm/scene.py @@ -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): diff --git a/tests/test_fsm/test_scene.py b/tests/test_fsm/test_scene.py index b22ef975..24b36507 100644 --- a/tests/test_fsm/test_scene.py +++ b/tests/test_fsm/test_scene.py @@ -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"]