From 7fa3ebf8bafaa69338037273642de7df79180728 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 29 Jul 2023 03:42:04 +0300 Subject: [PATCH] More docs --- .butcher/templates/enums/index.rst.jinja2 | 2 + aiogram/client/session/base.py | 6 + aiogram/dispatcher/router.py | 8 +- aiogram/exceptions.py | 74 ++++- docs/api/enums/index.rst | 2 + docs/api/session/custom_server.rst | 8 +- docs/api/session/index.rst | 1 + docs/api/session/middleware.rst | 75 +++++ docs/api/types/error_event.rst | 10 - docs/conf.py | 3 +- docs/dispatcher/errors.rst | 49 ++++ docs/dispatcher/index.rst | 2 +- docs/dispatcher/observer.rst | 26 -- docs/dispatcher/router.rst | 45 +-- docs/install.rst | 10 - docs/migration_2_to_3.rst | 324 ++-------------------- pyproject.toml | 1 - 17 files changed, 266 insertions(+), 380 deletions(-) create mode 100644 docs/api/session/middleware.rst delete mode 100644 docs/api/types/error_event.rst create mode 100644 docs/dispatcher/errors.rst delete mode 100644 docs/dispatcher/observer.rst diff --git a/.butcher/templates/enums/index.rst.jinja2 b/.butcher/templates/enums/index.rst.jinja2 index 9cbf463a..23af1de3 100644 --- a/.butcher/templates/enums/index.rst.jinja2 +++ b/.butcher/templates/enums/index.rst.jinja2 @@ -1,3 +1,5 @@ +.. _enums: + ##### Enums ##### diff --git a/aiogram/client/session/base.py b/aiogram/client/session/base.py index 9342cbcc..650b44bc 100644 --- a/aiogram/client/session/base.py +++ b/aiogram/client/session/base.py @@ -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, diff --git a/aiogram/dispatcher/router.py b/aiogram/dispatcher/router.py index 85c41ae8..423b7173 100644 --- a/aiogram/dispatcher/router.py +++ b/aiogram/dispatcher/router.py @@ -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: diff --git a/aiogram/exceptions.py b/aiogram/exceptions.py index 7ca7dcdd..2632fcdc 100644 --- a/aiogram/exceptions.py +++ b/aiogram/exceptions.py @@ -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 diff --git a/docs/api/enums/index.rst b/docs/api/enums/index.rst index e496f5b4..9b60d606 100644 --- a/docs/api/enums/index.rst +++ b/docs/api/enums/index.rst @@ -1,3 +1,5 @@ +.. _enums: + ##### Enums ##### diff --git a/docs/api/session/custom_server.rst b/docs/api/session/custom_server.rst index 23126551..22c5b6bd 100644 --- a/docs/api/session/custom_server.rst +++ b/docs/api/session/custom_server.rst @@ -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: diff --git a/docs/api/session/index.rst b/docs/api/session/index.rst index dff24e7a..da92bd4d 100644 --- a/docs/api/session/index.rst +++ b/docs/api/session/index.rst @@ -8,3 +8,4 @@ Client sessions is used for interacting with API server. custom_server base aiohttp + middleware diff --git a/docs/api/session/middleware.rst b/docs/api/session/middleware.rst new file mode 100644 index 00000000..fb06daca --- /dev/null +++ b/docs/api/session/middleware.rst @@ -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 diff --git a/docs/api/types/error_event.rst b/docs/api/types/error_event.rst deleted file mode 100644 index 562ea15b..00000000 --- a/docs/api/types/error_event.rst +++ /dev/null @@ -1,10 +0,0 @@ -########## -ErrorEvent -########## - - -.. automodule:: aiogram.types.error_event - :members: - :member-order: bysource - :undoc-members: True - :exclude-members: model_config,model_fields diff --git a/docs/conf.py b/docs/conf.py index 1c89245d..583a1922 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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 diff --git a/docs/dispatcher/errors.rst b/docs/dispatcher/errors.rst new file mode 100644 index 00000000..ace9a81b --- /dev/null +++ b/docs/dispatcher/errors.rst @@ -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 diff --git a/docs/dispatcher/index.rst b/docs/dispatcher/index.rst index c684ba36..b7299ea9 100644 --- a/docs/dispatcher/index.rst +++ b/docs/dispatcher/index.rst @@ -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 diff --git a/docs/dispatcher/observer.rst b/docs/dispatcher/observer.rst deleted file mode 100644 index 9276dcc4..00000000 --- a/docs/dispatcher/observer.rst +++ /dev/null @@ -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 diff --git a/docs/dispatcher/router.rst b/docs/dispatcher/router.rst index 3550ea8f..13555ec9 100644 --- a/docs/dispatcher/router.rst +++ b/docs/dispatcher/router.rst @@ -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:`.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 ` + .. _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? ------------- diff --git a/docs/install.rst b/docs/install.rst index 7dbf3cf8..7a3b8218 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -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 diff --git a/docs/migration_2_to_3.rst b/docs/migration_2_to_3.rst index a9d476cd..c407ce8b 100644 --- a/docs/migration_2_to_3.rst +++ b/docs/migration_2_to_3.rst @@ -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 » `). 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 » `) - 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 » `_) -- 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 » `) -- 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 `_) - (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 » `) - 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 » `) 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 » `) 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 `_handler` in favor of `` (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. - - ??? diff --git a/pyproject.toml b/pyproject.toml index a9d32f1d..d4471685 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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",