Skip to content

Commit 68e87db

Browse files
Wesley Gahrjh0ker
authored andcommitted
Job queue time units (python-telegram-bot#452)
* Adding timeunit and day support to the jobqueue * Adding tests * Changed the file permission back to 644. * Changed AssertEqual argument order to (actual, expectd). * Removed the TimeUnit enum and unit param, instead use datetime.time for interval. * Removing the TimeUnits enum and unit param in favour of optionally using a datetime.time as the interval. * Removing the TimeUnits enumeration, forgot the remove it in the last one. * Removed some old docstrings refering to the TimeUnits enum. * Removed the old TimeUnits import. * Adding some error handling for the 'days' argument (only a 'tuple' with 'Days') * Writing the error message directly in the exception. * Moving a debug statement wrongfully saying a job would be running on days it wouldn't. * Writing error messages directly in the exceptions instead of making an extra variable. * Replacing datetime.time in favour of datetime.timedelta because of the get_seconds() method. * Adding error handling for the method . * Splitting the tests up in multiple ones, no float test because I haven't found a reliable way to test it. * Excluding .exrc file. * Removing \ at EOF of ValueError. * Replacing Enums with plain new-style classes. * Using numbers.number to check for ints/floats instead of seperate int/float checks. * Fixing typo, number -> Number. * Changed lower_case Days attributes to UPPER_CASE. * Different formatting for Days class, removed the get_days function in favour of a tuple. * Removed redundant function get_days. * Edited the docstring for next_t to also take datetime.timedelta. * Removed for-loop in favour of any(). * Changed docstring for interval. * Removed debug print. * Changing some docstrings. * Changing some docstrings (again).
1 parent a7bfb0c commit 68e87db

File tree

4 files changed

+74
-10
lines changed

4 files changed

+74
-10
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,6 @@ telegram.webp
7171

7272
# original files from merges
7373
*.orig
74+
75+
# Exclude .exrc file for Vim
76+
.exrc

AUTHORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ The following wonderful people contributed directly or indirectly to this projec
3232
- `Shelomentsev D <https://github.com/shelomentsevd>`_
3333
- `sooyhwang <https://github.com/sooyhwang>`_
3434
- `Valentijn <https://github.com/Faalentijn>`_
35+
- `voider1 <https://github.com/voider1>`_
3536
- `wjt <https://github.com/wjt>`_
3637

3738
Please add yourself here alphabetically when you submit your first pull request.

telegram/ext/jobqueue.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,17 @@
2121
import logging
2222
import time
2323
import warnings
24+
import datetime
25+
from numbers import Number
2426
from threading import Thread, Lock, Event
2527
from queue import PriorityQueue, Empty
2628

2729

30+
class Days(object):
31+
MON, TUE, WED, THU, FRI, SAT, SUN = range(7)
32+
EVERY_DAY = tuple(range(7))
33+
34+
2835
class JobQueue(object):
2936
"""This class allows you to periodically perform tasks with the bot.
3037
@@ -61,14 +68,25 @@ def put(self, job, next_t=None):
6168
6269
Args:
6370
job (telegram.ext.Job): The ``Job`` instance representing the new job
64-
next_t (Optional[float]): Time in seconds in which the job should be executed first.
65-
Defaults to ``job.interval``
71+
next_t (Optional[int, float, datetime.timedelta]): Time in which the job
72+
should be executed first. Defaults to ``job.interval``. ``int`` and ``float``
73+
will be interpreted as seconds.
6674
6775
"""
6876
job.job_queue = self
6977

7078
if next_t is None:
71-
next_t = job.interval
79+
interval = job.interval
80+
81+
if isinstance(interval, Number):
82+
next_t = interval
83+
elif isinstance(interval, datetime.timedelta):
84+
next_t = interval.total_seconds()
85+
else:
86+
raise ValueError("The interval argument should be of type datetime.timedelta,"
87+
" int or float")
88+
elif isinstance(next_t, datetime.timedelta):
89+
next_t = next_t.total_second()
7290

7391
now = time.time()
7492
next_t += now
@@ -123,11 +141,11 @@ def tick(self):
123141
continue
124142

125143
if job.enabled:
126-
self.logger.debug('Running job %s', job.name)
127-
128144
try:
129-
job.run(self.bot)
130-
145+
current_week_day = datetime.datetime.now().weekday()
146+
if any(day == current_week_day for day in job.days):
147+
self.logger.debug('Running job %s', job.name)
148+
job.run(self.bot)
131149
except:
132150
self.logger.exception('An uncaught error was raised while executing job %s',
133151
job.name)
@@ -200,6 +218,7 @@ class Job(object):
200218
Attributes:
201219
callback (function):
202220
interval (float):
221+
days: (tuple)
203222
repeat (bool):
204223
name (str):
205224
enabled (bool): Boolean property that decides if this job is currently active
@@ -208,22 +227,34 @@ class Job(object):
208227
callback (function): The callback function that should be executed by the Job. It should
209228
take two parameters ``bot`` and ``job``, where ``job`` is the ``Job`` instance. It
210229
can be used to terminate the job or modify its interval.
211-
interval (float): The interval in which this job should execute its callback function in
212-
seconds.
230+
interval ([int, float, datetime.timedelta]): The interval in which the job will execute its
231+
callback function. ``int`` and ``float`` will be interpreted as seconds.
213232
repeat (Optional[bool]): If this job should be periodically execute its callback function
214233
(``True``) or only once (``False``). Defaults to ``True``
215234
context (Optional[object]): Additional data needed for the callback function. Can be
216235
accessed through ``job.context`` in the callback. Defaults to ``None``
236+
days (Tuple): Defines on which days the job should be ran.
217237
218238
"""
219239
job_queue = None
220240

221-
def __init__(self, callback, interval, repeat=True, context=None):
241+
def __init__(self, callback, interval, repeat=True, context=None, days=Days.EVERY_DAY):
222242
self.callback = callback
223243
self.interval = interval
224244
self.repeat = repeat
225245
self.context = context
226246

247+
if not isinstance(days, tuple):
248+
raise ValueError("The 'days argument should be of type 'tuple'")
249+
250+
if not all(isinstance(day, int) for day in days):
251+
raise ValueError("The elements of the 'days' argument should be of type 'int'")
252+
253+
if not all(day >= 0 and day <= 6 for day in days):
254+
raise ValueError("The elements of the 'days' argument should be from 0 up to and "
255+
"including 6")
256+
257+
self.days = days
227258
self.name = callback.__name__
228259
self._remove = Event()
229260
self._enabled = Event()

tests/test_jobqueue.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
import logging
2424
import sys
2525
import unittest
26+
import datetime
27+
import time
28+
from math import ceil
2629
from time import sleep
2730

2831
from tests.test_updater import MockBot
@@ -53,11 +56,15 @@ def setUp(self):
5356
self.jq = JobQueue(MockBot('jobqueue_test'))
5457
self.jq.start()
5558
self.result = 0
59+
self.job_time = 0
5660

5761
def tearDown(self):
5862
if self.jq is not None:
5963
self.jq.stop()
6064

65+
def getSeconds(self):
66+
return int(ceil(time.time()))
67+
6168
def job1(self, bot, job):
6269
self.result += 1
6370

@@ -71,6 +78,9 @@ def job3(self, bot, job):
7178
def job4(self, bot, job):
7279
self.result += job.context
7380

81+
def job5(self, bot, job):
82+
self.job_time = self.getSeconds()
83+
7484
def test_basic(self):
7585
self.jq.put(Job(self.job1, 0.1))
7686
sleep(1.5)
@@ -169,6 +179,25 @@ def test_inUpdater(self):
169179
finally:
170180
u.stop()
171181

182+
def test_time_unit_int(self):
183+
# Testing seconds in int
184+
seconds_interval = 5
185+
expected_time = self.getSeconds() + seconds_interval
186+
187+
self.jq.put(Job(self.job5, seconds_interval, repeat=False))
188+
sleep(6)
189+
self.assertEqual(self.job_time, expected_time)
190+
191+
def test_time_unit_dt_time(self):
192+
# Testing seconds, minutes and hours as datetime.timedelta object
193+
# This is sufficient to test that it actually works.
194+
interval = datetime.timedelta(seconds=5)
195+
expected_time = self.getSeconds() + interval.total_seconds()
196+
197+
self.jq.put(Job(self.job5, interval, repeat=False))
198+
sleep(6)
199+
self.assertEqual(self.job_time, expected_time)
200+
172201

173202
if __name__ == '__main__':
174203
unittest.main()

0 commit comments

Comments
 (0)