Aiogram integration inject doesn't work with middlewares
Description
As you know, aiogram integration, like any other, has a special function for preparing the main router to propagate the container into the handlers, middlewares, and etc. - setup_dishka(). But it works only with polling and webhooks, and not with manual update feeding via dp.feed_update.
Code example
Works good
async def start_bot():
"""Start the bot as coroutine."""
bot_container = make_async_container(
BotProvider(),
)
dp = await bot_container.get(Dispatcher)
bot = await bot_container.get(Bot)
setup_dishka(router=dp, container=bot_container, auto_inject=True)
await dp.start_polling(bot)
And doesnt work:
async def start_bot():
"""Start the bot as coroutine."""
bot_container = make_async_container(
BotProvider(),
)
dp = await bot_container.get(Dispatcher)
bot = await bot_container.get(Bot)
setup_dishka(router=dp, container=bot_container, auto_inject=True)
await dp.feed_update(bot, update=manual_update) # KeyError: 'dishka_container'
It doesn't work because aiogram uses special kwargs on the feed_update() method and ignores dishka injection previously, as it is useful only for testing and some add-on libraries.
Can we see a stacktrace? I do not see any problems from my side, feed_update is called by aiogram itself: https://github.com/aiogram/aiogram/blob/a3d6c1615ee07eb50577b6a1b6323a8657fd9f20/aiogram/dispatcher/dispatcher.py#L309
Can we see a stacktrace? I do not see any problems from my side,
feed_updateis called by aiogram itself: https://github.com/aiogram/aiogram/blob/a3d6c1615ee07eb50577b6a1b6323a8657fd9f20/aiogram/dispatcher/dispatcher.py#L309
I researched this and found that it originated from the Dispatcher side and because middleware uses @inject on the call method. So this isn't a problem with feed_update method. Here is another mre that explains this problem:
import asyncio
import datetime
from unittest.mock import AsyncMock
from aiogram import Dispatcher, BaseMiddleware
from aiogram.types import Update, Message, Chat
from dishka import make_async_container, FromDishka, Provider, Scope
from dishka.integrations.aiogram import setup_dishka, inject
class Some:
pass
class TestMiddleware(BaseMiddleware):
@inject
async def __call__(
self,
handler,
event,
data,
md: FromDishka[Some]
):
print(md)
return await handler(event, data)
async def start_bot():
provider = Provider()
provider.provide(source=lambda: Some(), provides=Some, scope=Scope.APP)
bot_container = make_async_container(provider)
dp = Dispatcher()
dp.update.outer_middleware(TestMiddleware())
bot = AsyncMock()
setup_dishka(router=dp, container=bot_container, auto_inject=True)
await dp.feed_update(
bot,
update=Update(
update_id=0,
message=Message(message_id=0, date=datetime.datetime.now(), chat=Chat(id=0, type="private"))
)
)
if __name__ == '__main__':
asyncio.run(start_bot())
Stacktrace:
Traceback (most recent call last):
File "/mre.py", line 49, in <module>
asyncio.run(start_bot())
File "/.pyenv/versions/3.10.2/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/.pyenv/versions/3.10.2/lib/python3.10/asyncio/base_events.py", line 641, in run_until_complete
return future.result()
File "/mre.py", line 39, in start_bot
await dp.feed_update(
File "/.venv/lib/python3.10/site-packages/aiogram/dispatcher/dispatcher.py", line 158, in feed_update
response = await self.update.wrap_outer_middleware(
File "/.venv/lib/python3.10/site-packages/aiogram/dispatcher/middlewares/error.py", line 25, in __call__
return await handler(event, data)
File "/.venv/lib/python3.10/site-packages/aiogram/dispatcher/middlewares/user_context.py", line 49, in __call__
return await handler(event, data)
File "/.venv/lib/python3.10/site-packages/aiogram/fsm/middleware.py", line 43, in __call__
return await handler(event, data)
File "/.venv/lib/python3.10/site-packages/dishka/integrations/base.py", line 147, in autoinjected_func
container = container_getter(args, kwargs)
File "/.venv/lib/python3.10/site-packages/dishka/integrations/aiogram.py", line 31, in <lambda>
container_getter=lambda _, p: p[CONTAINER_NAME],
KeyError: 'dishka_container'
Thanks,
Middlewares in aiogram have different logic comparing to handlers so you cannot reuse @inject there. Instead, you can get dishka_container manually from data dictionary and use its .get methods.
I'll think about how can we add more automatic here, but it is more like enhancement than a bug
def aiogram_middleware_inject(func):
return wrap_injection(
func=func,
is_async=True,
container_getter=lambda args, kwargs: args[3][CONTAINER_KEY],
)
here is the finished inject decorator