Skip to content

Commit 1960b00

Browse files
committed
Add a way to stop iterating through handlers
Closes #125
1 parent f440b1f commit 1960b00

File tree

11 files changed

+209
-62
lines changed

11 files changed

+209
-62
lines changed

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ To get started, press the Next button.
8484

8585
resources/UpdateHandling
8686
resources/UsingFilters
87+
resources/MoreOnUpdates
8788
resources/SmartPlugins
8889
resources/AutoAuthorization
8990
resources/CustomizeSessions
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
More on Updates
2+
===============
3+
4+
Here we'll show some advanced usages when working with updates.
5+
6+
.. note::
7+
This page makes use of Handlers and Filters to show you how to handle updates.
8+
Learn more at `Update Handling <UpdateHandling.html>`_ and `Using Filters <UsingFilters.html>`_.
9+
10+
Handler Groups
11+
--------------
12+
13+
If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored.
14+
15+
In order to process the same update more than once, you can register your handler in a different group.
16+
Groups are identified by a number (number 0 being the default) and are sorted, that is, a lower group number has a
17+
higher priority.
18+
19+
For example, in:
20+
21+
.. code-block:: python
22+
23+
@app.on_message(Filters.text | Filters.sticker)
24+
def text_or_sticker(client, message):
25+
print("Text or Sticker")
26+
27+
28+
@app.on_message(Filters.text)
29+
def just_text(client, message):
30+
print("Just Text")
31+
32+
``just_text`` is never executed because ``text_or_sticker`` already handles texts. To enable it, simply register the
33+
function using a different group:
34+
35+
.. code-block:: python
36+
37+
@app.on_message(Filters.text, group=1)
38+
def just_text(client, message):
39+
print("Just Text")
40+
41+
Or, if you want ``just_text`` to be fired *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``):
42+
43+
.. code-block:: python
44+
45+
@app.on_message(Filters.text, group=-1)
46+
def just_text(client, message):
47+
print("Just Text")
48+
49+
With :meth:`add_handler() <pyrogram.Client.add_handler>` (without decorators) the same can be achieved with:
50+
51+
.. code-block:: python
52+
53+
app.add_handler(MessageHandler(just_text, Filters.text), -1)
54+
55+
Update propagation
56+
------------------
57+
58+
Registering multiple handlers, each in a different group, becomes useful when you want to handle the same update more
59+
than once. Any incoming update will be sequentially processed by all of your registered functions by respecting the
60+
groups priority policy described above. Even in case any handler raises an unhandled exception, Pyrogram will still
61+
continue to propagate the same update to the next groups until all the handlers are done. Example:
62+
63+
.. code-block:: python
64+
65+
@app.on_message(Filters.private)
66+
def _(client, message):
67+
print(0)
68+
69+
70+
@app.on_message(Filters.private, group=1)
71+
def _(client, message):
72+
print(1 / 0) # Unhandled exception: ZeroDivisionError
73+
74+
75+
@app.on_message(Filters.private, group=2)
76+
def _(client, message):
77+
print(2)
78+
79+
All these handlers will handle the same kind of messages, that are, messages sent or received in private chats.
80+
The output for each incoming update will therefore be:
81+
82+
.. code-block:: text
83+
84+
0
85+
ZeroDivisionError: division by zero
86+
2
87+
88+
Stop Propagation
89+
^^^^^^^^^^^^^^^^
90+
91+
In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following:
92+
93+
- Call the update's bound-method ``.stop_propagation()`` (preferred way).
94+
- Manually ``raise StopPropagation`` error (more suitable for raw updates only).
95+
96+
.. note::
97+
98+
Note that ``.stop_propagation()`` is just an elegant and intuitive way to raise a ``StopPropagation`` error;
99+
this means that any code coming *after* calling it won't be executed as your function just raised a custom exception
100+
to signal the dispatcher not to propagate the update anymore.
101+
102+
Example with ``stop_propagation()``:
103+
104+
.. code-block:: python
105+
106+
@app.on_message(Filters.private)
107+
def _(client, message):
108+
print(0)
109+
110+
111+
@app.on_message(Filters.private, group=1)
112+
def _(client, message):
113+
print(1)
114+
message.stop_propagation()
115+
116+
117+
@app.on_message(Filters.private, group=2)
118+
def _(client, message):
119+
print(2)
120+
121+
Example with ``raise StopPropagation``:
122+
123+
.. code-block:: python
124+
125+
from pyrogram import StopPropagation
126+
127+
@app.on_message(Filters.private)
128+
def _(client, message):
129+
print(0)
130+
131+
132+
@app.on_message(Filters.private, group=1)
133+
def _(client, message):
134+
print(1)
135+
raise StopPropagation
136+
137+
138+
@app.on_message(Filters.private, group=2)
139+
def _(client, message):
140+
print(2)
141+
142+
The handler in group number 2 will never be executed because the propagation was stopped before. The output of both
143+
examples will be:
144+
145+
.. code-block:: text
146+
147+
0
148+
1

docs/source/resources/UsingFilters.rst

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ For a finer grained control over what kind of messages will be allowed or not in
55
:class:`Filters <pyrogram.Filters>`.
66

77
.. note::
8-
This section makes use of Handlers to handle updates. Learn more at `Update Handling <UpdateHandling.html>`_.
8+
This page makes use of Handlers to show you how to handle updates.
9+
Learn more at `Update Handling <UpdateHandling.html>`_.
910

1011
- This example will show you how to **only** handle messages containing an :obj:`Audio <pyrogram.Audio>` object and
1112
ignore any other message:
@@ -99,45 +100,6 @@ More handlers using different filters can also live together.
99100
def from_pyrogramchat(client, message):
100101
print("New message in @PyrogramChat")
101102
102-
Handler Groups
103-
--------------
104-
105-
If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored.
106-
107-
In order to process the same message more than once, you can register your handler in a different group.
108-
Groups are identified by a number (number 0 being the default) and are sorted. This means that a lower group number has
109-
a higher priority.
110-
111-
For example, in:
112-
113-
.. code-block:: python
114-
115-
@app.on_message(Filters.text | Filters.sticker)
116-
def text_or_sticker(client, message):
117-
print("Text or Sticker")
118-
119-
120-
@app.on_message(Filters.text)
121-
def just_text(client, message):
122-
print("Just Text")
123-
124-
``just_text`` is never executed because ``text_or_sticker`` already handles texts. To enable it, simply register the
125-
function using a different group:
126-
127-
.. code-block:: python
128-
129-
@app.on_message(Filters.text, group=1)
130-
def just_text(client, message):
131-
print("Just Text")
132-
133-
or, if you want ``just_text`` to be fired *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``):
134-
135-
.. code-block:: python
136-
137-
@app.on_message(Filters.text, group=-1)
138-
def just_text(client, message):
139-
print("Just Text")
140-
141103
Custom Filters
142104
--------------
143105

pyrogram/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus,
3333
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
3434
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove,
35-
Poll, PollOption, ChatPreview
35+
Poll, PollOption, ChatPreview, StopPropagation
3636
)
3737
from .client import (
3838
Client, ChatAction, ParseMode, Emoji,

pyrogram/client/dispatcher/dispatcher.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -134,24 +134,29 @@ def update_worker(self):
134134
parsed_update, handler_type = parser(update, users, chats)
135135

136136
for group in self.groups.values():
137-
for handler in group:
138-
args = None
139-
140-
if isinstance(handler, RawUpdateHandler):
141-
args = (update, users, chats)
142-
elif isinstance(handler, handler_type):
143-
if handler.check(parsed_update):
144-
args = (parsed_update,)
145-
146-
if args is None:
147-
continue
148-
149-
try:
150-
handler.callback(self.client, *args)
151-
except Exception as e:
152-
log.error(e, exc_info=True)
153-
finally:
137+
try:
138+
for handler in group:
139+
args = None
140+
141+
if isinstance(handler, RawUpdateHandler):
142+
args = (update, users, chats)
143+
elif isinstance(handler, handler_type):
144+
if handler.check(parsed_update):
145+
args = (parsed_update,)
146+
147+
if args is None:
148+
continue
149+
150+
try:
151+
handler.callback(self.client, *args)
152+
except StopIteration:
153+
raise
154+
except Exception as e:
155+
log.error(e, exc_info=True)
156+
154157
break
158+
except StopIteration:
159+
break
155160
except Exception as e:
156161
log.error(e, exc_info=True)
157162

pyrogram/client/types/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@
3737
Chat, ChatMember, ChatMembers, ChatPhoto,
3838
Dialog, Dialogs, User, UserStatus, ChatPreview
3939
)
40+
from .update import StopPropagation

pyrogram/client/types/bots/callback_query.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
import pyrogram
2323
from pyrogram.api import types
2424
from ..pyrogram_type import PyrogramType
25+
from ..update import Update
2526
from ..user_and_chats import User
2627

2728

28-
class CallbackQuery(PyrogramType):
29+
class CallbackQuery(PyrogramType, Update):
2930
"""This object represents an incoming callback query from a callback button in an inline keyboard.
3031
If the button that originated the query was attached to a message sent by the bot, the field message
3132
will be present. If the button was attached to a message sent via the bot (in inline mode),

pyrogram/client/types/messages_and_media/message.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@
2626
from .message_entity import MessageEntity
2727
from ..messages_and_media.photo import Photo
2828
from ..pyrogram_type import PyrogramType
29+
from ..update import Update
2930
from ..user_and_chats.chat import Chat
3031
from ..user_and_chats.user import User
3132

3233

33-
class Message(PyrogramType):
34+
class Message(PyrogramType, Update):
3435
"""This object represents a message.
3536
3637
Args:

pyrogram/client/types/messages_and_media/messages.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
from pyrogram.api import types
2323
from .message import Message
2424
from ..pyrogram_type import PyrogramType
25+
from ..update import Update
2526
from ..user_and_chats import Chat
2627

2728

28-
class Messages(PyrogramType):
29+
class Messages(PyrogramType, Update):
2930
"""This object represents a chat's messages.
3031
3132
Args:

pyrogram/client/types/update.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Pyrogram - Telegram MTProto API Client Library for Python
2+
# Copyright (C) 2017-2019 Dan Tès <https://github.com/delivrance>
3+
#
4+
# This file is part of Pyrogram.
5+
#
6+
# Pyrogram is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Lesser General Public License as published
8+
# by the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# Pyrogram is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public License
17+
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
18+
19+
20+
class StopPropagation(StopIteration):
21+
pass
22+
23+
24+
class Update:
25+
def stop_propagation(self):
26+
raise StopPropagation

0 commit comments

Comments
 (0)