mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
More docs
This commit is contained in:
parent
48c4884877
commit
7fa3ebf8ba
17 changed files with 266 additions and 380 deletions
|
|
@ -1,3 +1,5 @@
|
|||
.. _enums:
|
||||
|
||||
#####
|
||||
Enums
|
||||
#####
|
||||
|
|
|
|||
|
|
@ -53,6 +53,12 @@ DEFAULT_TIMEOUT: Final[float] = 60.0
|
|||
|
||||
|
||||
class BaseSession(abc.ABC):
|
||||
"""
|
||||
This is base class for all HTTP sessions in aiogram.
|
||||
|
||||
If you want to create your own session, you must inherit from this class.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
api: TelegramAPIServer = PRODUCTION,
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class Router:
|
|||
self.chat_member = TelegramEventObserver(router=self, event_name="chat_member")
|
||||
self.chat_join_request = TelegramEventObserver(router=self, event_name="chat_join_request")
|
||||
|
||||
self.errors = TelegramEventObserver(router=self, event_name="error")
|
||||
self.errors = self.error = TelegramEventObserver(router=self, event_name="error")
|
||||
|
||||
self.startup = EventObserver()
|
||||
self.shutdown = EventObserver()
|
||||
|
|
@ -184,6 +184,12 @@ class Router:
|
|||
router.sub_routers.append(self)
|
||||
|
||||
def include_routers(self, *routers: Router) -> None:
|
||||
"""
|
||||
Attach multiple routers.
|
||||
|
||||
:param routers:
|
||||
:return:
|
||||
"""
|
||||
if not routers:
|
||||
raise ValueError("At least one router must be provided")
|
||||
for router in routers:
|
||||
|
|
|
|||
|
|
@ -6,10 +6,16 @@ from aiogram.utils.link import docs_url
|
|||
|
||||
|
||||
class AiogramError(Exception):
|
||||
pass
|
||||
"""
|
||||
Base exception for all aiogram errors.
|
||||
"""
|
||||
|
||||
|
||||
class DetailedAiogramError(AiogramError):
|
||||
"""
|
||||
Base exception for all aiogram errors with detailed message.
|
||||
"""
|
||||
|
||||
url: Optional[str] = None
|
||||
|
||||
def __init__(self, message: str) -> None:
|
||||
|
|
@ -26,14 +32,24 @@ class DetailedAiogramError(AiogramError):
|
|||
|
||||
|
||||
class CallbackAnswerException(AiogramError):
|
||||
pass
|
||||
"""
|
||||
Exception for callback answer.
|
||||
"""
|
||||
|
||||
|
||||
class UnsupportedKeywordArgument(DetailedAiogramError):
|
||||
"""
|
||||
Exception raised when a keyword argument is passed as filter.
|
||||
"""
|
||||
|
||||
url = docs_url("migration_2_to_3.html", fragment_="filtering-events")
|
||||
|
||||
|
||||
class TelegramAPIError(DetailedAiogramError):
|
||||
"""
|
||||
Base exception for all Telegram API errors.
|
||||
"""
|
||||
|
||||
label: str = "Telegram server says"
|
||||
|
||||
def __init__(
|
||||
|
|
@ -50,10 +66,18 @@ class TelegramAPIError(DetailedAiogramError):
|
|||
|
||||
|
||||
class TelegramNetworkError(TelegramAPIError):
|
||||
"""
|
||||
Base exception for all Telegram network errors.
|
||||
"""
|
||||
|
||||
label = "HTTP Client says"
|
||||
|
||||
|
||||
class TelegramRetryAfter(TelegramAPIError):
|
||||
"""
|
||||
Exception raised when flood control exceeds.
|
||||
"""
|
||||
|
||||
url = "https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this"
|
||||
|
||||
def __init__(
|
||||
|
|
@ -73,6 +97,10 @@ class TelegramRetryAfter(TelegramAPIError):
|
|||
|
||||
|
||||
class TelegramMigrateToChat(TelegramAPIError):
|
||||
"""
|
||||
Exception raised when chat has been migrated to a supergroup.
|
||||
"""
|
||||
|
||||
url = "https://core.telegram.org/bots/api#responseparameters"
|
||||
|
||||
def __init__(
|
||||
|
|
@ -90,38 +118,66 @@ class TelegramMigrateToChat(TelegramAPIError):
|
|||
|
||||
|
||||
class TelegramBadRequest(TelegramAPIError):
|
||||
pass
|
||||
"""
|
||||
Exception raised when request is malformed.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramNotFound(TelegramAPIError):
|
||||
pass
|
||||
"""
|
||||
Exception raised when chat, message, user, etc. not found.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramConflictError(TelegramAPIError):
|
||||
pass
|
||||
"""
|
||||
Exception raised when bot token is already used by another application in polling mode.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramUnauthorizedError(TelegramAPIError):
|
||||
pass
|
||||
"""
|
||||
Exception raised when bot token is invalid.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramForbiddenError(TelegramAPIError):
|
||||
pass
|
||||
"""
|
||||
Exception raised when bot is kicked from chat or etc.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramServerError(TelegramAPIError):
|
||||
pass
|
||||
"""
|
||||
Exception raised when Telegram server returns 5xx error.
|
||||
"""
|
||||
|
||||
|
||||
class RestartingTelegram(TelegramServerError):
|
||||
pass
|
||||
"""
|
||||
Exception raised when Telegram server is restarting.
|
||||
|
||||
It seems like this error is not used by Telegram anymore,
|
||||
but it's still here for backward compatibility.
|
||||
|
||||
Currently, you should expect that Telegram can raise RetryAfter (with timeout 5 seconds)
|
||||
error instead of this one.
|
||||
"""
|
||||
|
||||
|
||||
class TelegramEntityTooLarge(TelegramNetworkError):
|
||||
"""
|
||||
Exception raised when you are trying to send a file that is too large.
|
||||
"""
|
||||
|
||||
url = "https://core.telegram.org/bots/api#sending-files"
|
||||
|
||||
|
||||
class ClientDecodeError(AiogramError):
|
||||
"""
|
||||
Exception raised when client can't decode response. (Malformed response, etc.)
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, original: Exception, data: Any) -> None:
|
||||
self.message = message
|
||||
self.original = original
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
.. _enums:
|
||||
|
||||
#####
|
||||
Enums
|
||||
#####
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
Use Custom API server
|
||||
=====================
|
||||
|
||||
.. autoclass:: aiogram.client.telegram.TelegramAPIServer
|
||||
:members:
|
||||
|
||||
For example, if you want to use self-hosted API server:
|
||||
|
||||
.. code-block:: python3
|
||||
.. code-block:: python
|
||||
|
||||
session = AiohttpSession(
|
||||
api=TelegramAPIServer.from_base('http://localhost:8082')
|
||||
)
|
||||
bot = Bot(..., session=session)
|
||||
|
||||
.. autoclass:: aiogram.client.telegram.TelegramAPIServer
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ Client sessions is used for interacting with API server.
|
|||
custom_server
|
||||
base
|
||||
aiohttp
|
||||
middleware
|
||||
|
|
|
|||
75
docs/api/session/middleware.rst
Normal file
75
docs/api/session/middleware.rst
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
##########################
|
||||
Client session middlewares
|
||||
##########################
|
||||
|
||||
In some cases you may want to add some middlewares to the client session to customize the behavior of the client.
|
||||
|
||||
Some useful cases that is:
|
||||
|
||||
- Log the outgoing requests
|
||||
- Customize the request parameters
|
||||
- Handle rate limiting errors and retry the request
|
||||
- others ...
|
||||
|
||||
So, you can do it using client session middlewares.
|
||||
A client session middleware is a function (or callable class) that receives the request and the next middleware to call.
|
||||
The middleware can modify the request and then call the next middleware to continue the request processing.
|
||||
|
||||
How to register client session middleware?
|
||||
==========================================
|
||||
|
||||
Register using register method
|
||||
------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
bot.session.middleware(RequestLogging(ignore_methods=[GetUpdates]))
|
||||
|
||||
Register using decorator
|
||||
------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@bot.session.middleware()
|
||||
async def my_middleware(
|
||||
make_request: NextRequestMiddlewareType[TelegramType],
|
||||
bot: "Bot",
|
||||
method: TelegramMethod[TelegramType],
|
||||
) -> Response[TelegramType]:
|
||||
# do something with request
|
||||
return await make_request(bot, method)
|
||||
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
Class based session middleware
|
||||
------------------------------
|
||||
|
||||
.. literalinclude:: ../../../aiogram/client/session/middlewares/request_logging.py
|
||||
:lines: 16-
|
||||
:language: python
|
||||
:linenos:
|
||||
|
||||
.. note::
|
||||
|
||||
this middlewware is already implemented inside aiogram, so, if you want to use it you can
|
||||
just import it :code:`from aiogram.client.session.middlewares.request_logging import RequestLogging`
|
||||
|
||||
|
||||
Function based session middleware
|
||||
---------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
make_request: NextRequestMiddlewareType[TelegramType],
|
||||
bot: "Bot",
|
||||
method: TelegramMethod[TelegramType],
|
||||
) -> Response[TelegramType]:
|
||||
try:
|
||||
# do something with request
|
||||
return await make_request(bot, method)
|
||||
finally:
|
||||
# do something after request
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
##########
|
||||
ErrorEvent
|
||||
##########
|
||||
|
||||
|
||||
.. automodule:: aiogram.types.error_event
|
||||
:members:
|
||||
:member-order: bysource
|
||||
:undoc-members: True
|
||||
:exclude-members: model_config,model_fields
|
||||
|
|
@ -28,7 +28,6 @@ extensions = [
|
|||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.ifconfig",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx-prompt",
|
||||
"sphinx_substitution_extensions",
|
||||
"sphinx_copybutton",
|
||||
"sphinxcontrib.towncrier.ext",
|
||||
|
|
@ -67,6 +66,6 @@ texinfo_documents = [
|
|||
|
||||
# add_module_names = False
|
||||
|
||||
towncrier_draft_autoversion_mode = 'draft'
|
||||
towncrier_draft_autoversion_mode = "draft"
|
||||
towncrier_draft_include_empty = False
|
||||
towncrier_draft_working_directory = Path(__file__).parent.parent
|
||||
|
|
|
|||
49
docs/dispatcher/errors.rst
Normal file
49
docs/dispatcher/errors.rst
Normal 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
|
||||
|
|
@ -17,7 +17,6 @@ Dispatcher is subclass of router and should be always is root router.
|
|||
|
||||
.. toctree::
|
||||
|
||||
observer
|
||||
router
|
||||
dispatcher
|
||||
class_based_handlers/index
|
||||
|
|
@ -25,3 +24,4 @@ Dispatcher is subclass of router and should be always is root router.
|
|||
middlewares
|
||||
finite_state_machine/index
|
||||
flags
|
||||
errors
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -3,7 +3,7 @@ Router
|
|||
######
|
||||
|
||||
.. autoclass:: aiogram.dispatcher.router.Router
|
||||
:members: __init__, include_router
|
||||
:members: __init__, include_router, include_routers, resolve_used_update_types
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
|
|
@ -21,23 +21,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
|
||||
|
||||
@dispatcher.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.
|
||||
|
||||
.. warning::
|
||||
|
||||
The only root Router (Dispatcher) can handle this type of event.
|
||||
|
||||
|
||||
Message
|
||||
-------
|
||||
|
||||
|
|
@ -149,10 +132,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
|
||||
==============
|
||||
|
||||
|
|
@ -166,8 +151,8 @@ Nested routers
|
|||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
:caption: module_2.py
|
||||
:name: module_2
|
||||
:caption: module_1.py
|
||||
:name: module_1
|
||||
|
||||
router2 = Router()
|
||||
|
||||
|
|
@ -177,7 +162,7 @@ Example:
|
|||
|
||||
.. code-block:: python
|
||||
:caption: module_2.py
|
||||
:name: module_1
|
||||
:name: module_2
|
||||
|
||||
from module_2 import router2
|
||||
|
||||
|
|
@ -186,6 +171,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?
|
||||
-------------
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
Installation
|
||||
############
|
||||
|
||||
Stable (2.x)
|
||||
============
|
||||
|
||||
From PyPI
|
||||
---------
|
||||
|
||||
|
|
@ -35,10 +32,3 @@ From GitHub
|
|||
.. code-block:: bash
|
||||
|
||||
pip install https://github.com/aiogram/aiogram/archive/refs/heads/dev-3.x.zip
|
||||
|
||||
From AUR
|
||||
--------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
yay -S python-aiogram3
|
||||
|
|
|
|||
|
|
@ -7,12 +7,20 @@ Migration FAQ (2.x -> 3.0)
|
|||
This guide is still in progress.
|
||||
|
||||
This version introduces much many breaking changes and architectural improvements,
|
||||
helping to reduce global variables count in your code, provides mechanisms useful mechanisms
|
||||
helping to reduce global variables count in your code, provides useful mechanisms
|
||||
to separate your code to modules or just make sharable modules via packages on the PyPi,
|
||||
makes middlewares and filters more controllable and others.
|
||||
|
||||
On this page you can read about points that changed corresponding to last stable 2.x version.
|
||||
|
||||
.. note::
|
||||
|
||||
This page is most like a detailed changelog than a migration guide,
|
||||
but it will be updated in the future.
|
||||
|
||||
Feel free to contribute to this page, if you find something that is not mentioned here.
|
||||
|
||||
|
||||
Dispatcher
|
||||
==========
|
||||
|
||||
|
|
@ -22,7 +30,7 @@ Dispatcher
|
|||
- :class:`Dispatcher` now can be extended with another Dispatcher-like
|
||||
thing named :class:`Router` (:ref:`Read more » <Nested routers>`).
|
||||
With routes you can easily separate your code to multiple modules
|
||||
and maybe share this modules between projects.
|
||||
and may be share this modules between projects.
|
||||
- Removed the **_handler** suffix from all event handler decorators and registering methods.
|
||||
(:ref:`Read more » <Event observers>`)
|
||||
- Executor entirely removed, now you can use Dispatcher directly to start polling or webhook.
|
||||
|
|
@ -31,24 +39,34 @@ Filtering events
|
|||
================
|
||||
|
||||
- Keyword filters can no more be used, use filters explicitly. (`Read more » <https://github.com/aiogram/aiogram/issues/942>`_)
|
||||
- In due to keyword filters was removed all enabled by default filters (state and content_type now is not enabled)
|
||||
- In due to keyword filters was removed all enabled by default filters (state and content_type now is not enabled),
|
||||
so you should specify them explicitly if you want to use.
|
||||
For example instead of using :code:`@dp.message_handler(content_types=ContentType.PHOTO)`
|
||||
you should use :code:`@router.message(F.photo)`
|
||||
- Most of common filters is replaced by "magic filter". (:ref:`Read more » <magic-filters>`)
|
||||
- Now by default message handler receives any content type, if you want specific one just add the filters (Magic or any other)
|
||||
- State filter is not enabled by default, that's mean if you using :code:`state="*"` in v2
|
||||
- Now by default message handler receives any content type,
|
||||
if you want specific one just add the filters (Magic or any other)
|
||||
- State filter now is not enabled by default, that's mean if you using :code:`state="*"` in v2
|
||||
then you should not pass any state filter in v3, and vice versa,
|
||||
if the state in v2 is not specified now you should specify the state.
|
||||
- Added possibility to register per-router global filters, that helps to reduces
|
||||
the number of repetitions in the code and makes easily way to control
|
||||
for what each router will be used.
|
||||
|
||||
Bot API Methods
|
||||
===============
|
||||
Bot API
|
||||
=======
|
||||
|
||||
- Now all API methods is classes with validation (via `pydantic <https://docs.pydantic.dev/>`_)
|
||||
(all API calls is also available as methods in the Bot class)
|
||||
- Increased pre-defined Enums count and moved into `aiogram.enums` sub-package.
|
||||
(all API calls is also available as methods in the Bot class).
|
||||
- Added more pre-defined Enums and moved into `aiogram.enums` sub-package. For example chat type enum now is
|
||||
:class:`aiogram.enums.ChatType` instead of :class:`aiogram.types.chat.ChatType`.
|
||||
(:ref:`Read more » <enums>`)
|
||||
- Separated HTTP client session into container that can be reused between different
|
||||
Bot instances in the application.
|
||||
- API Exceptions is no more classified by specific message in due to Telegram has no documented error codes.
|
||||
But all errors is classified by HTTP status code and for each method only one case can be caused with the same code,
|
||||
so in most cases you should check that only error type (by status-code) without checking error message.
|
||||
(:ref:`Read more » <error-types>`)
|
||||
|
||||
Middlewares
|
||||
===========
|
||||
|
|
@ -58,7 +76,7 @@ Middlewares
|
|||
For example now you can easily pass some data into context inside middleware and
|
||||
get it in the filters layer as the same way as in the handlers via keyword arguments.
|
||||
- Added mechanism named **flags**, that helps to customize handler behavior
|
||||
in conjunction with middlewares.
|
||||
in conjunction with middlewares. (:ref:`Read more » <flags>`)
|
||||
|
||||
Keyboard Markup
|
||||
===============
|
||||
|
|
@ -80,6 +98,8 @@ Finite State machine
|
|||
====================
|
||||
|
||||
- State filter will no more added to all handlers, you will need to specify state if you want
|
||||
- Added possibility to change FSM strategy, for example if you want to control state
|
||||
for each user in chat topics instead of user in chat you can specify it in the Dispatcher.
|
||||
|
||||
Sending Files
|
||||
=============
|
||||
|
|
@ -92,287 +112,3 @@ Webhook
|
|||
|
||||
- Simplified aiohttp web app configuration
|
||||
- By default added possibility to upload files when you use reply into webhook
|
||||
|
||||
|
||||
..
|
||||
This part of document will be removed
|
||||
|
||||
.. code-block:: markdown
|
||||
|
||||
## Keyboards and filters (part 1)
|
||||
|
||||
- `(Reply|Inline)KeyboardMarkup` is no longer used for building keyboards via `add`/`insert`/`row`, use `(Reply|Inline)KeyboardBuilder` and `button` instead.
|
||||
- `CallbackData` is now a base class, not a factory.
|
||||
- Integrate `[magic-filter](https://pypi.org/project/magic-filter/)` into aiogram.
|
||||
- Code for 2.x
|
||||
|
||||
```python
|
||||
from secrets import token_urlsafe
|
||||
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.types import (
|
||||
CallbackQuery,
|
||||
InlineKeyboardButton,
|
||||
InlineKeyboardMarkup,
|
||||
Message,
|
||||
)
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
|
||||
dp = Dispatcher(Bot(TOKEN))
|
||||
|
||||
vote_cb = CallbackData("vote", "action", "id", sep="_")
|
||||
votes = {} # For demo purposes only! Use database in real code!
|
||||
|
||||
@dp.message_handler(commands="start")
|
||||
async def post(message: Message) -> None:
|
||||
vote_id = token_urlsafe(8) # Lazy way to generate a random string
|
||||
kb = (
|
||||
InlineKeyboardMarkup(row_width=2)
|
||||
.insert(InlineKeyboardButton(text="+1", callback_data=vote_cb.new(action="up", id=vote_id)))
|
||||
.insert(InlineKeyboardButton(text="-1", callback_data=vote_cb.new(action="down", id=vote_id)))
|
||||
.insert(InlineKeyboardButton(text="?", callback_data=vote_cb.new(action="count", id=vote_id)))
|
||||
)
|
||||
await message.reply("Vote on this post", reply_markup=kb)
|
||||
|
||||
@dp.callback_query_handler(vote_cb.filter(action="count"))
|
||||
async def show_voters_count(query: CallbackQuery, callback_data: dict) -> None:
|
||||
vote_id = int(callback_data["id"])
|
||||
votes[vote_id] = votes.setdefault(vote_id, 0) + 1
|
||||
await query.answer(votes[vote_id], cache_time=1)
|
||||
|
||||
@dp.callback_query_handler(vote_cb.filter()) # all other actions
|
||||
async def vote(query: CallbackQuery, callback_data: dict) -> None:
|
||||
if (action := callback_data["action"]) == "up":
|
||||
d = 1
|
||||
elif action == "down":
|
||||
d = -1
|
||||
else:
|
||||
raise AssertionError(f"action action!r} is not implemented")
|
||||
votes[int(callback_data["id"])] += d
|
||||
await query.answer(f"{action.capitalize()}voted!")
|
||||
```
|
||||
|
||||
- Code for 3.0
|
||||
|
||||
```python
|
||||
from enum import Enum
|
||||
from secrets import token_urlsafe
|
||||
|
||||
from aiogram import Dispatcher, F
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiogram.dispatcher.filters.callback_data import CallbackData
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
dp = Dispatcher()
|
||||
votes = {} # For demo purposes only! Use database in real code!
|
||||
|
||||
class VoteAction(Enum):
|
||||
UP = "up"
|
||||
DOWN = "down"
|
||||
COUNT = "count"
|
||||
|
||||
class VoteCallback(CallbackData, prefix="vote", sep="_"):
|
||||
action: VoteAction # Yes, it also supports `Enum`s
|
||||
id: str
|
||||
|
||||
@dp.message(commands="start")
|
||||
async def post(message: Message) -> None:
|
||||
vote_id = token_urlsafe(8) # Lazy way to generate a random string
|
||||
kb = (
|
||||
InlineKeyboardBuilder()
|
||||
.button(text="+1", callback_data=VoteCallback(action=VoteAction.UP, id=vote_id))
|
||||
.button(text="-1", callback_data=VoteCallback(action=VoteAction.DOWN, id=vote_id))
|
||||
.button(text="?", callback_data=VoteCallback(action=VoteAction.COUNT, id=vote_id))
|
||||
.adjust(2) # row_width=2
|
||||
)
|
||||
await message.reply("Vote on this post", reply_markup=kb.as_markup())
|
||||
|
||||
# `F` is a `MagicFilter` instance, see docs for `magic-filter` for more info
|
||||
@dp.callback_query(VoteCallback.filter(F.action == VoteAction.COUNT))
|
||||
async def show_voters_count(
|
||||
query: CallbackQuery,
|
||||
callback_data: VoteCallback, # Now it is the class itself, not a mysterious `dict`
|
||||
) -> None:
|
||||
vote_id = callback_data.id
|
||||
votes[vote_id] = votes.setdefault(vote_id, 0) + 1
|
||||
await query.answer(votes[vote_id], cache_time=1)
|
||||
|
||||
@dp.callback_query(VoteCallback.filter()) # all other actions
|
||||
async def vote(query: CallbackQuery, callback_data: VoteCallback) -> None:
|
||||
if callback_data.action == VoteAction.UP:
|
||||
d = 1
|
||||
elif callback_data.action == VoteAction.DOWN:
|
||||
d = -1
|
||||
else:
|
||||
raise AssertionError(f"action {callback_data.action!r} is not implemented")
|
||||
votes[callback_data.id] += d
|
||||
await query.answer(f"{action.capitalize()}voted!")
|
||||
```
|
||||
|
||||
|
||||
## Code style
|
||||
|
||||
- Allow the code to be split into several files in a convenient way with `Router`s.
|
||||
- Make `Dispatcher` a router with some special abilities.
|
||||
- Remove `<event>_handler` in favor of `<event>` (e.g. `dp.message()` instead of `dp.message_handler()`)
|
||||
- Code for 2.x (one of possible ways)
|
||||
|
||||
```python
|
||||
from aiogram import Bot, Dispatcher, executor
|
||||
from mybot import handlers
|
||||
|
||||
dp = Dispatcher(Bot(TOKEN))
|
||||
|
||||
handlers.hello.setup(dp)
|
||||
...
|
||||
|
||||
executor.start_polling(dp, ...)
|
||||
```
|
||||
|
||||
```python
|
||||
from aiogram import Dispatcher
|
||||
from aiogram.types import Message
|
||||
|
||||
# No way to use decorators :(
|
||||
async def hello(message: Message) -> None:
|
||||
await message.reply("Hello!")
|
||||
|
||||
async def goodbye(message: Message) -> None:
|
||||
await message.reply("Bye!")
|
||||
|
||||
def setup(dp: Dispatcher) -> None:
|
||||
dp.register_message_handler(hello, commands=["hello", "hi"])
|
||||
dp.register_message_handler(goodbye, commands=["goodbye", "bye"])
|
||||
# This list can become huge in a time, may be inconvenient
|
||||
```
|
||||
|
||||
- Code for 3.0
|
||||
|
||||
```python
|
||||
from aiogram import Bot, Dispatcher
|
||||
from mybot import handlers
|
||||
|
||||
dp = Dispatcher()
|
||||
|
||||
# Any router can include a sub-router
|
||||
dp.include_router(handlers.hello.router) # `Dispatcher` is a `Router` too
|
||||
...
|
||||
|
||||
dp.run_polling(Bot(TOKEN)) # But it's special, e.g. it can `run_polling`
|
||||
```
|
||||
|
||||
```python
|
||||
from aiogram import Router
|
||||
from aiogram.types import Message
|
||||
|
||||
router = Router()
|
||||
|
||||
# Event handler decorator is an event type itself without `_handler` suffix
|
||||
@router.message(commands=["hello", "hi"]) # Yay, decorators!
|
||||
async def hello(message: Message) -> None:
|
||||
await message.reply("Hello!")
|
||||
|
||||
async def goodbye(message: Message) -> None:
|
||||
await message.reply("Bye!")
|
||||
|
||||
# If you still prefer registering handlers without decorators, use this
|
||||
router.message.register(goodbye, commands=["goodbye", "bye"])
|
||||
```
|
||||
|
||||
|
||||
## Webhooks and API methods
|
||||
|
||||
- All methods are classes now.
|
||||
- Allow using Reply into webhook with polling. *Check whether it worked in 2.x*
|
||||
- Webhook setup is more flexible ~~and complicated xd~~
|
||||
|
||||
## Exceptions
|
||||
|
||||
- No more specific exceptions, only by status code.
|
||||
- Code for 2.x [todo]
|
||||
|
||||
```python
|
||||
from asyncio import sleep
|
||||
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.dispatcher.filters import Command
|
||||
from aiogram.types import Message
|
||||
from aiogram.utils.exceptions import (
|
||||
BadRequest,
|
||||
BotBlocked,
|
||||
RestartingTelegram,
|
||||
RetryAfter,
|
||||
)
|
||||
|
||||
dp = Dispatcher(Bot(TOKEN))
|
||||
chats = set()
|
||||
|
||||
async def broadcaster(bot: Bot, chat: int, text: str) -> bool:
|
||||
"""Broadcasts a message and returns whether it was sent"""
|
||||
while True:
|
||||
try:
|
||||
await bot.send_message(chat, text)
|
||||
except BotBlocked:
|
||||
chats.discard(chat)
|
||||
log.warning("Remove chat %d because bot was blocked", chat)
|
||||
return False
|
||||
except RetryAfter as e:
|
||||
log.info("Sleeping %d due to flood wait", e.retry_after)
|
||||
await sleep(e.retry_after)
|
||||
continue
|
||||
except RestartingTelegram:
|
||||
log.info("Telegram is restarting, sleeping for 1 sec")
|
||||
await sleep(1)
|
||||
continue
|
||||
except BadRequest as e:
|
||||
log.warning("Remove chat %d because of bad request", chat)
|
||||
chats.discard(chat)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
@dp.message_handler(commands="broadcast")
|
||||
async def broadcast(message: Message, command: Command.CommandObj) -> None:
|
||||
# TODO ...
|
||||
```
|
||||
|
||||
- Code for 3.x [todo]
|
||||
|
||||
```python
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
## Filters (part 2)
|
||||
|
||||
- Remove the majority of filters in favor of `MagicFilter` (aka `F`).
|
||||
- Deprecate usage of bound filters in favor of classes, functions and `F`.
|
||||
- Message handler defaults to any content type.
|
||||
- Per-router filters.
|
||||
|
||||
## Middlewares and app state
|
||||
|
||||
- Rework middleware logic.
|
||||
- Pass `**kwargs` from `start_polling` to handlers and filters.
|
||||
- No more global `bot` and `message.bot`.
|
||||
- `bot["foo"]` → `dp["foo"]`.
|
||||
|
||||
## FSM
|
||||
|
||||
- FSMStrategy.
|
||||
- Default to any state.
|
||||
- States are also callable filters.
|
||||
- No more `next` and `proxy`.
|
||||
- No state filtering is done by default:
|
||||
|
||||
[Default state is not None · Issue #954 · aiogram/aiogram](https://github.com/aiogram/aiogram/issues/954#issuecomment-1172967490)
|
||||
|
||||
|
||||
## Misc
|
||||
|
||||
- No more unrelated attributes and methods in types.
|
||||
- `get_args()`
|
||||
- `get_(full_)command()`
|
||||
- …?
|
||||
- Add handler flags.
|
||||
- ???
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ docs = [
|
|||
"sphinx-autobuild~=2021.3.14",
|
||||
"sphinx-copybutton~=0.5.2",
|
||||
"furo~=2023.7.26",
|
||||
"sphinx-prompt~=1.7.0",
|
||||
"Sphinx-Substitution-Extensions~=2022.2.16",
|
||||
"towncrier~=23.6.0",
|
||||
"pygments~=2.15.1",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue