Update middlewares docs

This commit is contained in:
Alex Root Junior 2020-05-26 20:26:29 +03:00
parent 7f26ec9935
commit bafc2ff341
11 changed files with 119 additions and 215 deletions

View file

@ -5,6 +5,7 @@ from .api.client import session
from .api.client.bot import Bot
from .dispatcher import filters, handler
from .dispatcher.dispatcher import Dispatcher
from .dispatcher.middlewares.base import BaseMiddleware
from .dispatcher.router import Router
try:
@ -24,6 +25,7 @@ __all__ = (
"session",
"Dispatcher",
"Router",
"BaseMiddleware",
"filters",
"handler",
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before After
Before After

View file

@ -39,7 +39,7 @@ dp.include_router(router1)
## Handling updates
All updates can be propagated to the dispatcher by `feed_update` method:
```
```python3
bot = Bot(...)
dp = Dispathcher()

View file

@ -0,0 +1,95 @@
# Middlewares
**aiogram** provides powerful mechanism for customizing event handlers via middlewares.
Middlewares in bot framework seems like Middlewares mechanism in web-frameworks
(like [aiohttp](https://docs.aiohttp.org/en/stable/web_advanced.html#aiohttp-web-middlewares),
[fastapi](https://fastapi.tiangolo.com/tutorial/middleware/),
[Django](https://docs.djangoproject.com/en/3.0/topics/http/middleware/) or etc.)
with small difference - here is implemented two layers of middlewares (before and after filters).
!!! info
Middleware is function that triggered on every event received from
Telegram Bot API in many points on processing pipeline.
## Base theory
As many books and other literature in internet says:
> Middleware is reusable software that leverages patterns and frameworks to bridge
> the gap between the functional requirements of applications and the underlying operating systems,
> network protocol stacks, and databases.
Middleware can modify, extend or reject processing event in many places of pipeline.
## Basics
Middleware instance can be applied for every type of Telegram Event (Update, Message, etc.) in two places
1. Outer scope - before processing filters (`#!python <router>.<event>.outer_middleware(...)`)
2. Inner scope - after processing filters but before handler (`#!python <router>.<event>.middleware(...)`)
[![middlewares](../assets/images/basics_middleware.png)](../assets/images/basics_middleware.png)
_(Click on image to zoom it)_
!!! warning
Middleware should be subclass of `BaseMiddleware` (`#!python3 from aiogram import BaseMiddleware`) or any async callable
## Arguments specification
| Argument | Type | Description |
| - | - | - |
| `handler` | `#!python Callable[[T, Dict[str, Any]], Awaitable[Any]]` | Wrapped handler in middlewares chain |
| `event` | `#!python T` | Incoming event (Subclass of `TelegramObject`) |
| `data` | `#!python Dict[str, Any]` | Contextual data. Will be mapped to handler arguments |
## Examples
!!! danger
Middleware should always call `#!python await handler(event, data)` to propagate event for next middleware/handler
### Class-based
```python3
from aiogram import BaseMiddleware
from aiogram.api.types import Message
class CounterMiddleware(BaseMiddleware[Message]):
def __init__(self) -> None:
self.counter = 0
async def __call__(
self,
handler: Callable[[Message, Dict[str, Any]], Awaitable[Any]],
event: Message,
data: Dict[str, Any]
) -> Any:
self.counter += 1
data['counter'] = self.counter
return await handler(event, data)
```
and then
```python3
router = Router()
router.message.middleware(CounterMiddleware())
```
### Function-based
```python3
@dispatcher.update.outer_middleware()
async def database_transaction_middleware(
handler: Callable[[Update, Dict[str, Any]], Awaitable[Any]],
event: Update,
data: Dict[str, Any]
) -> Any:
async with database.transaction():
return await handler(event, data)
```
## Facts
1. Middlewares from outer scope will be called on every incoming event
1. Middlewares from inner scope will be called only when filters pass
1. Inner middlewares is always calls for `Update` event type in due to all incoming updates going to specific event type handler through built in update handler

View file

@ -1,117 +0,0 @@
# Basics
All middlewares should be made with `BaseMiddleware` (`#!python3 from aiogram import BaseMiddleware`) as base class.
For example:
```python3
class MyMiddleware(BaseMiddleware): ...
```
And then use next pattern in naming callback functions in middleware: `on_{step}_{event}`
Where is:
- `#!python3 step`:
- `#!python3 pre_process`
- `#!python3 process`
- `#!python3 post_process`
- `#!python3 event`:
- `#!python3 update`
- `#!python3 message`
- `#!python3 edited_message`
- `#!python3 channel_post`
- `#!python3 edited_channel_post`
- `#!python3 inline_query`
- `#!python3 chosen_inline_result`
- `#!python3 callback_query`
- `#!python3 shipping_query`
- `#!python3 pre_checkout_query`
- `#!python3 poll`
- `#!python3 poll_answer`
- `#!python3 error`
## Connecting middleware with router
Middlewares can be connected with router by next ways:
1. `#!python3 router.use(MyMiddleware())` (**recommended**)
1. `#!python3 router.middleware.setup(MyMiddleware())`
1. `#!python3 MyMiddleware().setup(router.middleware)` (**not recommended**)
!!! warning
One instance of middleware **can't** be registered twice in single or many middleware managers
## The specification of step callbacks
### Pre-process step
| Argument | Type | Description |
| --- | --- | --- |
| event name | Any of event type (Update, Message and etc.) | Event |
| `#!python3 data` | `#!python3 Dict[str, Any]` | Contextual data (Will be mapped to handler arguments) |
Returns `#!python3 Any`
### Process step
| Argument | Type | Description |
| --- | --- | --- |
| event name | Any of event type (Update, Message and etc.) | Event |
| `#!python3 data` | `#!python3 Dict[str, Any]` | Contextual data (Will be mapped to handler arguments) |
Returns `#!python3 Any`
### Post-Process step
| Argument | Type | Description |
| --- | --- | --- |
| event name | Any of event type (Update, Message and etc.) | Event |
| `#!python3 data` | `#!python3 Dict[str, Any]` | Contextual data (Will be mapped to handler arguments) |
| `#!python3 result` | `#!python3 Dict[str, Any]` | Response from handlers |
Returns `#!python3 Any`
## Full list of available callbacks
- `#!python3 on_pre_process_update` - will be triggered on **pre process** `#!python3 update` event
- `#!python3 on_process_update` - will be triggered on **process** `#!python3 update` event
- `#!python3 on_post_process_update` - will be triggered on **post process** `#!python3 update` event
- `#!python3 on_pre_process_message` - will be triggered on **pre process** `#!python3 message` event
- `#!python3 on_process_message` - will be triggered on **process** `#!python3 message` event
- `#!python3 on_post_process_message` - will be triggered on **post process** `#!python3 message` event
- `#!python3 on_pre_process_edited_message` - will be triggered on **pre process** `#!python3 edited_message` event
- `#!python3 on_process_edited_message` - will be triggered on **process** `#!python3 edited_message` event
- `#!python3 on_post_process_edited_message` - will be triggered on **post process** `#!python3 edited_message` event
- `#!python3 on_pre_process_channel_post` - will be triggered on **pre process** `#!python3 channel_post` event
- `#!python3 on_process_channel_post` - will be triggered on **process** `#!python3 channel_post` event
- `#!python3 on_post_process_channel_post` - will be triggered on **post process** `#!python3 channel_post` event
- `#!python3 on_pre_process_edited_channel_post` - will be triggered on **pre process** `#!python3 edited_channel_post` event
- `#!python3 on_process_edited_channel_post` - will be triggered on **process** `#!python3 edited_channel_post` event
- `#!python3 on_post_process_edited_channel_post` - will be triggered on **post process** `#!python3 edited_channel_post` event
- `#!python3 on_pre_process_inline_query` - will be triggered on **pre process** `#!python3 inline_query` event
- `#!python3 on_process_inline_query` - will be triggered on **process** `#!python3 inline_query` event
- `#!python3 on_post_process_inline_query` - will be triggered on **post process** `#!python3 inline_query` event
- `#!python3 on_pre_process_chosen_inline_result` - will be triggered on **pre process** `#!python3 chosen_inline_result` event
- `#!python3 on_process_chosen_inline_result` - will be triggered on **process** `#!python3 chosen_inline_result` event
- `#!python3 on_post_process_chosen_inline_result` - will be triggered on **post process** `#!python3 chosen_inline_result` event
- `#!python3 on_pre_process_callback_query` - will be triggered on **pre process** `#!python3 callback_query` event
- `#!python3 on_process_callback_query` - will be triggered on **process** `#!python3 callback_query` event
- `#!python3 on_post_process_callback_query` - will be triggered on **post process** `#!python3 callback_query` event
- `#!python3 on_pre_process_shipping_query` - will be triggered on **pre process** `#!python3 shipping_query` event
- `#!python3 on_process_shipping_query` - will be triggered on **process** `#!python3 shipping_query` event
- `#!python3 on_post_process_shipping_query` - will be triggered on **post process** `#!python3 shipping_query` event
- `#!python3 on_pre_process_pre_checkout_query` - will be triggered on **pre process** `#!python3 pre_checkout_query` event
- `#!python3 on_process_pre_checkout_query` - will be triggered on **process** `#!python3 pre_checkout_query` event
- `#!python3 on_post_process_pre_checkout_query` - will be triggered on **post process** `#!python3 pre_checkout_query` event
- `#!python3 on_pre_process_poll` - will be triggered on **pre process** `#!python3 poll` event
- `#!python3 on_process_poll` - will be triggered on **process** `#!python3 poll` event
- `#!python3 on_post_process_poll` - will be triggered on **post process** `#!python3 poll` event
- `#!python3 on_pre_process_poll_answer` - will be triggered on **pre process** `#!python3 poll_answer` event
- `#!python3 on_process_poll_answer` - will be triggered on **process** `#!python3 poll_answer` event
- `#!python3 on_post_process_poll_answer` - will be triggered on **post process** `#!python3 poll_answer` event
- `#!python3 on_pre_process_error` - will be triggered on **pre process** `#!python3 error` event
- `#!python3 on_process_error` - will be triggered on **process** `#!python3 error` event
- `#!python3 on_post_process_error` - will be triggered on **post process** `#!python3 error` event

View file

@ -1,77 +0,0 @@
# Overview
**aiogram** provides powerful mechanism for customizing event handlers via middlewares.
Middlewares in bot framework seems like Middlewares mechanism in web-frameworks
(like [aiohttp](https://docs.aiohttp.org/en/stable/web_advanced.html#aiohttp-web-middlewares),
[fastapi](https://fastapi.tiangolo.com/tutorial/middleware/),
[Django](https://docs.djangoproject.com/en/3.0/topics/http/middleware/) or etc.)
with small difference - here is implemented two layers of processing
(named as [pipeline](#event-pipeline)).
!!! info
Middleware is function that triggered on every event received from
Telegram Bot API in many points on processing pipeline.
## Base theory
As many books and other literature in internet says:
> Middleware is reusable software that leverages patterns and frameworks to bridge
>the gap between the functional requirements of applications and the underlying operating systems,
> network protocol stacks, and databases.
Middleware can modify, extend or reject processing event before-,
on- or after- processing of that event.
[![middlewares](../../assets/images/basics_middleware.png)](../../assets/images/basics_middleware.png)
_(Click on image to zoom it)_
## Event pipeline
As described below middleware an interact with event in many stages of pipeline.
Simple workflow:
1. Dispatcher receive an [Update](../../api/types/update.md)
1. Call **pre-process** update middleware in all routers tree
1. Filter Update over handlers
1. Call **process** update middleware in all routers tree
1. Router detects event type (Message, Callback query, etc.)
1. Router triggers **pre-process** <event> middleware of specific type
1. Pass event over [filters](../filters/index.md) to detect specific handler
1. Call **process** <event> middleware for specific type (only when handler for this event exists)
1. *Do magick*. Call handler (Read more [Event observers](../router.md#event-observers))
1. Call **post-process** <event> middleware
1. Call **post-process** update middleware in all routers tree
1. Emit response into webhook (when it needed)
!!! warning
When filters does not match any handler with this event the `#!python3 process`
step will not be called.
!!! warning
When exception will be caused in handlers pipeline will be stopped immediately
and then start processing error via errors handler and it own middleware callbacks.
!!! warning
Middlewares for updates will be called for all routers in tree but callbacks for events
will be called only for specific branch of routers.
### Pipeline in pictures:
#### Simple pipeline
[![middlewares](../../assets/images/middleware_pipeline.png)](../../assets/images/middleware_pipeline.png)
_(Click on image to zoom it)_
#### Nested routers pipeline
[![middlewares](../../assets/images/middleware_pipeline_nested.png)](../../assets/images/middleware_pipeline_nested.png)
_(Click on image to zoom it)_
## Read more
- [Middleware Basics](basics.md)

View file

@ -20,7 +20,7 @@ Documentation for version 3.0 [WIP] [^1]
- [Supports Telegram Bot API v{!_api_version.md!}](api/index.md)
- [Updates router](dispatcher/index.md) (Blueprints)
- Finite State Machine
- [Middlewares](dispatcher/middlewares/index.md)
- [Middlewares](dispatcher/middlewares.md)
- [Replies into Webhook](https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates)

View file

@ -0,0 +1,12 @@
@font-face {
font-family: 'JetBrainsMono';
src: url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Regular.woff2') format('woff2'),
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-Regular.woff') format('woff'),
url('https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/ttf/JetBrainsMono-Regular.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
code, kbd, pre {
font-family: "JetBrainsMono", "Roboto Mono", "Courier New", Courier, monospace;
}

View file

@ -16,6 +16,9 @@ theme:
favicon: 'assets/images/logo.png'
logo: 'assets/images/logo.png'
extra_css:
- stylesheets/extra.css
extra:
version: 3.0.0a3
@ -255,9 +258,8 @@ nav:
- dispatcher/class_based_handlers/pre_checkout_query.md
- dispatcher/class_based_handlers/shipping_query.md
- dispatcher/class_based_handlers/error.md
- Middlewares:
- dispatcher/middlewares/index.md
- dispatcher/middlewares/basics.md
- dispatcher/middlewares.md
- todo.md
- Build reports:
- reports.md

16
poetry.lock generated
View file

@ -434,14 +434,6 @@ html5 = ["html5lib"]
htmlsoup = ["beautifulsoup4"]
source = ["Cython (>=0.29.7)"]
[[package]]
category = "main"
description = "This package provides magic filter based on dynamic attribute getter"
name = "magic-filter"
optional = false
python-versions = ">=3.6.1,<4.0.0"
version = "0.1.2"
[[package]]
category = "dev"
description = "Python implementation of Markdown."
@ -963,7 +955,7 @@ python-versions = "*"
version = "1.4.1"
[[package]]
category = "main"
category = "dev"
description = "Backported and Experimental Type Hints for Python 3.5+"
name = "typing-extensions"
optional = false
@ -1039,7 +1031,7 @@ fast = ["uvloop"]
proxy = ["aiohttp-socks"]
[metadata]
content-hash = "5c53527f09e65af097aa3d3a25e41646e8b8a0dda25e96445ceef969c19297e5"
content-hash = "768759359beca8b84811bfc21adac9649925cd22b87427a10608c9d1e16a0923"
python-versions = "^3.7"
[metadata.files]
@ -1256,10 +1248,6 @@ lxml = [
{file = "lxml-4.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:d5b3c4b7edd2e770375a01139be11307f04341ec709cf724e0f26ebb1eef12c3"},
{file = "lxml-4.5.0.tar.gz", hash = "sha256:8620ce80f50d023d414183bf90cc2576c2837b88e00bea3f33ad2630133bbb60"},
]
magic-filter = [
{file = "magic-filter-0.1.2.tar.gz", hash = "sha256:dfd1a778493083ac1355791d1716c3beb6629598739f2c2ec078815952282c1d"},
{file = "magic_filter-0.1.2-py3-none-any.whl", hash = "sha256:16d0c96584f0660fd7fa94b6cd16f92383616208a32568bf8f95a57fc1a69e9d"},
]
markdown = [
{file = "Markdown-3.2.1-py2.py3-none-any.whl", hash = "sha256:e4795399163109457d4c5af2183fbe6b60326c17cfdf25ce6e7474c6624f725d"},
{file = "Markdown-3.2.1.tar.gz", hash = "sha256:90fee683eeabe1a92e149f7ba74e5ccdc81cd397bd6c516d93a8da0ef90b6902"},

View file

@ -40,8 +40,6 @@ aiofiles = "^0.4.0"
uvloop = {version = "^0.14.0", markers = "sys_platform == 'darwin' or sys_platform == 'linux'", optional = true}
async_lru = "^1.0"
aiohttp-socks = {version = "^0.3.8", optional = true}
typing-extensions = "^3.7.4"
magic-filter = "^0.1.2"
[tool.poetry.dev-dependencies]
uvloop = {version = "^0.14.0", markers = "sys_platform == 'darwin' or sys_platform == 'linux'"}
@ -69,6 +67,7 @@ markdown-include = "^0.5.1"
aiohttp-socks = "^0.3.4"
pre-commit = "^2.3.0"
packaging = "^20.3"
typing-extensions = "^3.7.4"
[tool.poetry.extras]
fast = ["uvloop"]