Dependency injection#

Dependency injection is a programming technique that makes a class independent of its dependencies. It achieves that by decoupling the usage of an object from its creation. This helps you to follow SOLID’s dependency inversion and single responsibility principles.

How it works in aiogram#

For each update aiogram.dispatcher.dispatcher.Dispatcher passes handling context data. Filters and middleware can also make changes to the context.

To access contextual data you should specify corresponding keyword parameter in handler or filter. For example, to get aiogram.fsm.context.FSMContext we do it like that:

@router.message(ProfileCompletion.add_photo, F.photo)
async def add_photo(
    message: types.Message, bot: Bot, state: FSMContext
) -> Any:
    ... # do something with photo

Injecting own dependencies#

Aiogram provides several ways to complement / modify contextual data.

The first and easiest way is to simply specify the named arguments in aiogram.dispatcher.dispatcher.Dispatcher initialization, polling start methods or aiogram.webhook.aiohttp_server.SimpleRequestHandler initialization if you use webhooks.

async def main() -> None:
    dp = Dispatcher(..., foo=42)
    return await dp.start_polling(
        bot, bar="Bazz"
    )

Analogy for webhook:

async def main() -> None:
    dp = Dispatcher(..., foo=42)
    handler = SimpleRequestHandler(dispatcher=dp, bot=bot, bar="Bazz")
    ... # starting webhook

aiogram.dispatcher.dispatcher.Dispatcher’s workflow data also can be supplemented by setting values as in a dictionary:

dp = Dispatcher(...)
dp["eggs"] = Spam()

The middlewares updates the context quite often. You can read more about them on this page:

The last way is to return a dictionary from the filter:

from typing import Any, Dict, Optional, Union

from aiogram import Router
from aiogram.filters import Filter
from aiogram.types import Message, User

router = Router(name=__name__)


class HelloFilter(Filter):
    def __init__(self, name: Optional[str] = None) -> None:
        self.name = name

    async def __call__(
        self,
        message: Message,
        event_from_user: User
        # Filters also can accept keyword parameters like in handlers
    ) -> Union[bool, Dict[str, Any]]:
        if message.text.casefold() == "hello":
            # Returning a dictionary that will update the context data
            return {"name": event_from_user.mention_html(name=self.name)}
        return False


@router.message(HelloFilter())
async def my_handler(
    message: Message, name: str  # Now we can accept "name" as named parameter
) -> Any:
    return message.answer("Hello, {name}!".format(name=name))

…or using MagicFilter with .as_(...) method.