Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions telegram/ext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""Extensions over the Telegram Bot API to facilitate bot making"""

from .dispatcher import Dispatcher
from .dispatcher import Dispatcher, DispatcherHandlerContinue, DispatcherHandlerStop, run_async
from .jobqueue import JobQueue, Job
from .updater import Updater
from .callbackqueryhandler import CallbackQueryHandler
Expand All @@ -42,4 +42,5 @@
'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler',
'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler',
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue')
'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue',
'DispatcherHandlerContinue', 'DispatcherHandlerStop', 'run_async')
62 changes: 50 additions & 12 deletions telegram/ext/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,28 @@ def async_func(*args, **kwargs):
return async_func


class DispatcherHandlerFlow(Exception):
"""
Dispatcher update processing manipulation exceptions are base on this class.
"""
pass


class DispatcherHandlerContinue(DispatcherHandlerFlow):
"""
If check Handler's check_updated returned true, but execution of handler raised this,
then handlers checking will continue.
"""
pass


class DispatcherHandlerStop(DispatcherHandlerFlow):
"""
Raise this in handler to prevent execution any other handlers (even in different group).
"""
pass


class Dispatcher(object):
"""
This class dispatches all kinds of updates to its registered handlers.
Expand Down Expand Up @@ -162,6 +184,9 @@ def _pooled(self):
break

promise.run()
if isinstance(promise.exception, DispatcherHandlerFlow):
self.logger.warning('DispatcherHandlerFlow is not supported with async '
'functions; func: %s', promise.pooled_function.__name__)

def run_async(self, func, *args, **kwargs):
"""
Expand Down Expand Up @@ -255,24 +280,29 @@ def process_update(self, update):
Processes a single update.

Args:
update (:obj:`str` | :class:`telegram.Update`): The update to process.
update (:obj:`str` | :class:`telegram.Update` | :class:`telegram.TelegramError`):
The update to process.
"""

# An error happened while polling
if isinstance(update, TelegramError):
self.dispatch_error(None, update)

else:
for group in self.groups:
return
for group in self.groups:
try:
for handler in self.handlers[group]:
try:
if handler.check_update(update):
handler.handle_update(update, self)
try:
handler.handle_update(update, self)
except DispatcherHandlerContinue:
continue
break
# Dispatch any errors
except DispatcherHandlerFlow:
raise
except TelegramError as te:
self.logger.warn('A TelegramError was raised while processing the '
'Update.')
self.logger.warning('A TelegramError was raised while processing the '
'Update.')

try:
self.dispatch_error(update, te)
Expand All @@ -287,6 +317,8 @@ def process_update(self, update):
self.logger.exception('An uncaught error was raised while '
'processing the update')
break
except DispatcherHandlerStop:
break

def add_handler(self, handler, group=DEFAULT_GROUP):
"""
Expand All @@ -297,14 +329,20 @@ def add_handler(self, handler, group=DEFAULT_GROUP):

A handler must be an instance of a subclass of :class:`telegram.ext.Handler`. All handlers
are organized in groups with a numeric value. The default group is 0. All groups will be
evaluated for handling an update, but only 0 or 1 handler per group will be used.
evaluated for handling an update, but only 0 or 1 handler per group will be used,
except situations when :class:`telegram.DispatcherHandlerContinue` or
:class:`telegram.DispatcherHandlerStop` were raised.

The priority/order of handlers is determined as follows:

* Priority of the group (lower group number == higher priority)
* The first handler in a group which should handle an update will be
used. Other handlers from the group will not be used. The order in
which handlers were added to the group defines the priority.
* If :class:`telegram.DispatcherHandlerContinue` was raised, then next handler in the
same group will be called.
* If :class:`telegram.DispatcherHandlerStop` was raised, then zero handlers (even
from other groups) will called.

Args:
handler (:class:`telegram.ext.Handler`): A Handler instance.
Expand Down Expand Up @@ -343,7 +381,7 @@ def add_error_handler(self, callback):
Registers an error handler in the Dispatcher.

Args:
handler (:obj:`callable`): A function that takes ``Bot, Update, TelegramError`` as
callback (:obj:`callable`): A function that takes ``Bot, Update, TelegramError`` as
arguments.
"""

Expand All @@ -354,7 +392,7 @@ def remove_error_handler(self, callback):
Removes an error handler.

Args:
handler (:obj:`callable`): The error handler to remove.
callback (:obj:`callable`): The error handler to remove.
"""

if callback in self.error_handlers:
Expand All @@ -365,7 +403,7 @@ def dispatch_error(self, update, error):
Dispatches an error.

Args:
update (:obj:`str` | :class:`telegram.Update`): The update that caused the error
update (:obj:`str` | :class:`telegram.Update` | None): The update that caused the error
error (:class:`telegram.TelegramError`): The Telegram error that was raised.
"""

Expand Down
4 changes: 4 additions & 0 deletions telegram/utils/promise.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,7 @@ def result(self, timeout=None):
if self._exception is not None:
raise self._exception # pylint: disable=raising-bad-type
return self._result

@property
def exception(self):
return self._exception
77 changes: 75 additions & 2 deletions tests/test_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@

from future.builtins import bytes

from telegram.utils.request import Request as Requester

try:
# python2
from urllib2 import urlopen, Request, HTTPError
Expand Down Expand Up @@ -924,6 +922,81 @@ def test_mutualExclusiveTokenBot(self):
def test_noTokenOrBot(self):
self.assertRaises(ValueError, Updater)

def test_dispatcher_handler_flow_continue(self):
passed = []

def start1(b, u):
passed.append('start1')
raise DispatcherHandlerContinue

def start2(b, u):
passed.append('start2')

def start3(b, u):
passed.append('start3')

def error(b, u, e):
passed.append('error')
passed.append(e)

# noinspection PyTypeChecker
update = Update(1, message=Message(1, None, None, None, text='/start', bot=self._bot))

# Without raising Continue everything should work as before
passed = []
dp = Dispatcher(self._bot, Queue())
dp.add_handler(CommandHandler('start', start3))
dp.add_handler(CommandHandler('start', start2))
dp.add_error_handler(error)
dp.process_update(update)
self.assertEqual(passed, ['start3'])

# If Continue raised next handler should be proceed.
passed = []
dp = Dispatcher(self._bot, Queue())
dp.add_handler(CommandHandler('start', start1))
dp.add_handler(CommandHandler('start', start2))
dp.process_update(update)
self.assertEqual(passed, ['start1', 'start2'])

def test_dispatcher_handler_flow_stop(self):
passed = []

def start1(b, u):
passed.append('start1')
raise DispatcherHandlerStop

def start2(b, u):
passed.append('start2')

def start3(b, u):
passed.append('start3')

def error(b, u, e):
passed.append('error')
passed.append(e)

# noinspection PyTypeChecker
update = Update(1, message=Message(1, None, None, None, text='/start', bot=self._bot))

# Without raising Stop everything should work as before
passed = []
dp = Dispatcher(self._bot, Queue())
dp.add_handler(CommandHandler('start', start3), 1)
dp.add_handler(CommandHandler('start', start2), 2)
dp.add_error_handler(error)
dp.process_update(update)
self.assertEqual(passed, ['start3', 'start2'])

# If Stop raised handlers in other groups should not be called.
passed = []
dp = Dispatcher(self._bot, Queue())
dp.add_handler(CommandHandler('start', start1), 1)
dp.add_handler(CommandHandler('start', start3), 1)
dp.add_handler(CommandHandler('start', start2), 2)
dp.process_update(update)
self.assertEqual(passed, ['start1'])


class MockBot(object):

Expand Down