2015-11-05 13:52:33 +01:00
|
|
|
#!/usr/bin/env python
|
2015-12-21 21:18:53 +01:00
|
|
|
#
|
|
|
|
# A library that provides a Python interface to the Telegram Bot API
|
2016-01-05 14:12:03 +01:00
|
|
|
# Copyright (C) 2015-2016
|
|
|
|
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
2015-12-21 21:18:53 +01:00
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Lesser Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program 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 Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Lesser Public License
|
|
|
|
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
2016-01-13 17:09:35 +01:00
|
|
|
"""This module contains the Dispatcher class."""
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2015-11-15 17:36:38 +01:00
|
|
|
import logging
|
2015-11-11 13:33:03 +01:00
|
|
|
from functools import wraps
|
2016-02-09 22:08:27 +01:00
|
|
|
from threading import Thread, BoundedSemaphore, Lock, Event, current_thread
|
2016-01-06 15:35:55 +01:00
|
|
|
from time import sleep
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-04-25 09:01:21 +02:00
|
|
|
from queue import Empty
|
2016-04-16 19:25:38 +02:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
from telegram import (TelegramError, NullHandler)
|
|
|
|
from telegram.ext.handler import Handler
|
2016-04-28 14:29:27 +02:00
|
|
|
from telegram.utils.deprecate import deprecate
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2016-03-15 02:56:20 +01:00
|
|
|
logging.getLogger(__name__).addHandler(NullHandler())
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2015-11-11 13:33:03 +01:00
|
|
|
semaphore = None
|
2016-02-09 22:08:27 +01:00
|
|
|
async_threads = set()
|
|
|
|
""":type: set[Thread]"""
|
2015-11-15 17:36:38 +01:00
|
|
|
async_lock = Lock()
|
2016-04-14 23:57:40 +02:00
|
|
|
DEFAULT_GROUP = 0
|
2015-11-06 00:24:01 +01:00
|
|
|
|
2015-11-11 13:33:03 +01:00
|
|
|
|
|
|
|
def run_async(func):
|
|
|
|
"""
|
2016-04-14 23:57:40 +02:00
|
|
|
Function decorator that will run the function in a new thread.
|
2015-11-11 13:33:03 +01:00
|
|
|
|
|
|
|
Args:
|
|
|
|
func (function): The function to run in the thread.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
function:
|
|
|
|
"""
|
|
|
|
|
2016-02-06 11:38:56 +01:00
|
|
|
# TODO: handle exception in async threads
|
|
|
|
# set a threading.Event to notify caller thread
|
|
|
|
|
2015-11-11 13:33:03 +01:00
|
|
|
@wraps(func)
|
2015-12-21 19:36:17 +01:00
|
|
|
def pooled(*pargs, **kwargs):
|
2015-11-15 17:36:38 +01:00
|
|
|
"""
|
|
|
|
A wrapper to run a thread in a thread pool
|
|
|
|
"""
|
2016-04-24 13:43:42 +02:00
|
|
|
try:
|
|
|
|
result = func(*pargs, **kwargs)
|
|
|
|
finally:
|
|
|
|
semaphore.release()
|
|
|
|
|
|
|
|
with async_lock:
|
|
|
|
async_threads.remove(current_thread())
|
2015-11-11 13:33:03 +01:00
|
|
|
return result
|
|
|
|
|
|
|
|
@wraps(func)
|
2015-12-21 19:36:17 +01:00
|
|
|
def async_func(*pargs, **kwargs):
|
2015-11-15 17:36:38 +01:00
|
|
|
"""
|
|
|
|
A wrapper to run a function in a thread
|
|
|
|
"""
|
2015-12-21 19:36:17 +01:00
|
|
|
thread = Thread(target=pooled, args=pargs, kwargs=kwargs)
|
2015-11-11 13:33:03 +01:00
|
|
|
semaphore.acquire()
|
2015-11-15 17:36:38 +01:00
|
|
|
with async_lock:
|
2016-02-09 22:08:27 +01:00
|
|
|
async_threads.add(thread)
|
2015-11-11 13:33:03 +01:00
|
|
|
thread.start()
|
|
|
|
return thread
|
|
|
|
|
|
|
|
return async_func
|
|
|
|
|
|
|
|
|
2016-04-15 16:20:37 +02:00
|
|
|
class Dispatcher(object):
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2015-11-22 14:47:38 +01:00
|
|
|
This class dispatches all kinds of updates to its registered handlers.
|
2015-12-21 20:25:31 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2015-11-16 20:43:35 +01:00
|
|
|
bot (telegram.Bot): The bot object that should be passed to the
|
2015-12-21 21:40:41 +01:00
|
|
|
handlers
|
2016-04-18 18:13:54 +02:00
|
|
|
update_queue (Queue): The synchronized queue that will contain the
|
|
|
|
updates.
|
2016-05-26 14:39:11 +02:00
|
|
|
job_queue (Optional[telegram.ext.JobQueue]): The ``JobQueue`` instance to pass onto handler
|
|
|
|
callbacks
|
|
|
|
workers (Optional[int]): Number of maximum concurrent worker threads for the ``@run_async``
|
|
|
|
decorator
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2016-05-15 03:46:40 +02:00
|
|
|
|
2016-05-28 14:21:39 +02:00
|
|
|
def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue=None):
|
2015-11-05 13:52:33 +01:00
|
|
|
self.bot = bot
|
|
|
|
self.update_queue = update_queue
|
2016-05-26 14:39:11 +02:00
|
|
|
self.job_queue = job_queue
|
2016-04-14 23:57:40 +02:00
|
|
|
|
|
|
|
self.handlers = {}
|
2016-04-25 09:18:26 +02:00
|
|
|
""":type: dict[int, list[Handler]"""
|
|
|
|
self.groups = []
|
|
|
|
""":type: list[int]"""
|
2015-11-05 13:52:33 +01:00
|
|
|
self.error_handlers = []
|
2016-04-14 23:57:40 +02:00
|
|
|
|
2015-11-15 17:36:38 +01:00
|
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
self.running = False
|
2016-02-06 09:10:08 +01:00
|
|
|
self.__stop_event = Event()
|
2016-02-06 11:38:56 +01:00
|
|
|
self.__exception_event = exception_event or Event()
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2015-11-11 13:33:03 +01:00
|
|
|
global semaphore
|
|
|
|
if not semaphore:
|
|
|
|
semaphore = BoundedSemaphore(value=workers)
|
|
|
|
else:
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Semaphore already initialized, skipping.')
|
2015-11-15 17:36:38 +01:00
|
|
|
|
|
|
|
def start(self):
|
|
|
|
"""
|
2015-11-22 14:47:38 +01:00
|
|
|
Thread target of thread 'dispatcher'. Runs in background and processes
|
2015-11-15 17:36:38 +01:00
|
|
|
the update queue.
|
|
|
|
"""
|
|
|
|
|
2016-02-06 09:10:08 +01:00
|
|
|
if self.running:
|
|
|
|
self.logger.warning('already running')
|
|
|
|
return
|
|
|
|
|
2016-02-06 11:38:56 +01:00
|
|
|
if self.__exception_event.is_set():
|
|
|
|
msg = 'reusing dispatcher after exception event is forbidden'
|
|
|
|
self.logger.error(msg)
|
|
|
|
raise TelegramError(msg)
|
|
|
|
|
2016-02-06 09:10:08 +01:00
|
|
|
self.running = True
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Dispatcher started')
|
2016-02-06 09:10:08 +01:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
while True:
|
2016-02-06 09:10:08 +01:00
|
|
|
try:
|
|
|
|
# Pop update from update queue.
|
2016-04-14 23:57:40 +02:00
|
|
|
update = self.update_queue.get(True, 1)
|
2016-02-06 09:10:08 +01:00
|
|
|
except Empty:
|
|
|
|
if self.__stop_event.is_set():
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('orderly stopping')
|
2016-02-06 11:38:56 +01:00
|
|
|
break
|
2016-04-14 22:23:02 +02:00
|
|
|
elif self.__exception_event.is_set():
|
2016-05-15 03:46:40 +02:00
|
|
|
self.logger.critical('stopping due to exception in another thread')
|
2016-02-06 09:10:08 +01:00
|
|
|
break
|
|
|
|
continue
|
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
self.logger.debug('Processing Update: %s' % update)
|
|
|
|
self.processUpdate(update)
|
2016-02-06 09:10:08 +01:00
|
|
|
|
|
|
|
self.running = False
|
2016-03-15 02:56:20 +01:00
|
|
|
self.logger.debug('Dispatcher thread stopped')
|
2015-11-15 17:36:38 +01:00
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
"""
|
|
|
|
Stops the thread
|
|
|
|
"""
|
2016-02-06 09:10:08 +01:00
|
|
|
if self.running:
|
|
|
|
self.__stop_event.set()
|
|
|
|
while self.running:
|
|
|
|
sleep(0.1)
|
|
|
|
self.__stop_event.clear()
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
def processUpdate(self, update):
|
2015-11-15 17:36:38 +01:00
|
|
|
"""
|
|
|
|
Processes a single update.
|
|
|
|
|
|
|
|
Args:
|
2016-04-18 18:13:54 +02:00
|
|
|
update (object):
|
2015-11-15 17:36:38 +01:00
|
|
|
"""
|
|
|
|
|
|
|
|
# An error happened while polling
|
|
|
|
if isinstance(update, TelegramError):
|
2015-11-22 14:47:38 +01:00
|
|
|
self.dispatchError(None, update)
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
else:
|
2016-04-25 09:18:26 +02:00
|
|
|
for group in self.groups:
|
|
|
|
for handler in self.handlers[group]:
|
2016-04-14 23:57:40 +02:00
|
|
|
try:
|
2016-05-16 15:02:51 +02:00
|
|
|
if handler.check_update(update):
|
|
|
|
handler.handle_update(update, self)
|
2016-04-18 17:13:06 +02:00
|
|
|
break
|
2016-04-14 23:57:40 +02:00
|
|
|
# Dispatch any errors
|
|
|
|
except TelegramError as te:
|
2016-05-15 03:46:40 +02:00
|
|
|
self.logger.warn('A TelegramError was raised while processing the '
|
|
|
|
'Update.')
|
2016-04-14 23:57:40 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
self.dispatchError(update, te)
|
2016-04-25 09:19:39 +02:00
|
|
|
except Exception:
|
2016-05-15 03:46:40 +02:00
|
|
|
self.logger.exception('An uncaught error was raised while '
|
|
|
|
'handling the error')
|
2016-04-18 17:13:06 +02:00
|
|
|
finally:
|
|
|
|
break
|
2016-04-14 23:57:40 +02:00
|
|
|
|
|
|
|
# Errors should not stop the thread
|
2016-04-25 09:19:39 +02:00
|
|
|
except Exception:
|
2016-05-15 03:46:40 +02:00
|
|
|
self.logger.exception('An uncaught error was raised while '
|
|
|
|
'processing the update')
|
2016-04-18 17:13:06 +02:00
|
|
|
break
|
2016-04-14 23:57:40 +02:00
|
|
|
|
2016-04-28 14:29:27 +02:00
|
|
|
def add_handler(self, handler, group=DEFAULT_GROUP):
|
2016-04-14 23:57:40 +02:00
|
|
|
"""
|
2016-04-25 09:45:55 +02:00
|
|
|
Register a handler.
|
|
|
|
|
|
|
|
TL;DR: Order and priority counts. 0 or 1 handlers per group will be
|
|
|
|
used.
|
|
|
|
|
|
|
|
A handler must be an instance of a subclass of
|
|
|
|
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.
|
|
|
|
|
|
|
|
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.
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2016-04-14 23:57:40 +02:00
|
|
|
handler (Handler): A Handler instance
|
2016-04-25 09:45:55 +02:00
|
|
|
group (Optional[int]): The group identifier. Default is 0
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
if not isinstance(handler, Handler):
|
2016-05-15 03:46:40 +02:00
|
|
|
raise TypeError('handler is not an instance of {0}'.format(Handler.__name__))
|
2016-04-25 09:44:55 +02:00
|
|
|
if not isinstance(group, int):
|
|
|
|
raise TypeError('group is not int')
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
if group not in self.handlers:
|
|
|
|
self.handlers[group] = list()
|
2016-04-25 09:18:26 +02:00
|
|
|
self.groups.append(group)
|
|
|
|
self.groups = sorted(self.groups)
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
self.handlers[group].append(handler)
|
2015-11-05 13:52:33 +01:00
|
|
|
|
2016-04-28 14:29:27 +02:00
|
|
|
def remove_handler(self, handler, group=DEFAULT_GROUP):
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2016-04-14 23:57:40 +02:00
|
|
|
Remove a handler from the specified group
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2016-04-14 23:57:40 +02:00
|
|
|
handler (Handler): A Handler instance
|
2016-04-18 18:13:54 +02:00
|
|
|
group (optional[object]): The group identifier. Default is 0
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2016-04-14 23:57:40 +02:00
|
|
|
if handler in self.handlers[group]:
|
|
|
|
self.handlers[group].remove(handler)
|
2016-04-25 09:18:45 +02:00
|
|
|
if not self.handlers[group]:
|
|
|
|
del self.handlers[group]
|
|
|
|
self.groups.remove(group)
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2016-04-28 14:29:27 +02:00
|
|
|
def add_error_handler(self, callback):
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2015-11-22 14:47:38 +01:00
|
|
|
Registers an error handler in the Dispatcher.
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2015-11-05 13:52:33 +01:00
|
|
|
Args:
|
2016-04-18 18:13:54 +02:00
|
|
|
handler (function): A function that takes ``Bot, Update,
|
|
|
|
TelegramError`` as arguments.
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2015-11-15 17:36:38 +01:00
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
self.error_handlers.append(callback)
|
2016-01-04 17:31:06 +01:00
|
|
|
|
2016-04-28 14:29:27 +02:00
|
|
|
def remove_error_handler(self, callback):
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
|
|
|
De-registers an error handler.
|
|
|
|
|
|
|
|
Args:
|
2016-04-18 18:13:54 +02:00
|
|
|
handler (function):
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
if callback in self.error_handlers:
|
|
|
|
self.error_handlers.remove(callback)
|
2016-01-04 17:31:06 +01:00
|
|
|
|
2015-11-22 14:47:38 +01:00
|
|
|
def dispatchError(self, update, error):
|
2015-11-05 13:52:33 +01:00
|
|
|
"""
|
2015-11-22 14:47:38 +01:00
|
|
|
Dispatches an error.
|
2015-11-05 13:52:33 +01:00
|
|
|
|
|
|
|
Args:
|
2016-04-18 18:13:54 +02:00
|
|
|
update (object): The update that caused the error
|
2015-11-05 13:52:33 +01:00
|
|
|
error (telegram.TelegramError): The Telegram error that was raised.
|
|
|
|
"""
|
|
|
|
|
2016-04-14 23:57:40 +02:00
|
|
|
for callback in self.error_handlers:
|
|
|
|
callback(self.bot, update, error)
|
2016-04-28 12:20:42 +02:00
|
|
|
|
2016-04-28 14:29:27 +02:00
|
|
|
# old non-PEP8 Dispatcher methods
|
|
|
|
m = "telegram.dispatcher."
|
|
|
|
addHandler = deprecate(add_handler, m + "AddHandler", m + "add_handler")
|
|
|
|
removeHandler = deprecate(remove_handler, m + "removeHandler", m + "remove_handler")
|
|
|
|
addErrorHandler = deprecate(add_error_handler, m + "addErrorHandler", m + "add_error_handler")
|
2016-05-15 03:46:40 +02:00
|
|
|
removeErrorHandler = deprecate(remove_error_handler, m + "removeErrorHandler",
|
|
|
|
m + "remove_error_handler")
|