Preserve middleware context across scene goto transitions (#1687)

This commit is contained in:
latand 2026-02-10 23:24:16 +02:00
parent da7bfdca0c
commit 33cf9b9a99
4 changed files with 67 additions and 0 deletions

1
CHANGES/1687.bugfix.rst Normal file
View file

@ -0,0 +1 @@
Fixed scene transitions to preserve middleware-injected data when moving between scenes via ``SceneWizard.goto``.

View file

@ -259,6 +259,7 @@ class SceneHandlerWrapper:
)
raise SceneException(msg) from None
event_update: Update = kwargs["event_update"]
scenes.data = {**scenes.data, **kwargs}
scene = self.scene(
wizard=SceneWizard(
scene_config=self.scene.__scene_config__,
@ -712,6 +713,9 @@ class ScenesManager:
:param kwargs: Additional keyword arguments to pass to the scene's wizard.enter() method.
:return: None
"""
if kwargs:
self.data = {**self.data, **kwargs}
if _check_active:
active_scene = await self._get_active_scene()
if active_scene is not None:

View file

@ -253,6 +253,7 @@ class TestSceneHandlerWrapper:
state_mock = AsyncMock(spec=FSMContext)
scenes_mock = AsyncMock(spec=ScenesManager)
scenes_mock.data = {}
event_update_mock = Update(
update_id=42,
message=Message(
@ -282,6 +283,7 @@ class TestSceneHandlerWrapper:
state_mock = AsyncMock(spec=FSMContext)
scenes_mock = AsyncMock(spec=ScenesManager)
scenes_mock.data = {}
event_update_mock = Update(
update_id=42,
message=Message(

View file

@ -0,0 +1,60 @@
from collections.abc import Awaitable, Callable
from datetime import datetime
from typing import Any
from aiogram import BaseMiddleware, Dispatcher
from aiogram.enums import ChatType
from aiogram.filters import CommandStart
from aiogram.fsm.scene import Scene, SceneRegistry, on
from aiogram.types import Chat, Message, TelegramObject, Update, User
from tests.mocked_bot import MockedBot
class TestContextMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: dict[str, Any],
) -> Any:
data["test_context"] = "context from middleware"
return await handler(event, data)
class TargetScene(Scene, state="target"):
entered_with_context: str | None = None
@on.message.enter()
async def on_enter(self, message: Message, test_context: str) -> None:
type(self).entered_with_context = test_context
class StartScene(Scene, state="start"):
@on.message.enter()
async def on_start(self, message: Message) -> None:
await self.wizard.goto(TargetScene)
async def test_scene_goto_preserves_message_middleware_data(bot: MockedBot) -> None:
dp = Dispatcher()
registry = SceneRegistry(dp)
registry.add(StartScene, TargetScene)
dp.message.register(StartScene.as_handler(), CommandStart())
dp.message.middleware(TestContextMiddleware())
TargetScene.entered_with_context = None
update = Update(
update_id=1,
message=Message(
message_id=1,
date=datetime.now(),
chat=Chat(id=42, type=ChatType.PRIVATE),
from_user=User(id=42, is_bot=False, first_name="Test"),
text="/start",
),
)
await dp.feed_update(bot, update)
assert TargetScene.entered_with_context == "context from middleware"