Внедрение зависимостей

Внедрение зависимостей — это техника программирования, которая делает классы независимыми от их зависимостей. Оно достигается путем отделения использования объекта от его создания. Это помогает соблюдать принципы инверсии зависимостей и единой ответственности по SOLID.

Как это работает в litegram

Для каждого обновления litegram.dispatcher.dispatcher.Dispatcher передает данные контекста обработки. Фильтры и промежуточное ПО также могут вносить изменения в контекст.

Чтобы получить доступ к контекстным данным, вы должны указать соответствующий ключевой параметр в обработчике или фильтре. Например, чтобы получить litegram.fsm.context.FSMContext, мы делаем это так:

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

Внедрение собственных зависимостей

Litegram предоставляет несколько способов дополнения/модификации контекстных данных.

Первый и самый простой способ — это просто указать именованные аргументы во время инициализации litegram.dispatcher.dispatcher.Dispatcher, запуска методов опроса или инициализации litegram.webhook.litestar_server.SimpleRequestHandler, если вы используете вебхуки.

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

Аналогия для вебхука:

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

Данные рабочего процесса litegram.dispatcher.dispatcher.Dispatcher также могут дополняться путем установки значений, как в словаре:

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

Промежуточное ПО достаточно часто обновляет контекст. Вы можете прочитать об этом больше на этой странице:

Последний способ — вернуть словарь из фильтра:

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from litegram import Router
from litegram.filters import Filter

if TYPE_CHECKING:
    from litegram.types import Message, User

router = Router(name=__name__)


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

    async def __call__(
        self,
        message: Message,
        event_from_user: User,
        # Filters also can accept keyword parameters like in handlers
    ) -> 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(f"Hello, {name}!")

…или используя MagicFilter с методом .as_(...).

Использование аннотаций типов

Примечание

Использование аннотаций типов для данных промежуточного ПО необязательно и не требуется для корректной работы диспетчера. Однако рекомендуется использовать их для улучшения читаемости кода.

Вы можете использовать аннотации типов для определения типа контекстных данных в промежуточном ПО, фильтрах и обработчиках.

Словарь типов данных по умолчанию для промежуточного ПО можно найти в litegram.dispatcher.middlewares.data.MiddlewareData.

В случае если вы расширили данные контекста, вы можете использовать litegram.dispatcher.middlewares.data.MiddlewareData как базовый класс и указать аннотации типов для новых полей.

Предупреждение

Если вы используете инструменты проверки типов, такие как mypy, вы можете получить предупреждение о том, что эти аннотации типов нарушают принцип подстановки Лисков из-за того, что более строгий тип не является подклассом dict[str, Any]. Это известная проблема, и она не является ошибкой. Вы можете проигнорировать это предупреждение или использовать комментарий # type: ignore.

Пример использования аннотаций типов:

from litegram.dispatcher.middlewares.data import MiddlewareData


class MyMiddlewareData(MiddlewareData, total=False):
    my_custom_value: int


class MyMessageMiddleware(BaseMiddleware):
    async def __call__(
        self,
        handler: Callable[[Message, MyMiddlewareData], Awaitable[Any]],
        event: Message,
        data: MyMiddlewareData,
    ) -> Any:
        bot = data["bot"]  # <-- IDE will show you that data has `bot` key and its type is `Bot`

        data["my_custom_value"] = bot.id * 42  # <-- IDE will show you that you can set `my_custom_value` key with int value and warn you if you try to set it with other type
        return await handler(event, data)

Доступные помощники для типов контекстных данных

class litegram.dispatcher.middlewares.data.MiddlewareData[исходный код]

Данные, передаваемые обработчику промежуточным ПО.

Вы можете добавить собственные данные, расширив этот класс.

dispatcher: Dispatcher
bot: Bot
bots: NotRequired[list[Bot]]
event_update: Update
event_router: Router
handler: NotRequired[HandlerObject]
event_context: EventContext
event_from_user: NotRequired[User]
event_chat: NotRequired[Chat]
event_thread_id: NotRequired[int]
event_business_connection_id: NotRequired[str]
fsm_storage: BaseStorage
state: NotRequired[FSMContext]
raw_state: NotRequired[str | None]
class litegram.dispatcher.middlewares.data.I18nData[исходный код]

Данные, связанные с I18n.

По умолчанию не включено, вам нужно добавить это в собственный класс данных, если это необходимо.

i18n: I18n

Объект I18n.

i18n_middleware: I18nMiddleware

Промежуточное ПО для I18n.