Add Dispatcher.add_handlers (#2823)

Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
This commit is contained in:
Harshil 2022-01-03 12:04:11 +04:00 committed by Hinrich Mahler
parent 9a8c76fc2b
commit 5891db2f6b
3 changed files with 110 additions and 15 deletions

View file

@ -36,10 +36,12 @@ from typing import (
Generic, Generic,
TypeVar, TypeVar,
TYPE_CHECKING, TYPE_CHECKING,
Tuple,
) )
from uuid import uuid4 from uuid import uuid4
from telegram import Update from telegram import Update
from telegram._utils.types import DVInput
from telegram.error import TelegramError from telegram.error import TelegramError
from telegram.ext import BasePersistence, ContextTypes, ExtBot from telegram.ext import BasePersistence, ContextTypes, ExtBot
from telegram.ext._handler import Handler from telegram.ext._handler import Handler
@ -98,7 +100,9 @@ class Dispatcher(Generic[BT, CCT, UD, CD, BD, JQ, PT]):
:meth:`builder` (for convenience). :meth:`builder` (for convenience).
.. versionchanged:: 14.0 .. versionchanged:: 14.0
Initialization is now done through the :class:`telegram.ext.DispatcherBuilder`.
* Initialization is now done through the :class:`telegram.ext.DispatcherBuilder`.
* Removed the attribute ``groups``.
Attributes: Attributes:
bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers. bot (:class:`telegram.Bot`): The bot object that should be passed to the handlers.
@ -120,11 +124,7 @@ class Dispatcher(Generic[BT, CCT, UD, CD, BD, JQ, PT]):
handler group to the list of handlers registered to that group. handler group to the list of handlers registered to that group.
.. seealso:: .. seealso::
:meth:`add_handler` :meth:`add_handler`, :meth:`add_handlers`.
groups (List[:obj:`int`]): A list of all handler groups that have handlers registered.
.. seealso::
:meth:`add_handler`
error_handlers (Dict[:obj:`callable`, :obj:`bool`]): A dict, where the keys are error error_handlers (Dict[:obj:`callable`, :obj:`bool`]): A dict, where the keys are error
handlers and the values indicate whether they are to be run asynchronously via handlers and the values indicate whether they are to be run asynchronously via
:meth:`run_async`. :meth:`run_async`.
@ -149,7 +149,6 @@ class Dispatcher(Generic[BT, CCT, UD, CD, BD, JQ, PT]):
'bot_data', 'bot_data',
'_update_persistence_lock', '_update_persistence_lock',
'handlers', 'handlers',
'groups',
'error_handlers', 'error_handlers',
'running', 'running',
'__stop_event', '__stop_event',
@ -243,7 +242,6 @@ class Dispatcher(Generic[BT, CCT, UD, CD, BD, JQ, PT]):
self.persistence = None self.persistence = None
self.handlers: Dict[int, List[Handler]] = {} self.handlers: Dict[int, List[Handler]] = {}
self.groups: List[int] = []
self.error_handlers: Dict[Callable, Union[bool, DefaultValue]] = {} self.error_handlers: Dict[Callable, Union[bool, DefaultValue]] = {}
self.running = False self.running = False
@ -482,9 +480,9 @@ class Dispatcher(Generic[BT, CCT, UD, CD, BD, JQ, PT]):
handled = False handled = False
sync_modes = [] sync_modes = []
for group in self.groups: for handlers in self.handlers.values():
try: try:
for handler in self.handlers[group]: for handler in handlers:
check = handler.check_update(update) check = handler.check_update(update)
if check is not None and check is not False: if check is not None and check is not False:
if not context: if not context:
@ -573,11 +571,53 @@ class Dispatcher(Generic[BT, CCT, UD, CD, BD, JQ, PT]):
if group not in self.handlers: if group not in self.handlers:
self.handlers[group] = [] self.handlers[group] = []
self.groups.append(group) self.handlers = dict(sorted(self.handlers.items())) # lower -> higher groups
self.groups = sorted(self.groups)
self.handlers[group].append(handler) self.handlers[group].append(handler)
def add_handlers(
self,
handlers: Union[
Union[List[Handler], Tuple[Handler]], Dict[int, Union[List[Handler], Tuple[Handler]]]
],
group: DVInput[int] = DefaultValue(0),
) -> None:
"""Registers multiple handlers at once. The order of the handlers in the passed
sequence(s) matters. See :meth:`add_handler` for details.
.. versionadded:: 14.0
.. seealso:: :meth:`add_handler`
Args:
handlers (List[:obj:`telegram.ext.Handler`] | \
Dict[int, List[:obj:`telegram.ext.Handler`]]): \
Specify a sequence of handlers *or* a dictionary where the keys are groups and
values are handlers.
group (:obj:`int`, optional): Specify which group the sequence of ``handlers``
should be added to. Defaults to ``0``.
"""
if isinstance(handlers, dict) and not isinstance(group, DefaultValue):
raise ValueError('The `group` argument can only be used with a sequence of handlers.')
if isinstance(handlers, dict):
for handler_group, grp_handlers in handlers.items():
if not isinstance(grp_handlers, (list, tuple)):
raise ValueError(f'Handlers for group {handler_group} must be a list or tuple')
for handler in grp_handlers:
self.add_handler(handler, handler_group)
elif isinstance(handlers, (list, tuple)):
for handler in handlers:
self.add_handler(handler, DefaultValue.get_value(group))
else:
raise ValueError(
"The `handlers` argument must be a sequence of handlers or a "
"dictionary where the keys are groups and values are sequences of handlers."
)
def remove_handler(self, handler: Handler, group: int = DEFAULT_GROUP) -> None: def remove_handler(self, handler: Handler, group: int = DEFAULT_GROUP) -> None:
"""Remove a handler from the specified group. """Remove a handler from the specified group.
@ -590,7 +630,6 @@ class Dispatcher(Generic[BT, CCT, UD, CD, BD, JQ, PT]):
self.handlers[group].remove(handler) self.handlers[group].remove(handler)
if not self.handlers[group]: if not self.handlers[group]:
del self.handlers[group] del self.handlers[group]
self.groups.remove(group)
def update_persistence(self, update: object = None) -> None: def update_persistence(self, update: object = None) -> None:
"""Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`. """Update :attr:`user_data`, :attr:`chat_data` and :attr:`bot_data` in :attr:`persistence`.

View file

@ -199,7 +199,6 @@ def dp(_dp):
_dp.bot_data = {} _dp.bot_data = {}
_dp.persistence = None _dp.persistence = None
_dp.handlers = {} _dp.handlers = {}
_dp.groups = []
_dp.error_handlers = {} _dp.error_handlers = {}
_dp.exception_event = Event() _dp.exception_event = Event()
_dp.__stop_event = Event() _dp.__stop_event = Event()

View file

@ -80,7 +80,7 @@ class TestDispatcher:
self.count += 1 self.count += 1
def callback_set_count(self, count): def callback_set_count(self, count):
def callback(bot, update): def callback(update, context):
self.count = count self.count = count
return callback return callback
@ -413,6 +413,63 @@ class TestDispatcher:
sleep(0.1) sleep(0.1)
assert self.count == 3 assert self.count == 3
def test_add_handlers_complex(self, dp):
"""Tests both add_handler & add_handlers together & confirms the correct insertion order"""
msg_handler_set_count = MessageHandler(filters.TEXT, self.callback_set_count(1))
msg_handler_inc_count = MessageHandler(filters.PHOTO, self.callback_increase_count)
dp.add_handler(msg_handler_set_count, 1)
dp.add_handlers((msg_handler_inc_count, msg_handler_inc_count), 1)
photo_update = Update(2, message=Message(2, None, None, photo=True))
dp.update_queue.put(self.message_update) # Putting updates in the queue calls the callback
dp.update_queue.put(photo_update)
sleep(0.1) # sleep is required otherwise there is random behaviour
# Test if handler was added to correct group with correct order-
assert (
self.count == 2
and len(dp.handlers[1]) == 3
and dp.handlers[1][0] is msg_handler_set_count
)
# Now lets test add_handlers when `handlers` is a dict-
voice_filter_handler_to_check = MessageHandler(filters.VOICE, self.callback_increase_count)
dp.add_handlers(
handlers={
1: [
MessageHandler(filters.USER, self.callback_increase_count),
voice_filter_handler_to_check,
],
-1: [MessageHandler(filters.CAPTION, self.callback_set_count(2))],
}
)
user_update = Update(3, message=Message(3, None, None, from_user=User(1, 's', True)))
voice_update = Update(4, message=Message(4, None, None, voice=True))
dp.update_queue.put(user_update)
dp.update_queue.put(voice_update)
sleep(0.1)
assert (
self.count == 4
and len(dp.handlers[1]) == 5
and dp.handlers[1][-1] is voice_filter_handler_to_check
)
dp.update_queue.put(Update(5, message=Message(5, None, None, caption='cap')))
sleep(0.1)
assert self.count == 2 and len(dp.handlers[-1]) == 1
# Now lets test the errors which can be produced-
with pytest.raises(ValueError, match="The `group` argument"):
dp.add_handlers({2: [msg_handler_set_count]}, group=0)
with pytest.raises(ValueError, match="Handlers for group 3"):
dp.add_handlers({3: msg_handler_set_count})
with pytest.raises(ValueError, match="The `handlers` argument must be a sequence"):
dp.add_handlers({msg_handler_set_count})
def test_add_handler_errors(self, dp): def test_add_handler_errors(self, dp):
handler = 'not a handler' handler = 'not a handler'
with pytest.raises(TypeError, match='handler is not an instance of'): with pytest.raises(TypeError, match='handler is not an instance of'):