Merge branch 'dev-3.x' into docs-update

This commit is contained in:
Alex Root Junior 2023-11-16 02:33:20 +02:00 committed by GitHub
commit fd5ca38c36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1216 changed files with 25995 additions and 39588 deletions

View file

@ -6,15 +6,15 @@ BaseHandler
Base handler is generic abstract class and should be used in all other class-based handlers.
Import: :code:`from aiogram.handler import BaseHandler`
Import: :code:`from aiogram.handlers import BaseHandler`
By default you will need to override only method :code:`async def handle(self) -> Any: ...`
This class is also have an default initializer and you don't need to change it.
Initializer accepts current event and all contextual data and which
This class also has a default initializer and you don't need to change it.
The initializer accepts the incoming event and all contextual data, which
can be accessed from the handler through attributes: :code:`event: TelegramEvent` and :code:`data: Dict[Any, str]`
If instance of the bot is specified in context data or current context it can be accessed through *bot* class attribute.
If an instance of the bot is specified in context data or current context it can be accessed through *bot* class attribute.
Example
=======

View file

@ -21,7 +21,7 @@ Simple usage
Extension
=========
This base handler is subclass of [BaseHandler](basics.md#basehandler) with some extensions:
This base handler is subclass of :ref:`BaseHandler <cbh-base-handler>` with some extensions:
- :code:`self.chat` is alias for :code:`self.event.chat`
- :code:`self.from_user` is alias for :code:`self.event.from_user`

View file

@ -0,0 +1,72 @@
####################
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 <https://en.wikipedia.org/wiki/SOLID>`_ dependency
inversion and single responsibility principles.
How it works in aiogram
=======================
For each update :class:`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 :class:`aiogram.fsm.context.FSMContext` we do it like that:
.. code-block:: python
@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
:class:`aiogram.dispatcher.dispatcher.Dispatcher` initialization, polling start methods
or :class:`aiogram.webhook.aiohttp_server.SimpleRequestHandler` initialization if you use webhooks.
.. code-block:: python
async def main() -> None:
dp = Dispatcher(..., foo=42)
return await dp.start_polling(
bot, bar="Bazz"
)
Analogy for webhook:
.. code-block:: python
async def main() -> None:
dp = Dispatcher(..., foo=42)
handler = SimpleRequestHandler(dispatcher=dp, bot=bot, bar="Bazz")
... # starting webhook
:class:`aiogram.dispatcher.dispatcher.Dispatcher`'s workflow data also can be supplemented
by setting values as in a dictionary:
.. code-block:: python
dp = Dispatcher(...)
dp["eggs"] = Spam()
The middlewares updates the context quite often.
You can read more about them on this page:
- :ref:`Middlewares <middlewares>`
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.

View file

@ -4,14 +4,14 @@ Dispatcher
Dispatcher is root :obj:`Router` and in code Dispatcher can be used directly for routing updates or attach another routers into dispatcher.
Here is only listed base information about Dispatcher. All about writing handlers, filters and etc. you can found in next pages:
Here is only listed base information about Dispatcher. All about writing handlers, filters and etc. you can find in next pages:
- `Router <router.html>`__
- `Observer <observer.html>`__
- :ref:`Router <Router>`
- :ref:`Filtering events`
.. autoclass:: aiogram.dispatcher.dispatcher.Dispatcher
:members: __init__, feed_update, feed_raw_update, feed_webhook_update, start_polling, run_polling
:members: __init__, feed_update, feed_raw_update, feed_webhook_update, start_polling, run_polling, stop_polling
Simple usage

View file

@ -0,0 +1,49 @@
######
Errors
######
Handling errors
===============
Is recommended way that you should use errors inside handlers using try-except block,
but in common cases you can use global errors handler at router or dispatcher level.
If you specify errors handler for router - it will be used for all handlers inside this router.
If you specify errors handler for dispatcher - it will be used for all handlers inside all routers.
.. code-block:: python
@router.error(ExceptionTypeFilter(MyCustomException), F.update.message.as_("message"))
async def handle_my_custom_exception(event: ErrorEvent, message: Message):
# do something with error
await message.answer("Oops, something went wrong!")
@router.error()
async def error_handler(event: ErrorEvent):
logger.critical("Critical error caused by %s", event.exception, exc_info=True)
# do something with error
...
.. _error-event:
ErrorEvent
==========
.. automodule:: aiogram.types.error_event
:members:
:member-order: bysource
:undoc-members: True
:exclude-members: model_config,model_fields
.. _error-types:
Error types
===========
.. automodule:: aiogram.exceptions
:members:
:member-order: bysource

View file

@ -1,3 +1,5 @@
.. _Callback data factory:
==============================
Callback Data Factory & Filter
==============================
@ -6,6 +8,7 @@ Callback Data Factory & Filter
:members:
:member-order: bysource
:undoc-members: False
:exclude-members: model_config,model_fields
Usage
=====

View file

@ -2,6 +2,27 @@
ChatMemberUpdated
=================
Usage
=====
Handle user leave or join events
.. code-block:: python
from aiogram.filters import IS_MEMBER, IS_NOT_MEMBER
@router.chat_member(ChatMemberUpdatedFilter(IS_MEMBER >> IS_NOT_MEMBER))
async def on_user_leave(event: ChatMemberUpdated): ...
@router.chat_member(ChatMemberUpdatedFilter(IS_NOT_MEMBER >> IS_MEMBER))
async def on_user_join(event: ChatMemberUpdated): ...
Or construct your own terms via using pre-defined set of statuses and transitions.
Explanation
===========
.. autoclass:: aiogram.filters.chat_member_updated.ChatMemberUpdatedFilter
:members:
:member-order: bysource
@ -77,22 +98,6 @@ will produce swap of old and new statuses.
Note that if you define the status unions (via :code:`|`) you will need to add brackets for the statement
before use shift operator in due to operator priorities.
Usage
=====
Handle user leave or join events
.. code-block:: python
from aiogram.filters import IS_MEMBER, IS_NOT_MEMBER
@router.chat_member(ChatMemberUpdatedFilter(IS_MEMBER >> IS_NOT_MEMBER))
async def on_user_leave(event: ChatMemberUpdated): ...
@router.chat_member(ChatMemberUpdatedFilter(IS_NOT_MEMBER >> IS_MEMBER))
async def on_user_join(event: ChatMemberUpdated): ...
Or construct your own terms via using pre-defined set of statuses and transitions.
Allowed handlers
================

View file

@ -2,19 +2,6 @@
Command
=======
.. autoclass:: aiogram.filters.command.Command
:members: __init__
:member-order: bysource
:undoc-members: False
When filter is passed the :class:`aiogram.filters.command.CommandObject` will be passed to the handler argument :code:`command`
.. autoclass:: aiogram.filters.command.CommandObject
:members:
:member-order: bysource
:undoc-members: False
Usage
=====
@ -28,6 +15,19 @@ Usage
Command cannot include spaces or any whitespace
.. autoclass:: aiogram.filters.command.Command
:members: __init__
:member-order: bysource
:undoc-members: False
When filter is passed the :class:`aiogram.filters.command.CommandObject` will be passed to the handler argument :code:`command`
.. autoclass:: aiogram.filters.command.CommandObject
:members:
:member-order: bysource
:undoc-members: False
Allowed handlers
================

View file

@ -1,11 +1,14 @@
.. _Filtering events:
================
Filtering events
================
Filters is needed for routing updates to the specific handler.
Searching of handler is always stops on first match set of filters are pass.
By default, all handlers has empty set of filters, so all updates will be passed to first handler that has empty set of filters.
*aiogram* has some builtin useful filters.
*aiogram* has some builtin useful filters or you can write own filters.
Builtin filters
===============

View file

@ -2,6 +2,14 @@
MagicData
=========
Usage
=====
#. :code:`MagicData(F.event.from_user.id == F.config.admin_id)` (Note that :code:`config` should be passed from middleware)
Explanation
===========
.. autoclass:: aiogram.filters.magic_data.MagicData
:members:
:member-order: bysource
@ -11,11 +19,6 @@ Can be imported:
- :code:`from aiogram.filters import MagicData`
Usage
=====
#. :code:`MagicData(F.event.from_user.id == F.config.admin_id)` (Note that :code:`config` should be passed from middleware)
Allowed handlers
================

View file

@ -1,3 +1,5 @@
.. _Finite State Machine:
====================
Finite State Machine
====================

View file

@ -15,13 +15,23 @@ With dispatcher you can do:
Dispatcher is also separated into two entities - Router and Dispatcher.
Dispatcher is subclass of router and should be always is root router.
Telegram supports two ways of receiving updates:
- :ref:`Webhook <webhook>` - you should configure your web server to receive updates from Telegram;
- :ref:`Long polling <long-polling>` - you should request updates from Telegram.
So, you can use both of them with *aiogram*.
.. toctree::
observer
router
dispatcher
class_based_handlers/index
dependency_injection
filters/index
middlewares
long_polling
webhook
finite_state_machine/index
middlewares
errors
flags
class_based_handlers/index

View file

@ -0,0 +1,32 @@
.. _long-polling:
############
Long-polling
############
Long-polling is a technology that allows a Telegram server to send updates in case
when you don't have dedicated IP address or port to receive webhooks for example
on a developer machine.
To use long-polling mode you should use :meth:`aiogram.dispatcher.dispatcher.Dispatcher.start_polling`
or :meth:`aiogram.dispatcher.dispatcher.Dispatcher.run_polling` methods.
.. note::
You can use polling from only one polling process per single Bot token,
in other case Telegram server will return an error.
.. note::
If you will need to scale your bot, you should use webhooks instead of long-polling.
.. note::
If you will use multibot mode, you should use webhook mode for all bots.
Example
=======
This example will show you how to create simple echo bot based on long-polling.
.. literalinclude:: ../../examples/echo_bot.py

View file

@ -1,3 +1,5 @@
.. _middlewares:
===========
Middlewares
===========
@ -57,7 +59,8 @@ Examples
.. danger::
Middleware should always call :code:`await handler(event, data)` to propagate event for next middleware/handler
Middleware should always call :code:`await handler(event, data)` to propagate event for next middleware/handler.
If you want to stop processing event in middleware you should not call :code:`await handler(event, data)`.
Class-based

View file

@ -1,26 +0,0 @@
########
Observer
########
Observer is used for filtering and handling different events. That is part of internal API with some public methods and is recommended to don't use methods is not listed here.
In `aiogram` framework is available two variants of observer:
- `EventObserver <#eventobserver>`__
- `TelegramEventObserver <#telegrameventobserver>`__
EventObserver
=============
.. autoclass:: aiogram.dispatcher.event.event.EventObserver
:members: register, trigger, __call__
:member-order: bysource
TelegramEventObserver
=====================
.. autoclass:: aiogram.dispatcher.event.telegram.TelegramEventObserver
:members: register, trigger, __call__, bind_filter, middleware, outer_middleware
:member-order: bysource

View file

@ -1,12 +1,30 @@
.. _Router:
######
Router
######
Usage:
.. code-block:: python
from aiogram import Router
from aiogram.types import Message
my_router = Router(name=__name__)
@my_router.message()
async def message_handler(message: Message) -> Any:
await message.answer('Hello from my router!')
.. autoclass:: aiogram.dispatcher.router.Router
:members: __init__, include_router
:members: __init__, include_router, include_routers, resolve_used_update_types
:show-inheritance:
.. _Event observers:
Event observers
===============
@ -19,19 +37,6 @@ Here is the list of available observers and examples of how to register handlers
In these examples only decorator-style registering handlers are used, but if you don't like @decorators just use :obj:`<event type>.register(...)` method instead.
Update
------
.. code-block:: python
@router.update()
async def message_handler(update: types.Update) -> Any: pass
.. note::
By default Router already has an update handler which route all event types to another observers.
Message
-------
@ -143,9 +148,12 @@ Errors
@router.errors()
async def error_handler(exception: ErrorEvent) -> Any: pass
Is useful for handling errors from other handlers
Is useful for handling errors from other handlers, error event described :ref:`here <error-event>`
.. _Nested routers:
Nested routers
==============
@ -159,8 +167,8 @@ Nested routers
Example:
.. code-block:: python
:caption: module_2.py
:name: module_2
:caption: module_1.py
:name: module_1
router2 = Router()
@ -170,7 +178,7 @@ Example:
.. code-block:: python
:caption: module_2.py
:name: module_1
:name: module_2
from module_2 import router2
@ -179,6 +187,22 @@ Example:
router1.include_router(router2)
Update
------
.. code-block:: python
@dispatcher.update()
async def message_handler(update: types.Update) -> Any: pass
.. warning::
The only root Router (Dispatcher) can handle this type of event.
.. note::
Dispatcher already has default handler for this event type, so you can use it for handling all updates that are not handled by any other handlers.
How it works?
-------------

125
docs/dispatcher/webhook.rst Normal file
View file

@ -0,0 +1,125 @@
.. _webhook:
#######
Webhook
#######
Telegram Bot API supports webhook.
If you set webhook for your bot, Telegram will send updates to the specified url.
You can use :meth:`aiogram.methods.set_webhook.SetWebhook` method to specify a url
and receive incoming updates on it.
.. note::
If you use webhook, you can't use long polling at the same time.
Before start i'll recommend you to read `official Telegram's documentation about webhook <https://core.telegram.org/bots/webhooks>`_
After you read it, you can start to read this section.
Generally to use webhook with aiogram you should use any async web framework.
By out of the box aiogram has an aiohttp integration, so we'll use it.
.. note::
You can use any async web framework you want, but you should write your own integration if you don't use aiohttp.
aiohttp integration
===================
Out of the box aiogram has aiohttp integration, so you can use it.
Here is available few ways to do it using different implementations of the webhook controller:
- :class:`aiogram.webhook.aiohttp_server.BaseRequestHandler` - Abstract class for aiohttp webhook controller
- :class:`aiogram.webhook.aiohttp_server.SimpleRequestHandler` - Simple webhook controller, uses single Bot instance
- :class:`aiogram.webhook.aiohttp_server.TokenBasedRequestHandler` - Token based webhook controller, uses multiple Bot instances and tokens
You can use it as is or inherit from it and override some methods.
.. autoclass:: aiogram.webhook.aiohttp_server.BaseRequestHandler
:members: __init__, register, close, resolve_bot, verify_secret, handle
.. autoclass:: aiogram.webhook.aiohttp_server.SimpleRequestHandler
:members: __init__, register, close, resolve_bot, verify_secret, handle
.. autoclass:: aiogram.webhook.aiohttp_server.TokenBasedRequestHandler
:members: __init__, register, close, resolve_bot, verify_secret, handle
Security
--------
Telegram supports two methods to verify incoming requests that they are from Telegram:
Using a secret token
~~~~~~~~~~~~~~~~~~~~
When you set webhook, you can specify a secret token and then use it to verify incoming requests.
Using IP filtering
~~~~~~~~~~~~~~~~~~
You can specify a list of IP addresses from which you expect incoming requests, and then use it to verify incoming requests.
It can be acy using firewall rules or nginx configuration or middleware on application level.
So, aiogram has an implementation of the IP filtering middleware for aiohttp.
.. autofunction:: aiogram.webhook.aiohttp_server.ip_filter_middleware
.. autoclass:: aiogram.webhook.security.IPFilter
:members: __init__, allow, allow_ip, default, check
Examples
--------
Behind reverse proxy
~~~~~~~~~~~~~~~~~~~~
In this example we'll use aiohttp as web framework and nginx as reverse proxy.
.. literalinclude:: ../../examples/echo_bot_webhook.py
When you use nginx as reverse proxy, you should set `proxy_pass` to your aiohttp server address.
.. code-block:: nginx
location /webhook {
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_buffering off;
proxy_pass http://127.0.0.1:8080;
}
Without reverse proxy (not recommended)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In case without using reverse proxy, you can use aiohttp's ssl context.
Also this example contains usage with self-signed certificate.
.. literalinclude:: ../../examples/echo_bot_webhook_ssl.py
With using other web framework
==============================
You can pass incoming request to aiogram's webhook controller from any web framework you want.
Read more about it in :meth:`aiogram.dispatcher.dispatcher.Dispatcher.feed_webhook_update`
or :meth:`aiogram.dispatcher.dispatcher.Dispatcher.feed_update` methods.
.. code-block:: python
update = Update.model_validate(await request.json(), context={"bot": bot})
await dispatcher.feed_update(update)
.. note::
If you want to use reply into webhook, you should check that result of the :code:`feed_update`
methods is an instance of API method and build :code:`multipart/form-data`
or :code:`application/json` response body manually.