diff --git a/.gitignore b/.gitignore index b0b1e45cc..6328e7612 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,6 @@ telegram.webp # original files from merges *.orig + +# Exclude .exrc file for Vim +.exrc diff --git a/AUTHORS.rst b/AUTHORS.rst index 01274656a..c146e5be1 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -32,6 +32,7 @@ The following wonderful people contributed directly or indirectly to this projec - `Shelomentsev D `_ - `sooyhwang `_ - `Valentijn `_ +- `voider1 `_ - `wjt `_ Please add yourself here alphabetically when you submit your first pull request. diff --git a/telegram/ext/jobqueue.py b/telegram/ext/jobqueue.py index 796e278bd..ce6c4a6b1 100644 --- a/telegram/ext/jobqueue.py +++ b/telegram/ext/jobqueue.py @@ -21,10 +21,17 @@ import logging import time import warnings +import datetime +from numbers import Number from threading import Thread, Lock, Event from queue import PriorityQueue, Empty +class Days(object): + MON, TUE, WED, THU, FRI, SAT, SUN = range(7) + EVERY_DAY = tuple(range(7)) + + class JobQueue(object): """This class allows you to periodically perform tasks with the bot. @@ -61,14 +68,25 @@ class JobQueue(object): Args: job (telegram.ext.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`` + next_t (Optional[int, float, datetime.timedelta]): Time in which the job + should be executed first. Defaults to ``job.interval``. ``int`` and ``float`` + will be interpreted as seconds. """ job.job_queue = self if next_t is None: - next_t = job.interval + interval = job.interval + + if isinstance(interval, Number): + next_t = interval + elif isinstance(interval, datetime.timedelta): + next_t = interval.total_seconds() + else: + raise ValueError("The interval argument should be of type datetime.timedelta," + " int or float") + elif isinstance(next_t, datetime.timedelta): + next_t = next_t.total_second() now = time.time() next_t += now @@ -123,11 +141,11 @@ class JobQueue(object): continue if job.enabled: - self.logger.debug('Running job %s', job.name) - try: - job.run(self.bot) - + current_week_day = datetime.datetime.now().weekday() + if any(day == current_week_day for day in job.days): + self.logger.debug('Running job %s', job.name) + job.run(self.bot) except: self.logger.exception('An uncaught error was raised while executing job %s', job.name) @@ -200,6 +218,7 @@ class Job(object): Attributes: callback (function): interval (float): + days: (tuple) repeat (bool): name (str): enabled (bool): Boolean property that decides if this job is currently active @@ -208,22 +227,34 @@ class Job(object): 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. + interval ([int, float, datetime.timedelta]): The interval in which the job will execute its + callback function. ``int`` and ``float`` will be interpreted as seconds. repeat (Optional[bool]): If this job should be periodically execute its callback function (``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`` + days (Tuple): Defines on which days the job should be ran. """ job_queue = None - def __init__(self, callback, interval, repeat=True, context=None): + def __init__(self, callback, interval, repeat=True, context=None, days=Days.EVERY_DAY): self.callback = callback self.interval = interval self.repeat = repeat self.context = context + if not isinstance(days, tuple): + raise ValueError("The 'days argument should be of type 'tuple'") + + if not all(isinstance(day, int) for day in days): + raise ValueError("The elements of the 'days' argument should be of type 'int'") + + if not all(day >= 0 and day <= 6 for day in days): + raise ValueError("The elements of the 'days' argument should be from 0 up to and " + "including 6") + + self.days = days self.name = callback.__name__ self._remove = Event() self._enabled = Event() diff --git a/tests/test_jobqueue.py b/tests/test_jobqueue.py index 656915e18..a208089ea 100644 --- a/tests/test_jobqueue.py +++ b/tests/test_jobqueue.py @@ -23,6 +23,9 @@ This module contains an object that represents Tests for JobQueue import logging import sys import unittest +import datetime +import time +from math import ceil from time import sleep from tests.test_updater import MockBot @@ -53,11 +56,15 @@ class JobQueueTest(BaseTest, unittest.TestCase): self.jq = JobQueue(MockBot('jobqueue_test')) self.jq.start() self.result = 0 + self.job_time = 0 def tearDown(self): if self.jq is not None: self.jq.stop() + def getSeconds(self): + return int(ceil(time.time())) + def job1(self, bot, job): self.result += 1 @@ -71,6 +78,9 @@ class JobQueueTest(BaseTest, unittest.TestCase): def job4(self, bot, job): self.result += job.context + def job5(self, bot, job): + self.job_time = self.getSeconds() + def test_basic(self): self.jq.put(Job(self.job1, 0.1)) sleep(1.5) @@ -169,6 +179,25 @@ class JobQueueTest(BaseTest, unittest.TestCase): finally: u.stop() + def test_time_unit_int(self): + # Testing seconds in int + seconds_interval = 5 + expected_time = self.getSeconds() + seconds_interval + + self.jq.put(Job(self.job5, seconds_interval, repeat=False)) + sleep(6) + self.assertEqual(self.job_time, expected_time) + + def test_time_unit_dt_time(self): + # Testing seconds, minutes and hours as datetime.timedelta object + # This is sufficient to test that it actually works. + interval = datetime.timedelta(seconds=5) + expected_time = self.getSeconds() + interval.total_seconds() + + self.jq.put(Job(self.job5, interval, repeat=False)) + sleep(6) + self.assertEqual(self.job_time, expected_time) + if __name__ == '__main__': unittest.main()