From 6b90ac9f1cb132ed33efc5c43814731ecc5a9c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Wed, 25 May 2016 22:51:13 +0200 Subject: [PATCH 01/18] make job queue API similar to the dispatcher, add new functionality --- README.rst | 44 +++++++++-- examples/timerbot.py | 36 ++++++--- telegram/ext/__init__.py | 10 +-- telegram/ext/jobqueue.py | 163 ++++++++++++++++++++++++++++----------- telegram/ext/updater.py | 5 +- tests/test_jobqueue.py | 86 +++++++++++++++++---- 6 files changed, 258 insertions(+), 86 deletions(-) diff --git a/README.rst b/README.rst index b7ce2c8c646..e24af25b7ca 100644 --- a/README.rst +++ b/README.rst @@ -395,23 +395,53 @@ The ``JobQueue`` allows you to perform tasks with a delay or even periodically. >>> u = Updater('TOKEN') >>> j = u.job_queue -The job queue uses functions for tasks, so we define one and add it to the queue. Usually, when the first job is added to the queue, it wil start automatically. We can prevent this by setting ``prevent_autostart=True``: +The job queue uses the ``Job`` class for tasks. We define a callback function, instantiate a ``Job`` and add it to the queue. + +Usually, when the first job is added to the queue, it wil start automatically. We can prevent this by setting ``prevent_autostart=True``: .. code:: python - >>> def job1(bot): + >>> from telegram.ext import Job + >>> def callback_minute(bot, job): ... bot.sendMessage(chat_id='@examplechannel', text='One message every minute') - >>> j.put(job1, 60, next_t=0, prevent_autostart=True) + ... + >>> job_minute = Job(callback_minute, 60.0, next_t=0.0) + >>> j.put(job_minute, prevent_autostart=True) -You can also have a job that will not be executed repeatedly: +You can also have a job that will be executed only once: .. code:: python - >>> def job2(bot): + >>> def callback_30(bot, job): ... bot.sendMessage(chat_id='@examplechannel', text='A single message with 30s delay') - >>> j.put(job2, 30, repeat=False) + ... + >>> j.put(Job(callback_30, 30.0, repeat=False)) + +Now, because we didn't prevent the auto start this time, the queue will start working. It runs in a seperate thread, so it is non-blocking. + +Jobs can be temporarily disabled or completely removed from the ``JobQueue``: + +.. code:: python + + >>> job_minute.enabled = False + >>> job_minute.schedule_removal() + +Please note that ``schedule_removal`` does not immediately removes the job from the queue. Instead, it is marked for removal and will be removed as soon as its current interval is over (it will not run again after being marked for removal). + +A job can also change its own behaviour, as it is passed to the callback function as the second argument: + +.. code:: python + + >>> def callback_increasing(bot, job): + ... bot.sendMessage(chat_id='@examplechannel', + ... text='Sending messages with increasing delay up to 10s, then stops.') + ... job.interval += 1.0 + ... if job.interval > 10.0: + ... job.schedule_removal() + ... + >>> j.put(Job(callback_increasing, 1.0)) -Now, because we didn't prevent the auto start this time, the queue will start ticking. It runs in a seperate thread, so it is non-blocking. When we stop the Updater, the related queue will be stopped as well: +When we stop the Updater, the related queue will be stopped as well: .. code:: python diff --git a/examples/timerbot.py b/examples/timerbot.py index 4c9faccf7d1..151f45156b0 100644 --- a/examples/timerbot.py +++ b/examples/timerbot.py @@ -17,15 +17,16 @@ bot. """ -from telegram.ext import Updater, CommandHandler +from telegram.ext import Updater, CommandHandler, Job import logging # Enable logging logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - level=logging.INFO) + level=logging.DEBUG) logger = logging.getLogger(__name__) job_queue = None +timers = dict() # Define a few command handlers. These usually take the two arguments bot and @@ -35,7 +36,7 @@ def start(bot, update): def set(bot, update, args): - """ Adds a job to the queue """ + """Adds a job to the queue""" chat_id = update.message.chat_id try: # args[0] should contain the time for the timer in seconds @@ -43,20 +44,34 @@ def set(bot, update, args): if due < 0: bot.sendMessage(chat_id, text='Sorry we can not go back to future!') - def alarm(bot): - """ Inner function to send the alarm message """ + def alarm(bot, job): + """Inner function to send the alarm message""" bot.sendMessage(chat_id, text='Beep!') # Add job to queue - job_queue.put(alarm, due, repeat=False) + job = Job(alarm, due, repeat=False) + timers[chat_id] = job + job_queue.put(job) + bot.sendMessage(chat_id, text='Timer successfully set!') - except IndexError: - bot.sendMessage(chat_id, text='Usage: /set ') - except ValueError: + except (IndexError, ValueError): bot.sendMessage(chat_id, text='Usage: /set ') +def unset(bot, update): + """Removes the job if the user changed their mind""" + chat_id = update.message.chat_id + + if chat_id not in timers: + bot.sendMessage(chat_id, text='You have no active timer') + return + + job = timers[chat_id] + job.schedule_removal() + bot.sendMessage(chat_id, text='Timer successfully unset!') + + def error(bot, update, error): logger.warn('Update "%s" caused error "%s"' % (update, error)) @@ -64,7 +79,7 @@ def error(bot, update, error): def main(): global job_queue - updater = Updater("TOKEN") + updater = Updater("148447715:AAH4M0gzPG11_mdQS1Qeb0Ex30I5-rw9bMY") job_queue = updater.job_queue # Get the dispatcher to register handlers @@ -74,6 +89,7 @@ def main(): dp.add_handler(CommandHandler("start", start)) dp.add_handler(CommandHandler("help", start)) dp.add_handler(CommandHandler("set", set, pass_args=True)) + dp.add_handler(CommandHandler("unset", unset)) # log all errors dp.add_error_handler(error) diff --git a/telegram/ext/__init__.py b/telegram/ext/__init__.py index 59a8b800d63..10b0194a589 100644 --- a/telegram/ext/__init__.py +++ b/telegram/ext/__init__.py @@ -19,7 +19,7 @@ """Extensions over the Telegram Bot API to facilitate bot making""" from .dispatcher import Dispatcher -from .jobqueue import JobQueue +from .jobqueue import JobQueue, Job from .updater import Updater from .callbackqueryhandler import CallbackQueryHandler from .choseninlineresulthandler import ChosenInlineResultHandler @@ -32,7 +32,7 @@ from .stringregexhandler import StringRegexHandler from .typehandler import TypeHandler -__all__ = ('Dispatcher', 'JobQueue', 'Updater', 'CallbackQueryHandler', - 'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler', - 'MessageHandler', 'Filters', 'RegexHandler', 'StringCommandHandler', - 'StringRegexHandler', 'TypeHandler') +__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler', + 'ChosenInlineResultHandler', 'CommandHandler', 'Handler', + 'InlineQueryHandler', 'MessageHandler', 'Filters', 'RegexHandler', + 'StringCommandHandler', 'StringRegexHandler', 'TypeHandler') diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 616468399e2..8a1dad25385 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -20,7 +20,7 @@ import logging import time -from threading import Thread, Lock +from threading import Thread, Lock, Event from queue import PriorityQueue @@ -29,56 +29,50 @@ class JobQueue(object): This class allows you to periodically perform tasks with the bot. Attributes: - tick_interval (float): queue (PriorityQueue): bot (Bot): - running (bool): Args: bot (Bot): The bot instance that should be passed to the jobs - tick_interval (Optional[float]): The interval this queue should check - the newest task in seconds. Defaults to 1.0 """ - def __init__(self, bot, tick_interval=1.0): - self.tick_interval = tick_interval + def __init__(self, bot): self.queue = PriorityQueue() self.bot = bot self.logger = logging.getLogger(__name__) self.__lock = Lock() + self.__tick = Event() + self.next_peek = None self.running = False - def put(self, run, interval, repeat=True, next_t=None, prevent_autostart=False): + def put(self, job, next_t=None, prevent_autostart=False): """ Queue a new job. If the JobQueue is not running, it will be started. Args: - run (function): A function that takes the parameter `bot` - interval (float): The interval in seconds in which `run` should be - executed - repeat (Optional[bool]): If `False`, job will only be executed once - next_t (Optional[float]): Time in seconds in which run should be - executed first. Defaults to `interval` - prevent_autostart (Optional[bool]): If `True`, the job queue will - not be started automatically if it is not running. + job (Job): The ``Job`` instance representing the new job + next_t (Optional[float]): Time in seconds in which the job should be executed first. + Defaults to ``job.interval`` + prevent_autostart (Optional[bool]): If ``True``, the job queue will not be started + automatically if it is not running. Defaults to ``False`` """ - name = run.__name__ - job = JobQueue.Job() - job.run = run - job.interval = interval - job.name = name - job.repeat = repeat + job.job_queue = self if next_t is None: - next_t = interval + next_t = job.interval - next_t += time.time() + now = time.time() + next_t += now self.logger.debug('Putting a %s with t=%f' % (job.name, next_t)) self.queue.put((next_t, job)) + if not self.next_peek or self.next_peek > next_t: + self.next_peek = next_t + self.__tick.set() + if not self.running and not prevent_autostart: self.logger.debug('Auto-starting JobQueue') self.start() @@ -90,25 +84,45 @@ def tick(self): now = time.time() self.logger.debug('Ticking jobs with t=%f' % now) + while not self.queue.empty(): - t, j = self.queue.queue[0] - self.logger.debug('Peeked at %s with t=%f' % (j.name, t)) + t, job = self.queue.queue[0] + self.logger.debug('Peeked at %s with t=%f' % (job.name, t)) - if t < now: + if t <= now: self.queue.get() - self.logger.debug('Running job %s' % j.name) - try: - j.run(self.bot) - except: - self.logger.exception('An uncaught error was raised while ' - 'executing job %s' % j.name) - if j.repeat: - self.put(j.run, j.interval) + + if job._remove.is_set(): + self.logger.debug('Removing job %s' % job.name) + continue + + elif job.enabled: + self.logger.debug('Running job %s' % job.name) + + try: + job.run() + + except: + self.logger.exception('An uncaught error was raised while executing job %s' + % job.name) + + else: + self.logger.debug('Skipping disabled job %s' % job.name) + + if job.repeat: + self.put(job) + continue self.logger.debug('Next task isn\'t due yet. Finished!') + self.next_peek = t break + else: + self.next_peek = None + + self.__tick.clear() + def start(self): """ Starts the job_queue thread. @@ -128,9 +142,16 @@ def _start(self): Thread target of thread 'job_queue'. Runs in background and performs ticks on the job queue. """ + while self.running: + self.__tick.wait(self.next_peek and self.next_peek - time.time()) + + # If we were woken up by set(), wait with the new timeout + if self.__tick.is_set(): + self.__tick.clear() + continue + self.tick() - time.sleep(self.tick_interval) self.logger.debug('Job Queue thread stopped') @@ -141,14 +162,66 @@ def stop(self): with self.__lock: self.running = False - class Job(object): - """ Inner class that represents a job """ - interval = None - name = None - repeat = None + self.__tick.set() + + def jobs(self): + """Returns a tuple of all jobs that are currently in the ``JobQueue``""" + return tuple(job[1] for job in self.queue.queue if job) + + +class Job(object): + """This class encapsulates a Job + + Attributes: + callback (function): + interval (float): + repeat (bool): + name (str): + enabled (bool): If this job is currently active + + Args: + callback (function): The callback function that should be executed by the Job. It should + take two parameters ``bot`` and ``job``, where ``job`` is the ``Job`` instance. It + can be used to terminate the job or modify its interval. + interval (float): The interval in which this job should execute its callback function in + seconds. + repeat (Optional[bool]): If this job should be periodically execute its callback function + (``True``) or only once (``False``). (default=``True``) + + """ + job_queue = None + + def __init__(self, callback, interval, repeat=True): + self.callback = callback + self.interval = interval + self.repeat = repeat + + self.name = callback.__name__ + self._remove = Event() + self._enabled = Event() + self._enabled.set() + + def run(self): + """Executes the callback function""" + self.callback(self.job_queue.bot, self) + + def schedule_removal(self): + """ + Schedules this job for removal from the ``JobQueue``. It will be removed without executing + its callback function again. + """ + self._remove.set() + + def is_enabled(self): + return self._enabled.is_set() + + def set_enabled(self, status): + if status: + self._enabled.set() + else: + self._enabled.clear() - def run(self): - pass + enabled = property(is_enabled, set_enabled) - def __lt__(self, other): - return False + def __lt__(self, other): + return False diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py index e552c81e255..d9d687ac57f 100644 --- a/telegram/ext/updater.py +++ b/telegram/ext/updater.py @@ -69,8 +69,7 @@ def __init__(self, token=None, base_url=None, workers=4, - bot=None, - job_queue_tick_interval=1.0): + bot=None): if (token is None) and (bot is None): raise ValueError('`token` or `bot` must be passed') if (token is not None) and (bot is not None): @@ -81,7 +80,7 @@ def __init__(self, else: self.bot = Bot(token, base_url) self.update_queue = Queue() - self.job_queue = JobQueue(self.bot, job_queue_tick_interval) + self.job_queue = JobQueue(self.bot) self.__exception_event = Event() self.dispatcher = Dispatcher(self.bot, self.update_queue, workers, self.__exception_event) self.last_update_id = 0 diff --git a/tests/test_jobqueue.py b/tests/test_jobqueue.py index 4ac90035df4..d54ea989023 100644 --- a/tests/test_jobqueue.py +++ b/tests/test_jobqueue.py @@ -31,7 +31,7 @@ sys.path.append('.') -from telegram.ext import JobQueue, Updater +from telegram.ext import JobQueue, Job, Updater from tests.base import BaseTest # Enable logging @@ -52,53 +52,107 @@ class JobQueueTest(BaseTest, unittest.TestCase): """ def setUp(self): - self.jq = JobQueue("Bot", tick_interval=0.005) + self.jq = JobQueue("Bot") self.result = 0 def tearDown(self): if self.jq is not None: self.jq.stop() - def job1(self, bot): + def job1(self, bot, job): self.result += 1 - def job2(self, bot): + def job2(self, bot, job): raise Exception("Test Error") + def job3(self, bot, job): + self.result += 1 + job.schedule_removal() + def test_basic(self): - self.jq.put(self.job1, 0.1) + self.jq.put(Job(self.job1, 0.1)) sleep(1.5) self.assertGreaterEqual(self.result, 10) def test_noRepeat(self): - self.jq.put(self.job1, 0.1, repeat=False) + self.jq.put(Job(self.job1, 0.1, repeat=False)) sleep(0.5) self.assertEqual(1, self.result) def test_nextT(self): - self.jq.put(self.job1, 0.1, next_t=0.5) + self.jq.put(Job(self.job1, 0.1), next_t=0.5) sleep(0.45) self.assertEqual(0, self.result) sleep(0.1) self.assertEqual(1, self.result) def test_multiple(self): - self.jq.put(self.job1, 0.1, repeat=False) - self.jq.put(self.job1, 0.2, repeat=False) - self.jq.put(self.job1, 0.4) + self.jq.put(Job(self.job1, 0.1, repeat=False)) + self.jq.put(Job(self.job1, 0.2, repeat=False)) + self.jq.put(Job(self.job1, 0.4)) sleep(1) self.assertEqual(4, self.result) + def test_disabled(self): + j0 = Job(self.job1, 0.1) + j1 = Job(self.job1, 0.2) + + self.jq.put(j0) + self.jq.put(Job(self.job1, 0.4)) + self.jq.put(j1) + + j0.enabled = False + j1.enabled = False + + sleep(1) + self.assertEqual(2, self.result) + + def test_schedule_removal(self): + j0 = Job(self.job1, 0.1) + j1 = Job(self.job1, 0.2) + + self.jq.put(j0) + self.jq.put(Job(self.job1, 0.4)) + self.jq.put(j1) + + j0.schedule_removal() + j1.schedule_removal() + + sleep(1) + self.assertEqual(2, self.result) + + def test_schedule_removal_from_within(self): + self.jq.put(Job(self.job1, 0.4)) + self.jq.put(Job(self.job3, 0.2)) + + sleep(1) + self.assertEqual(3, self.result) + + def test_longer_first(self): + self.jq.put(Job(self.job1, 0.2, repeat=False)) + self.jq.put(Job(self.job1, 0.1, repeat=False)) + sleep(0.15) + self.assertEqual(1, self.result) + def test_error(self): - self.jq.put(self.job2, 0.1) - self.jq.put(self.job1, 0.2) + self.jq.put(Job(self.job2, 0.1)) + self.jq.put(Job(self.job1, 0.2)) self.jq.start() - sleep(0.4) - self.assertEqual(1, self.result) + sleep(0.5) + self.assertEqual(2, self.result) + + def test_jobs_tuple(self): + + jobs = tuple(Job(self.job1, t) for t in range(5, 25)) + + for job in jobs: + self.jq.put(job) + + self.assertTupleEqual(jobs, self.jq.jobs()) def test_inUpdater(self): - u = Updater(bot="MockBot", job_queue_tick_interval=0.005) - u.job_queue.put(self.job1, 0.5) + u = Updater(bot="MockBot") + u.job_queue.put(Job(self.job1, 0.5)) sleep(0.75) self.assertEqual(1, self.result) u.stop() From 3aedd78e297e8647507e2c0c46caddf54b62b331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Wed, 25 May 2016 22:51:13 +0200 Subject: [PATCH 02/18] make job queue API similar to the dispatcher, add new functionality --- README.rst | 44 +++++++++-- examples/timerbot.py | 36 ++++++--- telegram/ext/__init__.py | 10 +-- telegram/ext/jobqueue.py | 163 ++++++++++++++++++++++++++++----------- telegram/ext/updater.py | 5 +- tests/test_jobqueue.py | 86 +++++++++++++++++---- 6 files changed, 258 insertions(+), 86 deletions(-) diff --git a/README.rst b/README.rst index b7ce2c8c646..e24af25b7ca 100644 --- a/README.rst +++ b/README.rst @@ -395,23 +395,53 @@ The ``JobQueue`` allows you to perform tasks with a delay or even periodically. >>> u = Updater('TOKEN') >>> j = u.job_queue -The job queue uses functions for tasks, so we define one and add it to the queue. Usually, when the first job is added to the queue, it wil start automatically. We can prevent this by setting ``prevent_autostart=True``: +The job queue uses the ``Job`` class for tasks. We define a callback function, instantiate a ``Job`` and add it to the queue. + +Usually, when the first job is added to the queue, it wil start automatically. We can prevent this by setting ``prevent_autostart=True``: .. code:: python - >>> def job1(bot): + >>> from telegram.ext import Job + >>> def callback_minute(bot, job): ... bot.sendMessage(chat_id='@examplechannel', text='One message every minute') - >>> j.put(job1, 60, next_t=0, prevent_autostart=True) + ... + >>> job_minute = Job(callback_minute, 60.0, next_t=0.0) + >>> j.put(job_minute, prevent_autostart=True) -You can also have a job that will not be executed repeatedly: +You can also have a job that will be executed only once: .. code:: python - >>> def job2(bot): + >>> def callback_30(bot, job): ... bot.sendMessage(chat_id='@examplechannel', text='A single message with 30s delay') - >>> j.put(job2, 30, repeat=False) + ... + >>> j.put(Job(callback_30, 30.0, repeat=False)) + +Now, because we didn't prevent the auto start this time, the queue will start working. It runs in a seperate thread, so it is non-blocking. + +Jobs can be temporarily disabled or completely removed from the ``JobQueue``: + +.. code:: python + + >>> job_minute.enabled = False + >>> job_minute.schedule_removal() + +Please note that ``schedule_removal`` does not immediately removes the job from the queue. Instead, it is marked for removal and will be removed as soon as its current interval is over (it will not run again after being marked for removal). + +A job can also change its own behaviour, as it is passed to the callback function as the second argument: + +.. code:: python + + >>> def callback_increasing(bot, job): + ... bot.sendMessage(chat_id='@examplechannel', + ... text='Sending messages with increasing delay up to 10s, then stops.') + ... job.interval += 1.0 + ... if job.interval > 10.0: + ... job.schedule_removal() + ... + >>> j.put(Job(callback_increasing, 1.0)) -Now, because we didn't prevent the auto start this time, the queue will start ticking. It runs in a seperate thread, so it is non-blocking. When we stop the Updater, the related queue will be stopped as well: +When we stop the Updater, the related queue will be stopped as well: .. code:: python diff --git a/examples/timerbot.py b/examples/timerbot.py index 4c9faccf7d1..151f45156b0 100644 --- a/examples/timerbot.py +++ b/examples/timerbot.py @@ -17,15 +17,16 @@ bot. """ -from telegram.ext import Updater, CommandHandler +from telegram.ext import Updater, CommandHandler, Job import logging # Enable logging logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - level=logging.INFO) + level=logging.DEBUG) logger = logging.getLogger(__name__) job_queue = None +timers = dict() # Define a few command handlers. These usually take the two arguments bot and @@ -35,7 +36,7 @@ def start(bot, update): def set(bot, update, args): - """ Adds a job to the queue """ + """Adds a job to the queue""" chat_id = update.message.chat_id try: # args[0] should contain the time for the timer in seconds @@ -43,20 +44,34 @@ def set(bot, update, args): if due < 0: bot.sendMessage(chat_id, text='Sorry we can not go back to future!') - def alarm(bot): - """ Inner function to send the alarm message """ + def alarm(bot, job): + """Inner function to send the alarm message""" bot.sendMessage(chat_id, text='Beep!') # Add job to queue - job_queue.put(alarm, due, repeat=False) + job = Job(alarm, due, repeat=False) + timers[chat_id] = job + job_queue.put(job) + bot.sendMessage(chat_id, text='Timer successfully set!') - except IndexError: - bot.sendMessage(chat_id, text='Usage: /set ') - except ValueError: + except (IndexError, ValueError): bot.sendMessage(chat_id, text='Usage: /set ') +def unset(bot, update): + """Removes the job if the user changed their mind""" + chat_id = update.message.chat_id + + if chat_id not in timers: + bot.sendMessage(chat_id, text='You have no active timer') + return + + job = timers[chat_id] + job.schedule_removal() + bot.sendMessage(chat_id, text='Timer successfully unset!') + + def error(bot, update, error): logger.warn('Update "%s" caused error "%s"' % (update, error)) @@ -64,7 +79,7 @@ def error(bot, update, error): def main(): global job_queue - updater = Updater("TOKEN") + updater = Updater("148447715:AAH4M0gzPG11_mdQS1Qeb0Ex30I5-rw9bMY") job_queue = updater.job_queue # Get the dispatcher to register handlers @@ -74,6 +89,7 @@ def main(): dp.add_handler(CommandHandler("start", start)) dp.add_handler(CommandHandler("help", start)) dp.add_handler(CommandHandler("set", set, pass_args=True)) + dp.add_handler(CommandHandler("unset", unset)) # log all errors dp.add_error_handler(error) diff --git a/telegram/ext/__init__.py b/telegram/ext/__init__.py index 59a8b800d63..10b0194a589 100644 --- a/telegram/ext/__init__.py +++ b/telegram/ext/__init__.py @@ -19,7 +19,7 @@ """Extensions over the Telegram Bot API to facilitate bot making""" from .dispatcher import Dispatcher -from .jobqueue import JobQueue +from .jobqueue import JobQueue, Job from .updater import Updater from .callbackqueryhandler import CallbackQueryHandler from .choseninlineresulthandler import ChosenInlineResultHandler @@ -32,7 +32,7 @@ from .stringregexhandler import StringRegexHandler from .typehandler import TypeHandler -__all__ = ('Dispatcher', 'JobQueue', 'Updater', 'CallbackQueryHandler', - 'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler', - 'MessageHandler', 'Filters', 'RegexHandler', 'StringCommandHandler', - 'StringRegexHandler', 'TypeHandler') +__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler', + 'ChosenInlineResultHandler', 'CommandHandler', 'Handler', + 'InlineQueryHandler', 'MessageHandler', 'Filters', 'RegexHandler', + 'StringCommandHandler', 'StringRegexHandler', 'TypeHandler') diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 616468399e2..8a1dad25385 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -20,7 +20,7 @@ import logging import time -from threading import Thread, Lock +from threading import Thread, Lock, Event from queue import PriorityQueue @@ -29,56 +29,50 @@ class JobQueue(object): This class allows you to periodically perform tasks with the bot. Attributes: - tick_interval (float): queue (PriorityQueue): bot (Bot): - running (bool): Args: bot (Bot): The bot instance that should be passed to the jobs - tick_interval (Optional[float]): The interval this queue should check - the newest task in seconds. Defaults to 1.0 """ - def __init__(self, bot, tick_interval=1.0): - self.tick_interval = tick_interval + def __init__(self, bot): self.queue = PriorityQueue() self.bot = bot self.logger = logging.getLogger(__name__) self.__lock = Lock() + self.__tick = Event() + self.next_peek = None self.running = False - def put(self, run, interval, repeat=True, next_t=None, prevent_autostart=False): + def put(self, job, next_t=None, prevent_autostart=False): """ Queue a new job. If the JobQueue is not running, it will be started. Args: - run (function): A function that takes the parameter `bot` - interval (float): The interval in seconds in which `run` should be - executed - repeat (Optional[bool]): If `False`, job will only be executed once - next_t (Optional[float]): Time in seconds in which run should be - executed first. Defaults to `interval` - prevent_autostart (Optional[bool]): If `True`, the job queue will - not be started automatically if it is not running. + job (Job): The ``Job`` instance representing the new job + next_t (Optional[float]): Time in seconds in which the job should be executed first. + Defaults to ``job.interval`` + prevent_autostart (Optional[bool]): If ``True``, the job queue will not be started + automatically if it is not running. Defaults to ``False`` """ - name = run.__name__ - job = JobQueue.Job() - job.run = run - job.interval = interval - job.name = name - job.repeat = repeat + job.job_queue = self if next_t is None: - next_t = interval + next_t = job.interval - next_t += time.time() + now = time.time() + next_t += now self.logger.debug('Putting a %s with t=%f' % (job.name, next_t)) self.queue.put((next_t, job)) + if not self.next_peek or self.next_peek > next_t: + self.next_peek = next_t + self.__tick.set() + if not self.running and not prevent_autostart: self.logger.debug('Auto-starting JobQueue') self.start() @@ -90,25 +84,45 @@ def tick(self): now = time.time() self.logger.debug('Ticking jobs with t=%f' % now) + while not self.queue.empty(): - t, j = self.queue.queue[0] - self.logger.debug('Peeked at %s with t=%f' % (j.name, t)) + t, job = self.queue.queue[0] + self.logger.debug('Peeked at %s with t=%f' % (job.name, t)) - if t < now: + if t <= now: self.queue.get() - self.logger.debug('Running job %s' % j.name) - try: - j.run(self.bot) - except: - self.logger.exception('An uncaught error was raised while ' - 'executing job %s' % j.name) - if j.repeat: - self.put(j.run, j.interval) + + if job._remove.is_set(): + self.logger.debug('Removing job %s' % job.name) + continue + + elif job.enabled: + self.logger.debug('Running job %s' % job.name) + + try: + job.run() + + except: + self.logger.exception('An uncaught error was raised while executing job %s' + % job.name) + + else: + self.logger.debug('Skipping disabled job %s' % job.name) + + if job.repeat: + self.put(job) + continue self.logger.debug('Next task isn\'t due yet. Finished!') + self.next_peek = t break + else: + self.next_peek = None + + self.__tick.clear() + def start(self): """ Starts the job_queue thread. @@ -128,9 +142,16 @@ def _start(self): Thread target of thread 'job_queue'. Runs in background and performs ticks on the job queue. """ + while self.running: + self.__tick.wait(self.next_peek and self.next_peek - time.time()) + + # If we were woken up by set(), wait with the new timeout + if self.__tick.is_set(): + self.__tick.clear() + continue + self.tick() - time.sleep(self.tick_interval) self.logger.debug('Job Queue thread stopped') @@ -141,14 +162,66 @@ def stop(self): with self.__lock: self.running = False - class Job(object): - """ Inner class that represents a job """ - interval = None - name = None - repeat = None + self.__tick.set() + + def jobs(self): + """Returns a tuple of all jobs that are currently in the ``JobQueue``""" + return tuple(job[1] for job in self.queue.queue if job) + + +class Job(object): + """This class encapsulates a Job + + Attributes: + callback (function): + interval (float): + repeat (bool): + name (str): + enabled (bool): If this job is currently active + + Args: + callback (function): The callback function that should be executed by the Job. It should + take two parameters ``bot`` and ``job``, where ``job`` is the ``Job`` instance. It + can be used to terminate the job or modify its interval. + interval (float): The interval in which this job should execute its callback function in + seconds. + repeat (Optional[bool]): If this job should be periodically execute its callback function + (``True``) or only once (``False``). (default=``True``) + + """ + job_queue = None + + def __init__(self, callback, interval, repeat=True): + self.callback = callback + self.interval = interval + self.repeat = repeat + + self.name = callback.__name__ + self._remove = Event() + self._enabled = Event() + self._enabled.set() + + def run(self): + """Executes the callback function""" + self.callback(self.job_queue.bot, self) + + def schedule_removal(self): + """ + Schedules this job for removal from the ``JobQueue``. It will be removed without executing + its callback function again. + """ + self._remove.set() + + def is_enabled(self): + return self._enabled.is_set() + + def set_enabled(self, status): + if status: + self._enabled.set() + else: + self._enabled.clear() - def run(self): - pass + enabled = property(is_enabled, set_enabled) - def __lt__(self, other): - return False + def __lt__(self, other): + return False diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py index e552c81e255..d9d687ac57f 100644 --- a/telegram/ext/updater.py +++ b/telegram/ext/updater.py @@ -69,8 +69,7 @@ def __init__(self, token=None, base_url=None, workers=4, - bot=None, - job_queue_tick_interval=1.0): + bot=None): if (token is None) and (bot is None): raise ValueError('`token` or `bot` must be passed') if (token is not None) and (bot is not None): @@ -81,7 +80,7 @@ def __init__(self, else: self.bot = Bot(token, base_url) self.update_queue = Queue() - self.job_queue = JobQueue(self.bot, job_queue_tick_interval) + self.job_queue = JobQueue(self.bot) self.__exception_event = Event() self.dispatcher = Dispatcher(self.bot, self.update_queue, workers, self.__exception_event) self.last_update_id = 0 diff --git a/tests/test_jobqueue.py b/tests/test_jobqueue.py index 4ac90035df4..d54ea989023 100644 --- a/tests/test_jobqueue.py +++ b/tests/test_jobqueue.py @@ -31,7 +31,7 @@ sys.path.append('.') -from telegram.ext import JobQueue, Updater +from telegram.ext import JobQueue, Job, Updater from tests.base import BaseTest # Enable logging @@ -52,53 +52,107 @@ class JobQueueTest(BaseTest, unittest.TestCase): """ def setUp(self): - self.jq = JobQueue("Bot", tick_interval=0.005) + self.jq = JobQueue("Bot") self.result = 0 def tearDown(self): if self.jq is not None: self.jq.stop() - def job1(self, bot): + def job1(self, bot, job): self.result += 1 - def job2(self, bot): + def job2(self, bot, job): raise Exception("Test Error") + def job3(self, bot, job): + self.result += 1 + job.schedule_removal() + def test_basic(self): - self.jq.put(self.job1, 0.1) + self.jq.put(Job(self.job1, 0.1)) sleep(1.5) self.assertGreaterEqual(self.result, 10) def test_noRepeat(self): - self.jq.put(self.job1, 0.1, repeat=False) + self.jq.put(Job(self.job1, 0.1, repeat=False)) sleep(0.5) self.assertEqual(1, self.result) def test_nextT(self): - self.jq.put(self.job1, 0.1, next_t=0.5) + self.jq.put(Job(self.job1, 0.1), next_t=0.5) sleep(0.45) self.assertEqual(0, self.result) sleep(0.1) self.assertEqual(1, self.result) def test_multiple(self): - self.jq.put(self.job1, 0.1, repeat=False) - self.jq.put(self.job1, 0.2, repeat=False) - self.jq.put(self.job1, 0.4) + self.jq.put(Job(self.job1, 0.1, repeat=False)) + self.jq.put(Job(self.job1, 0.2, repeat=False)) + self.jq.put(Job(self.job1, 0.4)) sleep(1) self.assertEqual(4, self.result) + def test_disabled(self): + j0 = Job(self.job1, 0.1) + j1 = Job(self.job1, 0.2) + + self.jq.put(j0) + self.jq.put(Job(self.job1, 0.4)) + self.jq.put(j1) + + j0.enabled = False + j1.enabled = False + + sleep(1) + self.assertEqual(2, self.result) + + def test_schedule_removal(self): + j0 = Job(self.job1, 0.1) + j1 = Job(self.job1, 0.2) + + self.jq.put(j0) + self.jq.put(Job(self.job1, 0.4)) + self.jq.put(j1) + + j0.schedule_removal() + j1.schedule_removal() + + sleep(1) + self.assertEqual(2, self.result) + + def test_schedule_removal_from_within(self): + self.jq.put(Job(self.job1, 0.4)) + self.jq.put(Job(self.job3, 0.2)) + + sleep(1) + self.assertEqual(3, self.result) + + def test_longer_first(self): + self.jq.put(Job(self.job1, 0.2, repeat=False)) + self.jq.put(Job(self.job1, 0.1, repeat=False)) + sleep(0.15) + self.assertEqual(1, self.result) + def test_error(self): - self.jq.put(self.job2, 0.1) - self.jq.put(self.job1, 0.2) + self.jq.put(Job(self.job2, 0.1)) + self.jq.put(Job(self.job1, 0.2)) self.jq.start() - sleep(0.4) - self.assertEqual(1, self.result) + sleep(0.5) + self.assertEqual(2, self.result) + + def test_jobs_tuple(self): + + jobs = tuple(Job(self.job1, t) for t in range(5, 25)) + + for job in jobs: + self.jq.put(job) + + self.assertTupleEqual(jobs, self.jq.jobs()) def test_inUpdater(self): - u = Updater(bot="MockBot", job_queue_tick_interval=0.005) - u.job_queue.put(self.job1, 0.5) + u = Updater(bot="MockBot") + u.job_queue.put(Job(self.job1, 0.5)) sleep(0.75) self.assertEqual(1, self.result) u.stop() From b3142d2974809d27019580c0e5e8a1852346b58e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Wed, 25 May 2016 23:57:29 +0200 Subject: [PATCH 03/18] yapf --- examples/timerbot.py | 2 +- telegram/ext/__init__.py | 6 +++--- telegram/ext/jobqueue.py | 4 ++-- telegram/ext/updater.py | 6 +----- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/examples/timerbot.py b/examples/timerbot.py index 151f45156b0..c4020598227 100644 --- a/examples/timerbot.py +++ b/examples/timerbot.py @@ -79,7 +79,7 @@ def error(bot, update, error): def main(): global job_queue - updater = Updater("148447715:AAH4M0gzPG11_mdQS1Qeb0Ex30I5-rw9bMY") + updater = Updater("TOKEN") job_queue = updater.job_queue # Get the dispatcher to register handlers diff --git a/telegram/ext/__init__.py b/telegram/ext/__init__.py index 10b0194a589..8d89fd4c87c 100644 --- a/telegram/ext/__init__.py +++ b/telegram/ext/__init__.py @@ -33,6 +33,6 @@ from .typehandler import TypeHandler __all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler', - 'ChosenInlineResultHandler', 'CommandHandler', 'Handler', - 'InlineQueryHandler', 'MessageHandler', 'Filters', 'RegexHandler', - 'StringCommandHandler', 'StringRegexHandler', 'TypeHandler') + 'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler', + 'MessageHandler', 'Filters', 'RegexHandler', 'StringCommandHandler', + 'StringRegexHandler', 'TypeHandler') diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 8a1dad25385..7ee85664fad 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -103,8 +103,8 @@ def tick(self): job.run() except: - self.logger.exception('An uncaught error was raised while executing job %s' - % job.name) + self.logger.exception( + 'An uncaught error was raised while executing job %s' % job.name) else: self.logger.debug('Skipping disabled job %s' % job.name) diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py index d9d687ac57f..ae491bdb5e6 100644 --- a/telegram/ext/updater.py +++ b/telegram/ext/updater.py @@ -65,11 +65,7 @@ class Updater(object): ValueError: If both `token` and `bot` are passed or none of them. """ - def __init__(self, - token=None, - base_url=None, - workers=4, - bot=None): + def __init__(self, token=None, base_url=None, workers=4, bot=None): if (token is None) and (bot is None): raise ValueError('`token` or `bot` must be passed') if (token is not None) and (bot is not None): From 786216305cfb1e1fe201ae9f04ffe8995bf95a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Thu, 26 May 2016 13:55:30 +0200 Subject: [PATCH 04/18] Add context parameter in Job class #281 --- telegram/ext/jobqueue.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 7ee85664fad..79ef5e20af5 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -186,15 +186,18 @@ class Job(object): interval (float): The interval in which this job should execute its callback function in seconds. repeat (Optional[bool]): If this job should be periodically execute its callback function - (``True``) or only once (``False``). (default=``True``) + (``True``) or only once (``False``). Defaults to ``True`` + context (Optional[object]): Additional data needed for the callback function. Can be + accessed through ``job.context`` in the callback. Defaults to ``None`` """ job_queue = None - def __init__(self, callback, interval, repeat=True): + def __init__(self, callback, interval, repeat=True, context=None): self.callback = callback self.interval = interval self.repeat = repeat + self.context = context self.name = callback.__name__ self._remove = Event() From 41daccce07749aaea964257a66540cfeeb57143c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Thu, 26 May 2016 14:01:59 +0200 Subject: [PATCH 05/18] minor comments and formatting --- telegram/ext/jobqueue.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 79ef5e20af5..600287eaf79 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -16,7 +16,7 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. -"""This module contains the class JobQueue.""" +"""This module contains the classes JobQueue and Job.""" import logging import time @@ -69,6 +69,7 @@ def put(self, job, next_t=None, prevent_autostart=False): self.logger.debug('Putting a %s with t=%f' % (job.name, next_t)) self.queue.put((next_t, job)) + # Wake up the loop if this job should be executed next if not self.next_peek or self.next_peek > next_t: self.next_peek = next_t self.__tick.set() @@ -128,19 +129,21 @@ def start(self): Starts the job_queue thread. """ self.__lock.acquire() + if not self.running: self.running = True self.__lock.release() job_queue_thread = Thread(target=self._start, name="job_queue") job_queue_thread.start() self.logger.debug('Job Queue thread started') + else: self.__lock.release() def _start(self): """ - Thread target of thread 'job_queue'. Runs in background and performs - ticks on the job queue. + Thread target of thread ``job_queue``. Runs in background and performs ticks on the job + queue. """ while self.running: From 20067ff178ac538f509dd2516645eeedc71ef9e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Thu, 26 May 2016 14:07:44 +0200 Subject: [PATCH 06/18] add test for context parameter --- tests/test_jobqueue.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_jobqueue.py b/tests/test_jobqueue.py index d54ea989023..aef9d6723a1 100644 --- a/tests/test_jobqueue.py +++ b/tests/test_jobqueue.py @@ -69,11 +69,19 @@ def job3(self, bot, job): self.result += 1 job.schedule_removal() + def job4(self, bot, job): + self.result += job.context + def test_basic(self): self.jq.put(Job(self.job1, 0.1)) sleep(1.5) self.assertGreaterEqual(self.result, 10) + def test_job_with_context(self): + self.jq.put(Job(self.job4, 0.1, context=5)) + sleep(1.5) + self.assertGreaterEqual(self.result, 50) + def test_noRepeat(self): self.jq.put(Job(self.job1, 0.1, repeat=False)) sleep(0.5) From bb165b6acf37abcd583466770df0c80aa490e550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Thu, 26 May 2016 14:39:11 +0200 Subject: [PATCH 07/18] add pass_job_queue parameter to all handler classes --- telegram/ext/callbackqueryhandler.py | 9 ++- telegram/ext/choseninlineresulthandler.py | 9 ++- telegram/ext/commandhandler.py | 14 ++++- telegram/ext/dispatcher.py | 7 ++- telegram/ext/handler.py | 8 ++- telegram/ext/inlinequeryhandler.py | 9 ++- telegram/ext/messagehandler.py | 9 ++- telegram/ext/regexhandler.py | 10 +++- telegram/ext/stringcommandhandler.py | 14 ++++- telegram/ext/stringregexhandler.py | 10 +++- telegram/ext/typehandler.py | 14 ++++- telegram/ext/updater.py | 6 +- tests/test_updater.py | 73 +++++++++++++---------- 13 files changed, 139 insertions(+), 53 deletions(-) diff --git a/telegram/ext/callbackqueryhandler.py b/telegram/ext/callbackqueryhandler.py index 669943c96c3..98cf04bdfa8 100644 --- a/telegram/ext/callbackqueryhandler.py +++ b/telegram/ext/callbackqueryhandler.py @@ -34,10 +34,15 @@ class CallbackQueryHandler(Handler): pass_update_queue (optional[bool]): If the handler should be passed the update queue as a keyword argument called ``update_queue``. It can be used to insert updates. Default is ``False`` + pass_job_queue (optional[bool]): If the callback should be passed the job queue as a + keyword argument called ``job_queue``. It can be used to schedule new jobs. + Default is ``False`` """ - def __init__(self, callback, pass_update_queue=False): - super(CallbackQueryHandler, self).__init__(callback, pass_update_queue) + def __init__(self, callback, pass_update_queue=False, pass_job_queue=False): + super(CallbackQueryHandler, self).__init__(callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) def check_update(self, update): return isinstance(update, Update) and update.callback_query diff --git a/telegram/ext/choseninlineresulthandler.py b/telegram/ext/choseninlineresulthandler.py index 09e8bb3a081..6580c01435a 100644 --- a/telegram/ext/choseninlineresulthandler.py +++ b/telegram/ext/choseninlineresulthandler.py @@ -35,10 +35,15 @@ class ChosenInlineResultHandler(Handler): pass_update_queue (optional[bool]): If the handler should be passed the update queue as a keyword argument called ``update_queue``. It can be used to insert updates. Default is ``False`` + pass_job_queue (optional[bool]): If the callback should be passed the job queue as a + keyword argument called ``job_queue``. It can be used to schedule new jobs. + Default is ``False`` """ - def __init__(self, callback, pass_update_queue=False): - super(ChosenInlineResultHandler, self).__init__(callback, pass_update_queue) + def __init__(self, callback, pass_update_queue=False, pass_job_queue=False): + super(ChosenInlineResultHandler, self).__init__(callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) def check_update(self, update): return isinstance(update, Update) and update.chosen_inline_result diff --git a/telegram/ext/commandhandler.py b/telegram/ext/commandhandler.py index 2d22f9221a3..d185b335fa2 100644 --- a/telegram/ext/commandhandler.py +++ b/telegram/ext/commandhandler.py @@ -41,10 +41,20 @@ class CommandHandler(Handler): pass_update_queue (optional[bool]): If the handler should be passed the update queue as a keyword argument called ``update_queue``. It can be used to insert updates. Default is ``False`` + pass_job_queue (optional[bool]): If the callback should be passed the job queue as a + keyword argument called ``job_queue``. It can be used to schedule new jobs. + Default is ``False`` """ - def __init__(self, command, callback, pass_args=False, pass_update_queue=False): - super(CommandHandler, self).__init__(callback, pass_update_queue) + def __init__(self, + command, + callback, + pass_args=False, + pass_update_queue=False, + pass_job_queue=False): + super(CommandHandler, self).__init__(callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) self.command = command self.pass_args = pass_args diff --git a/telegram/ext/dispatcher.py b/telegram/ext/dispatcher.py index c715e6007d8..a380fd5e7cf 100644 --- a/telegram/ext/dispatcher.py +++ b/telegram/ext/dispatcher.py @@ -90,11 +90,16 @@ class Dispatcher(object): handlers update_queue (Queue): The synchronized queue that will contain the updates. + 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 """ - def __init__(self, bot, update_queue, workers=4, exception_event=None): + def __init__(self, bot, update_queue, job_queue=None, workers=4, exception_event=None): self.bot = bot self.update_queue = update_queue + self.job_queue = job_queue self.handlers = {} """:type: dict[int, list[Handler]""" diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 964bbb98aca..1273b47ad05 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -34,11 +34,15 @@ class Handler(object): pass_update_queue (optional[bool]): If the callback should be passed the update queue as a keyword argument called ``update_queue``. It can be used to insert updates. Default is ``False`` + pass_job_queue (optional[bool]): If the callback should be passed the job queue as a + keyword argument called ``job_queue``. It can be used to schedule new jobs. + Default is ``False`` """ - def __init__(self, callback, pass_update_queue=False): + def __init__(self, callback, pass_update_queue=False, pass_job_queue=False): self.callback = callback self.pass_update_queue = pass_update_queue + self.pass_job_queue = pass_job_queue def check_update(self, update): """ @@ -77,6 +81,8 @@ def collect_optional_args(self, dispatcher): optional_args = dict() if self.pass_update_queue: optional_args['update_queue'] = dispatcher.update_queue + if self.pass_job_queue: + optional_args['job_queue'] = dispatcher.job_queue return optional_args diff --git a/telegram/ext/inlinequeryhandler.py b/telegram/ext/inlinequeryhandler.py index 12cedfe6139..20f1dc5147c 100644 --- a/telegram/ext/inlinequeryhandler.py +++ b/telegram/ext/inlinequeryhandler.py @@ -34,10 +34,15 @@ class InlineQueryHandler(Handler): pass_update_queue (optional[bool]): If the handler should be passed the update queue as a keyword argument called ``update_queue``. It can be used to insert updates. Default is ``False`` + pass_job_queue (optional[bool]): If the callback should be passed the job queue as a + keyword argument called ``job_queue``. It can be used to schedule new jobs. + Default is ``False`` """ - def __init__(self, callback, pass_update_queue=False): - super(InlineQueryHandler, self).__init__(callback, pass_update_queue) + def __init__(self, callback, pass_update_queue=False, pass_job_queue=False): + super(InlineQueryHandler, self).__init__(callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) def check_update(self, update): return isinstance(update, Update) and update.inline_query diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index fd324876266..e6db95e4d5d 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -102,10 +102,15 @@ class MessageHandler(Handler): pass_update_queue (optional[bool]): If the handler should be passed the update queue as a keyword argument called ``update_queue``. It can be used to insert updates. Default is ``False`` + pass_job_queue (optional[bool]): If the callback should be passed the job queue as a + keyword argument called ``job_queue``. It can be used to schedule new jobs. + Default is ``False`` """ - def __init__(self, filters, callback, pass_update_queue=False): - super(MessageHandler, self).__init__(callback, pass_update_queue) + def __init__(self, filters, callback, pass_update_queue=False, pass_job_queue=False): + super(MessageHandler, self).__init__(callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) self.filters = filters def check_update(self, update): diff --git a/telegram/ext/regexhandler.py b/telegram/ext/regexhandler.py index 958f8f6d427..f699e2f720e 100644 --- a/telegram/ext/regexhandler.py +++ b/telegram/ext/regexhandler.py @@ -48,6 +48,9 @@ class RegexHandler(Handler): pass_update_queue (optional[bool]): If the handler should be passed the update queue as a keyword argument called ``update_queue``. It can be used to insert updates. Default is ``False`` + pass_job_queue (optional[bool]): If the callback should be passed the job queue as a + keyword argument called ``job_queue``. It can be used to schedule new jobs. + Default is ``False`` """ def __init__(self, @@ -55,8 +58,11 @@ def __init__(self, callback, pass_groups=False, pass_groupdict=False, - pass_update_queue=False): - super(RegexHandler, self).__init__(callback, pass_update_queue) + pass_update_queue=False, + pass_job_queue=False): + super(RegexHandler, self).__init__(callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) if isinstance(pattern, string_types): pattern = re.compile(pattern) diff --git a/telegram/ext/stringcommandhandler.py b/telegram/ext/stringcommandhandler.py index 9d69f98ab3a..97043320e3a 100644 --- a/telegram/ext/stringcommandhandler.py +++ b/telegram/ext/stringcommandhandler.py @@ -39,10 +39,20 @@ class StringCommandHandler(Handler): pass_update_queue (optional[bool]): If the handler should be passed the update queue as a keyword argument called ``update_queue``. It can be used to insert updates. Default is ``False`` + pass_job_queue (optional[bool]): If the callback should be passed the job queue as a + keyword argument called ``job_queue``. It can be used to schedule new jobs. + Default is ``False`` """ - def __init__(self, command, callback, pass_args=False, pass_update_queue=False): - super(StringCommandHandler, self).__init__(callback, pass_update_queue) + def __init__(self, + command, + callback, + pass_args=False, + pass_update_queue=False, + pass_job_queue=False): + super(StringCommandHandler, self).__init__(callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) self.command = command self.pass_args = pass_args diff --git a/telegram/ext/stringregexhandler.py b/telegram/ext/stringregexhandler.py index 5ec3896e8bd..88d7c1a95a2 100644 --- a/telegram/ext/stringregexhandler.py +++ b/telegram/ext/stringregexhandler.py @@ -47,6 +47,9 @@ class StringRegexHandler(Handler): pass_update_queue (optional[bool]): If the handler should be passed the update queue as a keyword argument called ``update_queue``. It can be used to insert updates. Default is ``False`` + pass_job_queue (optional[bool]): If the callback should be passed the job queue as a + keyword argument called ``job_queue``. It can be used to schedule new jobs. + Default is ``False`` """ def __init__(self, @@ -54,8 +57,11 @@ def __init__(self, callback, pass_groups=False, pass_groupdict=False, - pass_update_queue=False): - super(StringRegexHandler, self).__init__(callback, pass_update_queue) + pass_update_queue=False, + pass_job_queue=False): + super(StringRegexHandler, self).__init__(callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) if isinstance(pattern, string_types): pattern = re.compile(pattern) diff --git a/telegram/ext/typehandler.py b/telegram/ext/typehandler.py index f8ad76ceb97..bb836fe1972 100644 --- a/telegram/ext/typehandler.py +++ b/telegram/ext/typehandler.py @@ -37,10 +37,20 @@ class TypeHandler(Handler): pass_update_queue (optional[bool]): If the handler should be passed the update queue as a keyword argument called ``update_queue``. It can be used to insert updates. Default is ``False`` + pass_job_queue (optional[bool]): If the callback should be passed the job queue as a + keyword argument called ``job_queue``. It can be used to schedule new jobs. + Default is ``False`` """ - def __init__(self, type, callback, strict=False, pass_update_queue=False): - super(TypeHandler, self).__init__(callback, pass_update_queue) + def __init__(self, + type, + callback, + strict=False, + pass_update_queue=False, + pass_job_queue=False): + super(TypeHandler, self).__init__(callback, + pass_update_queue=pass_update_queue, + pass_job_queue=pass_job_queue) self.type = type self.strict = strict diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py index ae491bdb5e6..e7c814b15e4 100644 --- a/telegram/ext/updater.py +++ b/telegram/ext/updater.py @@ -78,7 +78,11 @@ def __init__(self, token=None, base_url=None, workers=4, bot=None): self.update_queue = Queue() self.job_queue = JobQueue(self.bot) self.__exception_event = Event() - self.dispatcher = Dispatcher(self.bot, self.update_queue, workers, self.__exception_event) + self.dispatcher = Dispatcher(self.bot, + self.update_queue, + job_queue=self.job_queue, + workers=workers, + exception_event=self.__exception_event) self.last_update_id = 0 self.logger = logging.getLogger(__name__) self.running = False diff --git a/tests/test_updater.py b/tests/test_updater.py index ab12d9bda46..aefb297aee3 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -71,6 +71,11 @@ class UpdaterTest(BaseTest, unittest.TestCase): WebhookHandler """ + updater = None + received_message = None + message_count = None + lock = None + def setUp(self): self.updater = None self.received_message = None @@ -116,9 +121,12 @@ def regexGroupHandlerTest(self, bot, update, groups, groupdict): self.received_message = (groups, groupdict) self.message_count += 1 - def additionalArgsTest(self, bot, update, update_queue, args): + def additionalArgsTest(self, bot, update, update_queue, job_queue, args): + job_queue.put(Job(lambda bot, job: job.schedule_removal(), 0.1)) + self.received_message = update self.message_count += 1 + if args[0] == 'resend': update_queue.put('/test5 noresend') elif args[0] == 'noresend': @@ -144,13 +152,13 @@ def test_addRemoveTelegramMessageHandler(self): d = self.updater.dispatcher from telegram.ext import Filters handler = MessageHandler([Filters.text], self.telegramHandlerTest) - d.addHandler(handler) + d.add_handler(handler) self.updater.start_polling(0.01) sleep(.1) self.assertEqual(self.received_message, 'Test') # Remove handler - d.removeHandler(handler) + d.remove_handler(handler) self.reset() self.updater.bot.send_messages = 1 @@ -159,7 +167,7 @@ def test_addRemoveTelegramMessageHandler(self): def test_addTelegramMessageHandlerMultipleMessages(self): self._setup_updater('Multiple', 100) - self.updater.dispatcher.addHandler(MessageHandler([], self.telegramHandlerTest)) + self.updater.dispatcher.add_handler(MessageHandler([], self.telegramHandlerTest)) self.updater.start_polling(0.0) sleep(2) self.assertEqual(self.received_message, 'Multiple') @@ -170,13 +178,13 @@ def test_addRemoveTelegramRegexHandler(self): d = self.updater.dispatcher regobj = re.compile('Te.*') handler = RegexHandler(regobj, self.telegramHandlerTest) - self.updater.dispatcher.addHandler(handler) + self.updater.dispatcher.add_handler(handler) self.updater.start_polling(0.01) sleep(.1) self.assertEqual(self.received_message, 'Test2') # Remove handler - d.removeHandler(handler) + d.remove_handler(handler) self.reset() self.updater.bot.send_messages = 1 @@ -187,13 +195,13 @@ def test_addRemoveTelegramCommandHandler(self): self._setup_updater('/test') d = self.updater.dispatcher handler = CommandHandler('test', self.telegramHandlerTest) - self.updater.dispatcher.addHandler(handler) + self.updater.dispatcher.add_handler(handler) self.updater.start_polling(0.01) sleep(.1) self.assertEqual(self.received_message, '/test') # Remove handler - d.removeHandler(handler) + d.remove_handler(handler) self.reset() self.updater.bot.send_messages = 1 @@ -204,14 +212,14 @@ def test_addRemoveStringRegexHandler(self): self._setup_updater('', messages=0) d = self.updater.dispatcher handler = StringRegexHandler('Te.*', self.stringHandlerTest) - d.addHandler(handler) + d.add_handler(handler) queue = self.updater.start_polling(0.01) queue.put('Test3') sleep(.1) self.assertEqual(self.received_message, 'Test3') # Remove handler - d.removeHandler(handler) + d.remove_handler(handler) self.reset() queue.put('Test3') @@ -222,7 +230,7 @@ def test_addRemoveStringCommandHandler(self): self._setup_updater('', messages=0) d = self.updater.dispatcher handler = StringCommandHandler('test3', self.stringHandlerTest) - d.addHandler(handler) + d.add_handler(handler) queue = self.updater.start_polling(0.01) queue.put('/test3') @@ -230,7 +238,7 @@ def test_addRemoveStringCommandHandler(self): self.assertEqual(self.received_message, '/test3') # Remove handler - d.removeHandler(handler) + d.remove_handler(handler) self.reset() queue.put('/test3') @@ -240,7 +248,7 @@ def test_addRemoveStringCommandHandler(self): def test_addRemoveErrorHandler(self): self._setup_updater('', messages=0) d = self.updater.dispatcher - d.addErrorHandler(self.errorHandlerTest) + d.add_error_handler(self.errorHandlerTest) queue = self.updater.start_polling(0.01) error = TelegramError("Unauthorized.") queue.put(error) @@ -248,7 +256,7 @@ def test_addRemoveErrorHandler(self): self.assertEqual(self.received_message, "Unauthorized.") # Remove handler - d.removeErrorHandler(self.errorHandlerTest) + d.remove_error_handler(self.errorHandlerTest) self.reset() queue.put(error) @@ -259,8 +267,8 @@ def test_errorInHandler(self): self._setup_updater('', messages=0) d = self.updater.dispatcher handler = StringRegexHandler('.*', self.errorRaisingHandlerTest) - d.addHandler(handler) - self.updater.dispatcher.addErrorHandler(self.errorHandlerTest) + d.add_handler(handler) + self.updater.dispatcher.add_error_handler(self.errorHandlerTest) queue = self.updater.start_polling(0.01) queue.put('Test Error 1') @@ -271,7 +279,7 @@ def test_cleanBeforeStart(self): self._setup_updater('') d = self.updater.dispatcher handler = MessageHandler([], self.telegramHandlerTest) - d.addHandler(handler) + d.add_handler(handler) self.updater.start_polling(0.01, clean=True) sleep(.1) self.assertEqual(self.message_count, 0) @@ -280,7 +288,7 @@ def test_cleanBeforeStart(self): def test_errorOnGetUpdates(self): self._setup_updater('', raise_error=True) d = self.updater.dispatcher - d.addErrorHandler(self.errorHandlerTest) + d.add_error_handler(self.errorHandlerTest) self.updater.start_polling(0.01) sleep(.1) self.assertEqual(self.received_message, "Test Error 2") @@ -289,7 +297,7 @@ def test_addRemoveTypeHandler(self): self._setup_updater('', messages=0) d = self.updater.dispatcher handler = TypeHandler(dict, self.stringHandlerTest) - d.addHandler(handler) + d.add_handler(handler) queue = self.updater.start_polling(0.01) payload = {"Test": 42} queue.put(payload) @@ -297,7 +305,7 @@ def test_addRemoveTypeHandler(self): self.assertEqual(self.received_message, payload) # Remove handler - d.removeHandler(handler) + d.remove_handler(handler) self.reset() queue.put(payload) @@ -309,8 +317,8 @@ def test_addRemoveInlineQueryHandler(self): d = self.updater.dispatcher handler = InlineQueryHandler(self.telegramInlineHandlerTest) handler2 = ChosenInlineResultHandler(self.telegramInlineHandlerTest) - d.addHandler(handler) - d.addHandler(handler2) + d.add_handler(handler) + d.add_handler(handler2) queue = self.updater.start_polling(0.01) update = Update(update_id=0, inline_query="testquery") update2 = Update(update_id=0, chosen_inline_result="testresult") @@ -323,8 +331,8 @@ def test_addRemoveInlineQueryHandler(self): self.assertEqual(self.received_message[1], "testresult") # Remove handler - d.removeHandler(handler) - d.removeHandler(handler2) + d.remove_handler(handler) + d.remove_handler(handler2) self.reset() queue.put(update) @@ -335,7 +343,7 @@ def test_addRemoveCallbackQueryHandler(self): self._setup_updater('', messages=0) d = self.updater.dispatcher handler = CallbackQueryHandler(self.telegramCallbackHandlerTest) - d.addHandler(handler) + d.add_handler(handler) queue = self.updater.start_polling(0.01) update = Update(update_id=0, callback_query="testcallback") queue.put(update) @@ -343,7 +351,7 @@ def test_addRemoveCallbackQueryHandler(self): self.assertEqual(self.received_message, "testcallback") # Remove handler - d.removeHandler(handler) + d.remove_handler(handler) self.reset() queue.put(update) @@ -354,7 +362,7 @@ def test_runAsync(self): self._setup_updater('Test5', messages=2) d = self.updater.dispatcher handler = MessageHandler([], self.asyncHandlerTest) - d.addHandler(handler) + d.add_handler(handler) self.updater.start_polling(0.01) sleep(1.2) self.assertEqual(self.received_message, 'Test5') @@ -365,8 +373,9 @@ def test_additionalArgs(self): handler = StringCommandHandler('test5', self.additionalArgsTest, pass_update_queue=True, + pass_job_queue=True, pass_args=True) - self.updater.dispatcher.addHandler(handler) + self.updater.dispatcher.add_handler(handler) queue = self.updater.start_polling(0.01) queue.put('/test5 resend') @@ -381,7 +390,7 @@ def test_regexGroupHandler(self): self.regexGroupHandlerTest, pass_groupdict=True, pass_groups=True) - d.addHandler(handler) + d.add_handler(handler) queue = self.updater.start_polling(0.01) queue.put('This is a test message for regex group matching.') sleep(.1) @@ -392,7 +401,7 @@ def test_runAsyncWithAdditionalArgs(self): self._setup_updater('Test6', messages=2) d = self.updater.dispatcher handler = MessageHandler([], self.asyncAdditionalHandlerTest, pass_update_queue=True) - d.addHandler(handler) + d.add_handler(handler) self.updater.start_polling(0.01) sleep(1.2) self.assertEqual(self.received_message, 'Test6') @@ -402,7 +411,7 @@ def test_webhook(self): self._setup_updater('', messages=0) d = self.updater.dispatcher handler = MessageHandler([], self.telegramHandlerTest) - d.addHandler(handler) + d.add_handler(handler) ip = '127.0.0.1' port = randrange(1024, 49152) # Select random port for travis @@ -452,7 +461,7 @@ def test_webhook_no_ssl(self): self._setup_updater('', messages=0) d = self.updater.dispatcher handler = MessageHandler([], self.telegramHandlerTest) - d.addHandler(handler) + d.add_handler(handler) ip = '127.0.0.1' port = randrange(1024, 49152) # Select random port for travis From e7f4a07b7af79959708e4b19f8a4a3c83e7a64b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Thu, 26 May 2016 14:48:50 +0200 Subject: [PATCH 08/18] update timerbot example with pass_job_queue --- examples/timerbot.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/timerbot.py b/examples/timerbot.py index c4020598227..8473a543a32 100644 --- a/examples/timerbot.py +++ b/examples/timerbot.py @@ -25,7 +25,6 @@ level=logging.DEBUG) logger = logging.getLogger(__name__) -job_queue = None timers = dict() @@ -35,7 +34,7 @@ def start(bot, update): bot.sendMessage(update.message.chat_id, text='Hi! Use /set to ' 'set a timer') -def set(bot, update, args): +def set(bot, update, args, job_queue): """Adds a job to the queue""" chat_id = update.message.chat_id try: @@ -77,10 +76,7 @@ def error(bot, update, error): def main(): - global job_queue - updater = Updater("TOKEN") - job_queue = updater.job_queue # Get the dispatcher to register handlers dp = updater.dispatcher @@ -88,7 +84,7 @@ def main(): # on different commands - answer in Telegram dp.add_handler(CommandHandler("start", start)) dp.add_handler(CommandHandler("help", start)) - dp.add_handler(CommandHandler("set", set, pass_args=True)) + dp.add_handler(CommandHandler("set", set, pass_args=True, pass_job_queue=True)) dp.add_handler(CommandHandler("unset", unset)) # log all errors From 2534e0df9b08965d22f90e1a2385d37efbe05d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Sat, 28 May 2016 13:41:23 +0200 Subject: [PATCH 09/18] allow jobs to be ran outside of jobqueue --- telegram/ext/jobqueue.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 600287eaf79..4094bc284db 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -101,7 +101,7 @@ def tick(self): self.logger.debug('Running job %s' % job.name) try: - job.run() + job.run(self.bot) except: self.logger.exception( @@ -180,7 +180,7 @@ class Job(object): interval (float): repeat (bool): name (str): - enabled (bool): If this job is currently active + enabled (bool): Boolean property that decides if this job is currently active Args: callback (function): The callback function that should be executed by the Job. It should @@ -207,9 +207,9 @@ def __init__(self, callback, interval, repeat=True, context=None): self._enabled = Event() self._enabled.set() - def run(self): + def run(self, bot): """Executes the callback function""" - self.callback(self.job_queue.bot, self) + self.callback(bot, self) def schedule_removal(self): """ From 406303d6bb868b27fd8b4c7f45b52b88c0a3b1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Sat, 28 May 2016 13:46:57 +0200 Subject: [PATCH 10/18] refactor: running -> _running, next_peek -> _next_peek --- telegram/ext/jobqueue.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 4094bc284db..9572f0eea1e 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -43,8 +43,8 @@ def __init__(self, bot): self.logger = logging.getLogger(__name__) self.__lock = Lock() self.__tick = Event() - self.next_peek = None - self.running = False + self._next_peek = None + self._running = False def put(self, job, next_t=None, prevent_autostart=False): """ @@ -70,11 +70,11 @@ def put(self, job, next_t=None, prevent_autostart=False): self.queue.put((next_t, job)) # Wake up the loop if this job should be executed next - if not self.next_peek or self.next_peek > next_t: - self.next_peek = next_t + if not self._next_peek or self._next_peek > next_t: + self._next_peek = next_t self.__tick.set() - if not self.running and not prevent_autostart: + if not self._running and not prevent_autostart: self.logger.debug('Auto-starting JobQueue') self.start() @@ -116,11 +116,11 @@ def tick(self): continue self.logger.debug('Next task isn\'t due yet. Finished!') - self.next_peek = t + self._next_peek = t break else: - self.next_peek = None + self._next_peek = None self.__tick.clear() @@ -130,8 +130,8 @@ def start(self): """ self.__lock.acquire() - if not self.running: - self.running = True + if not self._running: + self._running = True self.__lock.release() job_queue_thread = Thread(target=self._start, name="job_queue") job_queue_thread.start() @@ -146,8 +146,8 @@ def _start(self): queue. """ - while self.running: - self.__tick.wait(self.next_peek and self.next_peek - time.time()) + while self._running: + self.__tick.wait(self._next_peek and self._next_peek - time.time()) # If we were woken up by set(), wait with the new timeout if self.__tick.is_set(): @@ -163,7 +163,7 @@ def stop(self): Stops the thread """ with self.__lock: - self.running = False + self._running = False self.__tick.set() From 783f9c375ccf105fbf693d8b0a243810a9e2fb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Sat, 28 May 2016 14:21:39 +0200 Subject: [PATCH 11/18] move job_queue kwarg to end --- telegram/ext/dispatcher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/ext/dispatcher.py b/telegram/ext/dispatcher.py index a380fd5e7cf..fd10639ff85 100644 --- a/telegram/ext/dispatcher.py +++ b/telegram/ext/dispatcher.py @@ -96,7 +96,7 @@ class Dispatcher(object): decorator """ - def __init__(self, bot, update_queue, job_queue=None, workers=4, exception_event=None): + def __init__(self, bot, update_queue, workers=4, exception_event=None, job_queue=None): self.bot = bot self.update_queue = update_queue self.job_queue = job_queue From d40f0a830914105f7f3cf4df23e7101e3566cb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Sat, 28 May 2016 16:04:19 +0200 Subject: [PATCH 12/18] update update_queue and job_queue docstrings on all handlers --- telegram/ext/callbackqueryhandler.py | 14 ++++++++------ telegram/ext/choseninlineresulthandler.py | 14 ++++++++------ telegram/ext/commandhandler.py | 14 ++++++++------ telegram/ext/handler.py | 14 ++++++++------ telegram/ext/inlinequeryhandler.py | 14 ++++++++------ telegram/ext/messagehandler.py | 14 ++++++++------ telegram/ext/regexhandler.py | 14 ++++++++------ telegram/ext/stringcommandhandler.py | 14 ++++++++------ telegram/ext/stringregexhandler.py | 14 ++++++++------ telegram/ext/typehandler.py | 14 ++++++++------ 10 files changed, 80 insertions(+), 60 deletions(-) diff --git a/telegram/ext/callbackqueryhandler.py b/telegram/ext/callbackqueryhandler.py index 98cf04bdfa8..ac2b563fae5 100644 --- a/telegram/ext/callbackqueryhandler.py +++ b/telegram/ext/callbackqueryhandler.py @@ -31,12 +31,14 @@ class CallbackQueryHandler(Handler): callback (function): A function that takes ``bot, update`` as positional arguments. It will be called when the ``check_update`` has determined that an update should be processed by this handler. - pass_update_queue (optional[bool]): If the handler should be passed the - update queue as a keyword argument called ``update_queue``. It can - be used to insert updates. Default is ``False`` - pass_job_queue (optional[bool]): If the callback should be passed the job queue as a - keyword argument called ``job_queue``. It can be used to schedule new jobs. - Default is ``False`` + pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called + ``update_queue`` will be passed to the callback function. It will be the ``Queue`` + instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can + be used to insert updates. Default is ``False``. + pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called + ``job_queue`` will be passed to the callback function. It will be a ``JobQueue`` + instance created by the ``Updater`` which can be used to schedule new jobs. + Default is ``False``. """ def __init__(self, callback, pass_update_queue=False, pass_job_queue=False): diff --git a/telegram/ext/choseninlineresulthandler.py b/telegram/ext/choseninlineresulthandler.py index 6580c01435a..167dc954105 100644 --- a/telegram/ext/choseninlineresulthandler.py +++ b/telegram/ext/choseninlineresulthandler.py @@ -32,12 +32,14 @@ class ChosenInlineResultHandler(Handler): callback (function): A function that takes ``bot, update`` as positional arguments. It will be called when the ``check_update`` has determined that an update should be processed by this handler. - pass_update_queue (optional[bool]): If the handler should be passed the - update queue as a keyword argument called ``update_queue``. It can - be used to insert updates. Default is ``False`` - pass_job_queue (optional[bool]): If the callback should be passed the job queue as a - keyword argument called ``job_queue``. It can be used to schedule new jobs. - Default is ``False`` + pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called + ``update_queue`` will be passed to the callback function. It will be the ``Queue`` + instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can + be used to insert updates. Default is ``False``. + pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called + ``job_queue`` will be passed to the callback function. It will be a ``JobQueue`` + instance created by the ``Updater`` which can be used to schedule new jobs. + Default is ``False``. """ def __init__(self, callback, pass_update_queue=False, pass_job_queue=False): diff --git a/telegram/ext/commandhandler.py b/telegram/ext/commandhandler.py index d185b335fa2..a661a068b6c 100644 --- a/telegram/ext/commandhandler.py +++ b/telegram/ext/commandhandler.py @@ -38,12 +38,14 @@ class CommandHandler(Handler): arguments passed to the command as a keyword argument called ` ``args``. It will contain a list of strings, which is the text following the command split on spaces. Default is ``False`` - pass_update_queue (optional[bool]): If the handler should be passed the - update queue as a keyword argument called ``update_queue``. It can - be used to insert updates. Default is ``False`` - pass_job_queue (optional[bool]): If the callback should be passed the job queue as a - keyword argument called ``job_queue``. It can be used to schedule new jobs. - Default is ``False`` + pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called + ``update_queue`` will be passed to the callback function. It will be the ``Queue`` + instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can + be used to insert updates. Default is ``False``. + pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called + ``job_queue`` will be passed to the callback function. It will be a ``JobQueue`` + instance created by the ``Updater`` which can be used to schedule new jobs. + Default is ``False``. """ def __init__(self, diff --git a/telegram/ext/handler.py b/telegram/ext/handler.py index 1273b47ad05..8c0fb2e8b3f 100644 --- a/telegram/ext/handler.py +++ b/telegram/ext/handler.py @@ -31,12 +31,14 @@ class Handler(object): callback (function): A function that takes ``bot, update`` as positional arguments. It will be called when the ``check_update`` has determined that an update should be processed by this handler. - pass_update_queue (optional[bool]): If the callback should be passed - the update queue as a keyword argument called ``update_queue``. It - can be used to insert updates. Default is ``False`` - pass_job_queue (optional[bool]): If the callback should be passed the job queue as a - keyword argument called ``job_queue``. It can be used to schedule new jobs. - Default is ``False`` + pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called + ``update_queue`` will be passed to the callback function. It will be the ``Queue`` + instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can + be used to insert updates. Default is ``False``. + pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called + ``job_queue`` will be passed to the callback function. It will be a ``JobQueue`` + instance created by the ``Updater`` which can be used to schedule new jobs. + Default is ``False``. """ def __init__(self, callback, pass_update_queue=False, pass_job_queue=False): diff --git a/telegram/ext/inlinequeryhandler.py b/telegram/ext/inlinequeryhandler.py index 20f1dc5147c..64695105d8e 100644 --- a/telegram/ext/inlinequeryhandler.py +++ b/telegram/ext/inlinequeryhandler.py @@ -31,12 +31,14 @@ class InlineQueryHandler(Handler): callback (function): A function that takes ``bot, update`` as positional arguments. It will be called when the ``check_update`` has determined that an update should be processed by this handler. - pass_update_queue (optional[bool]): If the handler should be passed the - update queue as a keyword argument called ``update_queue``. It can - be used to insert updates. Default is ``False`` - pass_job_queue (optional[bool]): If the callback should be passed the job queue as a - keyword argument called ``job_queue``. It can be used to schedule new jobs. - Default is ``False`` + pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called + ``update_queue`` will be passed to the callback function. It will be the ``Queue`` + instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can + be used to insert updates. Default is ``False``. + pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called + ``job_queue`` will be passed to the callback function. It will be a ``JobQueue`` + instance created by the ``Updater`` which can be used to schedule new jobs. + Default is ``False``. """ def __init__(self, callback, pass_update_queue=False, pass_job_queue=False): diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index e6db95e4d5d..5844bd9c1b8 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -99,12 +99,14 @@ class MessageHandler(Handler): callback (function): A function that takes ``bot, update`` as positional arguments. It will be called when the ``check_update`` has determined that an update should be processed by this handler. - pass_update_queue (optional[bool]): If the handler should be passed the - update queue as a keyword argument called ``update_queue``. It can - be used to insert updates. Default is ``False`` - pass_job_queue (optional[bool]): If the callback should be passed the job queue as a - keyword argument called ``job_queue``. It can be used to schedule new jobs. - Default is ``False`` + pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called + ``update_queue`` will be passed to the callback function. It will be the ``Queue`` + instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can + be used to insert updates. Default is ``False``. + pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called + ``job_queue`` will be passed to the callback function. It will be a ``JobQueue`` + instance created by the ``Updater`` which can be used to schedule new jobs. + Default is ``False``. """ def __init__(self, filters, callback, pass_update_queue=False, pass_job_queue=False): diff --git a/telegram/ext/regexhandler.py b/telegram/ext/regexhandler.py index f699e2f720e..7ef01a108ca 100644 --- a/telegram/ext/regexhandler.py +++ b/telegram/ext/regexhandler.py @@ -45,12 +45,14 @@ class RegexHandler(Handler): pass_groupdict (optional[bool]): If the callback should be passed the result of ``re.match(pattern, text).groupdict()`` as a keyword argument called ``groupdict``. Default is ``False`` - pass_update_queue (optional[bool]): If the handler should be passed the - update queue as a keyword argument called ``update_queue``. It can - be used to insert updates. Default is ``False`` - pass_job_queue (optional[bool]): If the callback should be passed the job queue as a - keyword argument called ``job_queue``. It can be used to schedule new jobs. - Default is ``False`` + pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called + ``update_queue`` will be passed to the callback function. It will be the ``Queue`` + instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can + be used to insert updates. Default is ``False``. + pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called + ``job_queue`` will be passed to the callback function. It will be a ``JobQueue`` + instance created by the ``Updater`` which can be used to schedule new jobs. + Default is ``False``. """ def __init__(self, diff --git a/telegram/ext/stringcommandhandler.py b/telegram/ext/stringcommandhandler.py index 97043320e3a..47b31500e4e 100644 --- a/telegram/ext/stringcommandhandler.py +++ b/telegram/ext/stringcommandhandler.py @@ -36,12 +36,14 @@ class StringCommandHandler(Handler): arguments passed to the command as a keyword argument called ` ``args``. It will contain a list of strings, which is the text following the command split on spaces. Default is ``False`` - pass_update_queue (optional[bool]): If the handler should be passed the - update queue as a keyword argument called ``update_queue``. It can - be used to insert updates. Default is ``False`` - pass_job_queue (optional[bool]): If the callback should be passed the job queue as a - keyword argument called ``job_queue``. It can be used to schedule new jobs. - Default is ``False`` + pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called + ``update_queue`` will be passed to the callback function. It will be the ``Queue`` + instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can + be used to insert updates. Default is ``False``. + pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called + ``job_queue`` will be passed to the callback function. It will be a ``JobQueue`` + instance created by the ``Updater`` which can be used to schedule new jobs. + Default is ``False``. """ def __init__(self, diff --git a/telegram/ext/stringregexhandler.py b/telegram/ext/stringregexhandler.py index 88d7c1a95a2..c09489c7b27 100644 --- a/telegram/ext/stringregexhandler.py +++ b/telegram/ext/stringregexhandler.py @@ -44,12 +44,14 @@ class StringRegexHandler(Handler): pass_groupdict (optional[bool]): If the callback should be passed the result of ``re.match(pattern, update).groupdict()`` as a keyword argument called ``groupdict``. Default is ``False`` - pass_update_queue (optional[bool]): If the handler should be passed the - update queue as a keyword argument called ``update_queue``. It can - be used to insert updates. Default is ``False`` - pass_job_queue (optional[bool]): If the callback should be passed the job queue as a - keyword argument called ``job_queue``. It can be used to schedule new jobs. - Default is ``False`` + pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called + ``update_queue`` will be passed to the callback function. It will be the ``Queue`` + instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can + be used to insert updates. Default is ``False``. + pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called + ``job_queue`` will be passed to the callback function. It will be a ``JobQueue`` + instance created by the ``Updater`` which can be used to schedule new jobs. + Default is ``False``. """ def __init__(self, diff --git a/telegram/ext/typehandler.py b/telegram/ext/typehandler.py index bb836fe1972..7339b3b8966 100644 --- a/telegram/ext/typehandler.py +++ b/telegram/ext/typehandler.py @@ -34,12 +34,14 @@ class TypeHandler(Handler): has determined that an update should be processed by this handler. strict (optional[bool]): Use ``type`` instead of ``isinstance``. Default is ``False`` - pass_update_queue (optional[bool]): If the handler should be passed the - update queue as a keyword argument called ``update_queue``. It can - be used to insert updates. Default is ``False`` - pass_job_queue (optional[bool]): If the callback should be passed the job queue as a - keyword argument called ``job_queue``. It can be used to schedule new jobs. - Default is ``False`` + pass_update_queue (optional[bool]): If set to ``True``, a keyword argument called + ``update_queue`` will be passed to the callback function. It will be the ``Queue`` + instance used by the ``Updater`` and ``Dispatcher`` that contains new updates which can + be used to insert updates. Default is ``False``. + pass_job_queue (optional[bool]): If set to ``True``, a keyword argument called + ``job_queue`` will be passed to the callback function. It will be a ``JobQueue`` + instance created by the ``Updater`` which can be used to schedule new jobs. + Default is ``False``. """ def __init__(self, From b08d41d0ff9dc8981643f72075f4ad9b70094038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Tue, 31 May 2016 15:35:40 +0200 Subject: [PATCH 13/18] formatting --- telegram/ext/messagehandler.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index 485f05af784..36989e7d500 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -105,7 +105,12 @@ class MessageHandler(Handler): be used to insert updates. Default is ``False`` """ - def __init__(self, filters, callback, allow_edited=False, pass_update_queue=False, pass_job_queue=False): + def __init__(self, + filters, + callback, + allow_edited=False, + pass_update_queue=False, + pass_job_queue=False): super(MessageHandler, self).__init__(callback, pass_update_queue=pass_update_queue, pass_job_queue=pass_job_queue) From 02af1ea803a561b238de2c4da03a3e0a3f174650 Mon Sep 17 00:00:00 2001 From: Noam Meltzer Date: Tue, 21 Jun 2016 22:20:57 +0300 Subject: [PATCH 14/18] jobqueue: cosmetic fixes --- telegram/ext/jobqueue.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 9572f0eea1e..5252c950fc6 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -25,8 +25,7 @@ class JobQueue(object): - """ - This class allows you to periodically perform tasks with the bot. + """This class allows you to periodically perform tasks with the bot. Attributes: queue (PriorityQueue): @@ -47,8 +46,7 @@ def __init__(self, bot): self._running = False def put(self, job, next_t=None, prevent_autostart=False): - """ - Queue a new job. If the JobQueue is not running, it will be started. + """Queue a new job. If the JobQueue is not running, it will be started. Args: job (Job): The ``Job`` instance representing the new job @@ -56,8 +54,8 @@ def put(self, job, next_t=None, prevent_autostart=False): Defaults to ``job.interval`` prevent_autostart (Optional[bool]): If ``True``, the job queue will not be started automatically if it is not running. Defaults to ``False`` - """ + """ job.job_queue = self if next_t is None: @@ -66,7 +64,7 @@ def put(self, job, next_t=None, prevent_autostart=False): now = time.time() next_t += now - self.logger.debug('Putting a %s with t=%f' % (job.name, next_t)) + self.logger.debug('Putting job %s with t=%f', job.name, next_t) self.queue.put((next_t, job)) # Wake up the loop if this job should be executed next @@ -80,35 +78,36 @@ def put(self, job, next_t=None, prevent_autostart=False): def tick(self): """ - Run all jobs that are due and re-enqueue them with their interval + Run all jobs that are due and re-enqueue them with their interval. + """ now = time.time() - self.logger.debug('Ticking jobs with t=%f' % now) + self.logger.debug('Ticking jobs with t=%f', now) while not self.queue.empty(): t, job = self.queue.queue[0] - self.logger.debug('Peeked at %s with t=%f' % (job.name, t)) + self.logger.debug('Peeked at %s with t=%f', job.name, t) if t <= now: self.queue.get() if job._remove.is_set(): - self.logger.debug('Removing job %s' % job.name) + self.logger.debug('Removing job %s', job.name) continue elif job.enabled: - self.logger.debug('Running job %s' % job.name) + self.logger.debug('Running job %s', job.name) try: job.run(self.bot) except: self.logger.exception( - 'An uncaught error was raised while executing job %s' % job.name) + 'An uncaught error was raised while executing job %s', job.name) else: - self.logger.debug('Skipping disabled job %s' % job.name) + self.logger.debug('Skipping disabled job %s', job.name) if job.repeat: self.put(job) @@ -127,6 +126,7 @@ def tick(self): def start(self): """ Starts the job_queue thread. + """ self.__lock.acquire() @@ -135,7 +135,7 @@ def start(self): self.__lock.release() job_queue_thread = Thread(target=self._start, name="job_queue") job_queue_thread.start() - self.logger.debug('Job Queue thread started') + self.logger.debug('%s thread started', self.__class__.__name__) else: self.__lock.release() @@ -144,8 +144,8 @@ def _start(self): """ Thread target of thread ``job_queue``. Runs in background and performs ticks on the job queue. - """ + """ while self._running: self.__tick.wait(self._next_peek and self._next_peek - time.time()) @@ -156,7 +156,7 @@ def _start(self): self.tick() - self.logger.debug('Job Queue thread stopped') + self.logger.debug('%s thread stopped', self.__class__.__name__) def stop(self): """ @@ -215,6 +215,7 @@ def schedule_removal(self): """ Schedules this job for removal from the ``JobQueue``. It will be removed without executing its callback function again. + """ self._remove.set() From f65b6911ea61838e8ced486417453cb9bc26687b Mon Sep 17 00:00:00 2001 From: Noam Meltzer Date: Tue, 21 Jun 2016 22:25:15 +0300 Subject: [PATCH 15/18] JobQueue: use class name for the logger name --- telegram/ext/jobqueue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 5252c950fc6..9ee452cb77e 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -39,7 +39,7 @@ class JobQueue(object): def __init__(self, bot): self.queue = PriorityQueue() self.bot = bot - self.logger = logging.getLogger(__name__) + self.logger = logging.getLogger(self.__class__.__name__) self.__lock = Lock() self.__tick = Event() self._next_peek = None From 35872d7a8b718a4247ba6c6de926ea87b8ee8b26 Mon Sep 17 00:00:00 2001 From: Noam Meltzer Date: Wed, 22 Jun 2016 01:46:29 +0300 Subject: [PATCH 16/18] test_jobqueue: fix test_jobs_tuple() this test was based on timing and assumed that the JobQueue did not have time to start processing the queue before checking the assert. what we really should do is make sure JobQueue does not process anything --- tests/test_jobqueue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_jobqueue.py b/tests/test_jobqueue.py index 25466867062..bdfb602fb0d 100644 --- a/tests/test_jobqueue.py +++ b/tests/test_jobqueue.py @@ -152,7 +152,7 @@ def test_error(self): self.assertEqual(2, self.result) def test_jobs_tuple(self): - + self.jq.stop() jobs = tuple(Job(self.job1, t) for t in range(5, 25)) for job in jobs: From 1e0ebe89f3c2e658fb6ec60ea1ebeed40aadcb1d Mon Sep 17 00:00:00 2001 From: Noam Meltzer Date: Wed, 22 Jun 2016 01:24:59 +0300 Subject: [PATCH 17/18] JobQueue: minimize the amount of places changing self.__tick state - start the jobqueue (by default) during __init__() instead of during put() - protect self._next_peek and self.__tick with a Lock - rename self._start() to self._main_loop() - stop() is now blocking until the event loop thread exits --- telegram/ext/jobqueue.py | 129 +++++++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 54 deletions(-) diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 9ee452cb77e..8169be37623 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -21,7 +21,7 @@ import logging import time from threading import Thread, Lock, Event -from queue import PriorityQueue +from queue import PriorityQueue, Empty class JobQueue(object): @@ -30,30 +30,38 @@ class JobQueue(object): Attributes: queue (PriorityQueue): bot (Bot): + prevent_autostart (Optional[bool]): If ``True``, the job queue will not be started + automatically. Defaults to ``False`` Args: bot (Bot): The bot instance that should be passed to the jobs """ - def __init__(self, bot): + def __init__(self, bot, prevent_autostart=False): self.queue = PriorityQueue() self.bot = bot self.logger = logging.getLogger(self.__class__.__name__) - self.__lock = Lock() + self.__start_lock = Lock() + self.__next_peek_lock = Lock() # to protect self._next_peek & self.__tick self.__tick = Event() + self.__thread = None + """:type: Thread""" self._next_peek = None + """:type: float""" self._running = False - def put(self, job, next_t=None, prevent_autostart=False): + if not prevent_autostart: + self.logger.debug('Auto-starting %s', self.__class__.__name__) + self.start() + + def put(self, job, next_t=None): """Queue a new job. If the JobQueue is not running, it will be started. Args: job (Job): The ``Job`` instance representing the new job next_t (Optional[float]): Time in seconds in which the job should be executed first. Defaults to ``job.interval`` - prevent_autostart (Optional[bool]): If ``True``, the job queue will not be started - automatically if it is not running. Defaults to ``False`` """ job.job_queue = self @@ -68,13 +76,18 @@ def put(self, job, next_t=None, prevent_autostart=False): self.queue.put((next_t, job)) # Wake up the loop if this job should be executed next - if not self._next_peek or self._next_peek > next_t: - self._next_peek = next_t - self.__tick.set() + self._set_next_peek(next_t) - if not self._running and not prevent_autostart: - self.logger.debug('Auto-starting JobQueue') - self.start() + def _set_next_peek(self, t): + """ + Set next peek if not defined or `t` is before next peek. + In case the next peek was set, also trigger the `self.__tick` event. + + """ + with self.__next_peek_lock: + if not self._next_peek or self._next_peek > t: + self._next_peek = t + self.__tick.set() def tick(self): """ @@ -85,74 +98,80 @@ def tick(self): self.logger.debug('Ticking jobs with t=%f', now) - while not self.queue.empty(): - t, job = self.queue.queue[0] - self.logger.debug('Peeked at %s with t=%f', job.name, t) - - if t <= now: - self.queue.get() - - if job._remove.is_set(): - self.logger.debug('Removing job %s', job.name) - continue - - elif job.enabled: - self.logger.debug('Running job %s', job.name) + while True: + try: + t, job = self.queue.get(False) + except Empty: + break - try: - job.run(self.bot) + self.logger.debug('Peeked at %s with t=%f', job.name, t) - except: - self.logger.exception( - 'An uncaught error was raised while executing job %s', job.name) + if t > now: + # we can get here in two conditions: + # 1. At the second or later pass of the while loop, after we've already processed + # the job(s) we were supposed to at this time. + # 2. At the first iteration of the loop only if `self.put()` had triggered + # `self.__tick` because `self._next_peek` wasn't set + self.logger.debug("Next task isn't due yet. Finished!") + self.queue.put((t, job)) + self._set_next_peek(t) + break + + if job._remove.is_set(): + self.logger.debug('Removing job %s', job.name) + continue - else: - self.logger.debug('Skipping disabled job %s', job.name) + if job.enabled: + self.logger.debug('Running job %s', job.name) - if job.repeat: - self.put(job) + try: + job.run(self.bot) - continue + except: + self.logger.exception( + 'An uncaught error was raised while executing job %s', job.name) - self.logger.debug('Next task isn\'t due yet. Finished!') - self._next_peek = t - break - - else: - self._next_peek = None + else: + self.logger.debug('Skipping disabled job %s', job.name) - self.__tick.clear() + if job.repeat: + self.put(job) def start(self): """ Starts the job_queue thread. """ - self.__lock.acquire() + self.__start_lock.acquire() if not self._running: self._running = True - self.__lock.release() - job_queue_thread = Thread(target=self._start, name="job_queue") - job_queue_thread.start() + self.__start_lock.release() + self.__thread = Thread(target=self._main_loop, name="job_queue") + self.__thread.start() self.logger.debug('%s thread started', self.__class__.__name__) else: - self.__lock.release() + self.__start_lock.release() - def _start(self): + def _main_loop(self): """ Thread target of thread ``job_queue``. Runs in background and performs ticks on the job queue. """ while self._running: - self.__tick.wait(self._next_peek and self._next_peek - time.time()) - - # If we were woken up by set(), wait with the new timeout - if self.__tick.is_set(): + # self._next_peek may be (re)scheduled during self.tick() or self.put() + with self.__next_peek_lock: + tmout = self._next_peek and self._next_peek - time.time() + self._next_peek = None self.__tick.clear() - continue + + self.__tick.wait(tmout) + + # If we were woken up by self.stop(), just bail out + if not self._running: + break self.tick() @@ -162,10 +181,12 @@ def stop(self): """ Stops the thread """ - with self.__lock: + with self.__start_lock: self._running = False self.__tick.set() + if self.__thread is not None: + self.__thread.join() def jobs(self): """Returns a tuple of all jobs that are currently in the ``JobQueue``""" From 31073101a3a2ce126d7469dbd42c9fefddff45f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannes=20H=C3=B6ke?= Date: Fri, 24 Jun 2016 19:22:49 +0200 Subject: [PATCH 18/18] yapf --- telegram/ext/jobqueue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 8169be37623..60fe921b988 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -128,8 +128,8 @@ def tick(self): job.run(self.bot) except: - self.logger.exception( - 'An uncaught error was raised while executing job %s', job.name) + self.logger.exception('An uncaught error was raised while executing job %s', + job.name) else: self.logger.debug('Skipping disabled job %s', job.name) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy