diff --git a/docs/source/index.rst b/docs/source/index.rst index 085b38fb..ca9a38a3 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -84,6 +84,7 @@ To get started, press the Next button. resources/UpdateHandling resources/UsingFilters + resources/MoreOnUpdates resources/SmartPlugins resources/AutoAuthorization resources/CustomizeSessions diff --git a/docs/source/resources/MoreOnUpdates.rst b/docs/source/resources/MoreOnUpdates.rst new file mode 100644 index 00000000..44295f35 --- /dev/null +++ b/docs/source/resources/MoreOnUpdates.rst @@ -0,0 +1,148 @@ +More on Updates +=============== + +Here we'll show some advanced usages when working with updates. + +.. note:: + This page makes use of Handlers and Filters to show you how to handle updates. + Learn more at `Update Handling `_ and `Using Filters `_. + +Handler Groups +-------------- + +If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored. + +In order to process the same update more than once, you can register your handler in a different group. +Groups are identified by a number (number 0 being the default) and are sorted, that is, a lower group number has a +higher priority. + +For example, in: + +.. code-block:: python + + @app.on_message(Filters.text | Filters.sticker) + def text_or_sticker(client, message): + print("Text or Sticker") + + + @app.on_message(Filters.text) + def just_text(client, message): + print("Just Text") + +``just_text`` is never executed because ``text_or_sticker`` already handles texts. To enable it, simply register the +function using a different group: + +.. code-block:: python + + @app.on_message(Filters.text, group=1) + def just_text(client, message): + print("Just Text") + +Or, if you want ``just_text`` to be fired *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``): + +.. code-block:: python + + @app.on_message(Filters.text, group=-1) + def just_text(client, message): + print("Just Text") + +With :meth:`add_handler() ` (without decorators) the same can be achieved with: + +.. code-block:: python + + app.add_handler(MessageHandler(just_text, Filters.text), -1) + +Update propagation +------------------ + +Registering multiple handlers, each in a different group, becomes useful when you want to handle the same update more +than once. Any incoming update will be sequentially processed by all of your registered functions by respecting the +groups priority policy described above. Even in case any handler raises an unhandled exception, Pyrogram will still +continue to propagate the same update to the next groups until all the handlers are done. Example: + +.. code-block:: python + + @app.on_message(Filters.private) + def _(client, message): + print(0) + + + @app.on_message(Filters.private, group=1) + def _(client, message): + print(1 / 0) # Unhandled exception: ZeroDivisionError + + + @app.on_message(Filters.private, group=2) + def _(client, message): + print(2) + +All these handlers will handle the same kind of messages, that are, messages sent or received in private chats. +The output for each incoming update will therefore be: + +.. code-block:: text + + 0 + ZeroDivisionError: division by zero + 2 + +Stop Propagation +^^^^^^^^^^^^^^^^ + +In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following: + +- Call the update's bound-method ``.stop_propagation()`` (preferred way). +- Manually ``raise StopPropagation`` error (more suitable for raw updates only). + +.. note:: + + Note that ``.stop_propagation()`` is just an elegant and intuitive way to raise a ``StopPropagation`` error; + this means that any code coming *after* calling it won't be executed as your function just raised a custom exception + to signal the dispatcher not to propagate the update anymore. + +Example with ``stop_propagation()``: + +.. code-block:: python + + @app.on_message(Filters.private) + def _(client, message): + print(0) + + + @app.on_message(Filters.private, group=1) + def _(client, message): + print(1) + message.stop_propagation() + + + @app.on_message(Filters.private, group=2) + def _(client, message): + print(2) + +Example with ``raise StopPropagation``: + +.. code-block:: python + + from pyrogram import StopPropagation + + @app.on_message(Filters.private) + def _(client, message): + print(0) + + + @app.on_message(Filters.private, group=1) + def _(client, message): + print(1) + raise StopPropagation + + + @app.on_message(Filters.private, group=2) + def _(client, message): + print(2) + +The handler in group number 2 will never be executed because the propagation was stopped before. The output of both +examples will be: + +.. code-block:: text + + 0 + 1 diff --git a/docs/source/resources/UsingFilters.rst b/docs/source/resources/UsingFilters.rst index d70005a5..3fe87b8a 100644 --- a/docs/source/resources/UsingFilters.rst +++ b/docs/source/resources/UsingFilters.rst @@ -5,7 +5,8 @@ For a finer grained control over what kind of messages will be allowed or not in :class:`Filters `. .. note:: - This section makes use of Handlers to handle updates. Learn more at `Update Handling `_. + This page makes use of Handlers to show you how to handle updates. + Learn more at `Update Handling `_. - This example will show you how to **only** handle messages containing an :obj:`Audio ` object and ignore any other message: @@ -99,45 +100,6 @@ More handlers using different filters can also live together. def from_pyrogramchat(client, message): print("New message in @PyrogramChat") -Handler Groups --------------- - -If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored. - -In order to process the same message more than once, you can register your handler in a different group. -Groups are identified by a number (number 0 being the default) and are sorted. This means that a lower group number has -a higher priority. - -For example, in: - -.. code-block:: python - - @app.on_message(Filters.text | Filters.sticker) - def text_or_sticker(client, message): - print("Text or Sticker") - - - @app.on_message(Filters.text) - def just_text(client, message): - print("Just Text") - -``just_text`` is never executed because ``text_or_sticker`` already handles texts. To enable it, simply register the -function using a different group: - -.. code-block:: python - - @app.on_message(Filters.text, group=1) - def just_text(client, message): - print("Just Text") - -or, if you want ``just_text`` to be fired *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``): - -.. code-block:: python - - @app.on_message(Filters.text, group=-1) - def just_text(client, message): - print("Just Text") - Custom Filters -------------- diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 97dd4688..bb67afce 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -32,7 +32,7 @@ from .client.types import ( Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, - Poll, PollOption, ChatPreview + Poll, PollOption, ChatPreview, StopPropagation ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index d167cf13..47999bc6 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -134,24 +134,29 @@ class Dispatcher: parsed_update, handler_type = parser(update, users, chats) for group in self.groups.values(): - for handler in group: - args = None + try: + for handler in group: + args = None - if isinstance(handler, RawUpdateHandler): - args = (update, users, chats) - elif isinstance(handler, handler_type): - if handler.check(parsed_update): - args = (parsed_update,) + if isinstance(handler, RawUpdateHandler): + args = (update, users, chats) + elif isinstance(handler, handler_type): + if handler.check(parsed_update): + args = (parsed_update,) - if args is None: - continue + if args is None: + continue + + try: + handler.callback(self.client, *args) + except StopIteration: + raise + except Exception as e: + log.error(e, exc_info=True) - try: - handler.callback(self.client, *args) - except Exception as e: - log.error(e, exc_info=True) - finally: break + except StopIteration: + break except Exception as e: log.error(e, exc_info=True) diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index e4ed11ba..0f4628ce 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -37,3 +37,4 @@ from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, Dialog, Dialogs, User, UserStatus, ChatPreview ) +from .update import StopPropagation diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index 456e705f..62f651d0 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -22,10 +22,11 @@ from struct import pack import pyrogram from pyrogram.api import types from ..pyrogram_type import PyrogramType +from ..update import Update from ..user_and_chats import User -class CallbackQuery(PyrogramType): +class CallbackQuery(PyrogramType, Update): """This object represents an incoming callback query from a callback button in an inline keyboard. If the button that originated the query was attached to a message sent by the bot, the field message will be present. If the button was attached to a message sent via the bot (in inline mode), diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 95eb4f48..830a24de 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -26,11 +26,12 @@ from .location import Location from .message_entity import MessageEntity from ..messages_and_media.photo import Photo from ..pyrogram_type import PyrogramType +from ..update import Update from ..user_and_chats.chat import Chat from ..user_and_chats.user import User -class Message(PyrogramType): +class Message(PyrogramType, Update): """This object represents a message. Args: diff --git a/pyrogram/client/types/messages_and_media/messages.py b/pyrogram/client/types/messages_and_media/messages.py index 12fdb2e3..67dc2367 100644 --- a/pyrogram/client/types/messages_and_media/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -22,10 +22,11 @@ import pyrogram from pyrogram.api import types from .message import Message from ..pyrogram_type import PyrogramType +from ..update import Update from ..user_and_chats import Chat -class Messages(PyrogramType): +class Messages(PyrogramType, Update): """This object represents a chat's messages. Args: diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py new file mode 100644 index 00000000..80c233c0 --- /dev/null +++ b/pyrogram/client/types/update.py @@ -0,0 +1,26 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + + +class StopPropagation(StopIteration): + pass + + +class Update: + def stop_propagation(self): + raise StopPropagation diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py index 531b37f1..8c936f8e 100644 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ b/pyrogram/client/types/user_and_chats/user_status.py @@ -20,9 +20,10 @@ import pyrogram from pyrogram.api import types from ..pyrogram_type import PyrogramType +from ..update import Update -class UserStatus(PyrogramType): +class UserStatus(PyrogramType, Update): """This object represents a User status (Last Seen privacy). .. note::