diff --git a/CHANGES/1637.feature.rst b/CHANGES/1637.feature.rst new file mode 100644 index 00000000..58596250 --- /dev/null +++ b/CHANGES/1637.feature.rst @@ -0,0 +1,4 @@ +Add TypedDict definitions for middleware context data to the dispatcher dependency injection docs. + +So, now you can use :class:`aiogram.dispatcher.middleware.data.MiddlewareData` directly or +extend it with your own data in the middlewares. diff --git a/aiogram/dispatcher/middlewares/data.py b/aiogram/dispatcher/middlewares/data.py new file mode 100644 index 00000000..23957606 --- /dev/null +++ b/aiogram/dispatcher/middlewares/data.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, TypedDict + +from typing_extensions import NotRequired + +if TYPE_CHECKING: + from aiogram import Bot, Dispatcher, Router + from aiogram.dispatcher.event.handler import HandlerObject + from aiogram.dispatcher.middlewares.user_context import EventContext + from aiogram.fsm.context import FSMContext + from aiogram.fsm.storage.base import BaseStorage + from aiogram.types import Chat, Update, User + from aiogram.utils.i18n import I18n, I18nMiddleware + + +class DispatcherData(TypedDict, total=False): + """ + Dispatcher and bot related data. + """ + + dispatcher: Dispatcher + bot: Bot + bots: list[Bot] + event_update: Update + event_router: Router + handler: NotRequired[HandlerObject] + + +class UserContextData(TypedDict, total=False): + """ + Event context related data about user and chat. + """ + + event_context: EventContext + event_from_user: NotRequired[User] + event_chat: NotRequired[Chat] # Deprecated + event_thread_id: NotRequired[int] # Deprecated + event_business_connection_id: NotRequired[str] # Deprecated + + +class FSMData(TypedDict, total=False): + """ + FSM related data. + """ + + fsm_storage: BaseStorage + state: NotRequired[FSMContext] + raw_state: NotRequired[str | None] + + +class I18nData(TypedDict, total=False): + """ + I18n related data. + + Is not included by default, you need to add it to your own Data class if you need it. + """ + + i18n: I18n + i18n_middleware: I18nMiddleware + + +class MiddlewareData( + DispatcherData, + UserContextData, + FSMData, + # I18nData, # Disabled by default, add it if you need it to your own Data class. + total=False, +): + """ + Data passed to the handler by the middlewares. + + You can add your own data by extending this class. + """ diff --git a/docs/dispatcher/dependency_injection.rst b/docs/dispatcher/dependency_injection.rst index 0e7af388..562c4da1 100644 --- a/docs/dispatcher/dependency_injection.rst +++ b/docs/dispatcher/dependency_injection.rst @@ -70,3 +70,61 @@ The last way is to return a dictionary from the filter: .. literalinclude:: ../../examples/context_addition_from_filter.py ...or using :ref:`MagicFilter ` with :code:`.as_(...)` method. + + +Using type hints +================ + +.. note:: + + Type-hinting middleware data is optional and is not required for the correct operation of the dispatcher. + However, it is recommended to use it to improve the readability of the code. + +You can use type hints to specify the type of the context data in the middlewares, filters and handlers. + +The default middleware data typed dict can be found in :class:`aiogram.dispatcher.middlewares.data.MiddlewareData`. + +In case when you have extended the context data, you can use the :class:`aiogram.dispatcher.middlewares.data.MiddlewareData` as a base class and specify the type hints for the new fields. + +.. warning:: + + If you using type checking tools like mypy, you can experience warnings about that this type hint against Liskov substitution principle in due stricter type is not a subclass of :code:`dict[str, Any]`. + This is a known issue and it is not a bug. You can ignore this warning or use :code:`# type: ignore` comment. + +Example of using type hints: + +.. code-block:: python + + from aiogram.dispatcher.middlewares.data import MiddlewareData + + + class MyMiddlewareData(MiddlewareData, total=False): + my_custom_value: int + + + class MyMessageMiddleware(BaseMiddleware): + async def __call__( + self, + handler: Callable[[Message, MiddlewareData], Awaitable[Any]], + event: Message, + data: MiddlewareData, + ) -> Any: + bot = data["bot"] # <-- IDE will show you that data has `bot` key and its type is `Bot` + + data["my_custom_value"] = 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)) + + +Available context data type helpers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: aiogram.dispatcher.middlewares.data.MiddlewareData + :members: + :undoc-members: + :member-order: bysource + + +.. autoclass:: aiogram.dispatcher.middlewares.data.I18nData + :members: + :undoc-members: + :member-order: bysource diff --git a/pyproject.toml b/pyproject.toml index 6cf4a060..be73b725 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -261,7 +261,8 @@ filterwarnings = [ branch = false parallel = true omit = [ - "aiogram/__about__.py", + "aiogram/__meta__.py", + "aiogram/dispatcher/middlewares/data.py" ] [tool.coverage.report]