Typed data (#1647)
Some checks failed
Tests / tests (macos-latest, 3.10) (push) Has been cancelled
Tests / tests (macos-latest, 3.11) (push) Has been cancelled
Tests / tests (macos-latest, 3.12) (push) Has been cancelled
Tests / tests (macos-latest, 3.13) (push) Has been cancelled
Tests / tests (macos-latest, 3.9) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.10) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.11) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.12) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.13) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.9) (push) Has been cancelled
Tests / tests (windows-latest, 3.10) (push) Has been cancelled
Tests / tests (windows-latest, 3.11) (push) Has been cancelled
Tests / tests (windows-latest, 3.12) (push) Has been cancelled
Tests / tests (windows-latest, 3.13) (push) Has been cancelled
Tests / tests (windows-latest, 3.9) (push) Has been cancelled
Tests / pypy-tests (macos-latest, pypy3.10) (push) Has been cancelled
Tests / pypy-tests (macos-latest, pypy3.9) (push) Has been cancelled
Tests / pypy-tests (ubuntu-latest, pypy3.10) (push) Has been cancelled
Tests / pypy-tests (ubuntu-latest, pypy3.9) (push) Has been cancelled

* Add TypedDict support for middleware context data

Introduced `MiddlewareData` and associated TypedDicts to type-hint middleware context data. Updated documentation to include usage examples and guidelines for extending the default middleware data. Also adjusted coverage configuration to exclude the new data module.

* Added more docstrings

* Typo fixes
This commit is contained in:
Alex Root Junior 2025-03-02 01:24:30 +02:00 committed by GitHub
parent 8b4976b3de
commit 843c1dec7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 161 additions and 1 deletions

4
CHANGES/1637.feature.rst Normal file
View file

@ -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.

View file

@ -0,0 +1,97 @@
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
"""Instance of the Dispatcher from which the handler was called."""
bot: Bot
"""Bot that received the update."""
bots: NotRequired[list[Bot]]
"""List of all bots in the Dispatcher. Used only in polling mode."""
event_update: Update
"""Update object that triggered the handler."""
event_router: Router
"""Router that was used to find the handler."""
handler: NotRequired[HandlerObject]
"""Handler object that was called.
Available only in the handler itself and inner middlewares."""
class UserContextData(TypedDict, total=False):
"""
Event context related data about user and chat.
"""
event_context: EventContext
"""Event context object that contains user and chat data."""
event_from_user: NotRequired[User]
"""User object that triggered the handler."""
event_chat: NotRequired[Chat]
"""Chat object that triggered the handler.
.. deprecated:: 3.5.0
Use :attr:`event_context.chat` instead."""
event_thread_id: NotRequired[int]
"""Thread ID of the chat that triggered the handler.
.. deprecated:: 3.5.0
Use :attr:`event_context.chat` instead."""
event_business_connection_id: NotRequired[str]
"""Business connection ID of the chat that triggered the handler.
.. deprecated:: 3.5.0
Use :attr:`event_context.business_connection_id` instead."""
class FSMData(TypedDict, total=False):
"""
FSM related data.
"""
fsm_storage: BaseStorage
"""Storage used for FSM."""
state: NotRequired[FSMContext]
"""Current state of the FSM."""
raw_state: NotRequired[str | None]
"""Raw state of the FSM."""
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 object."""
i18n_middleware: I18nMiddleware
"""I18n middleware."""
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.
"""

View file

@ -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 <magic-filters>` 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, 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)
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

View file

@ -261,7 +261,8 @@ filterwarnings = [
branch = false
parallel = true
omit = [
"aiogram/__about__.py",
"aiogram/__meta__.py",
"aiogram/dispatcher/middlewares/data.py"
]
[tool.coverage.report]