2222import time
2323import warnings
2424import datetime
25+ import weakref
2526from numbers import Number
2627from threading import Thread , Lock , Event
2728from queue import PriorityQueue , Empty
@@ -82,6 +83,8 @@ def put(self, job, next_t=None):
8283 """
8384 warnings .warn ("'JobQueue.put' is being deprecated, use 'JobQueue.one_time_job', "
8485 "'JobQueue.repeating_job' or 'JobQueue.daily_job' instead" )
86+ if job .job_queue is None :
87+ job .job_queue = self
8588 self ._put (job , next_t = next_t )
8689
8790 def _put (self , job , next_t = None , last_t = None ):
@@ -92,37 +95,39 @@ def _put(self, job, next_t=None, last_t=None):
9295 next_t (Optional[int, float, datetime.timedelta, datetime.datetime, datetime.time]):
9396 Time in or at which the job should run for the first time. This parameter will be
9497 interpreted depending on its type.
95- ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
96- should run.
97- ``datetime.timedelta`` will be interpreted as "time from now" in which the job
98- should run.
99- ``datetime.datetime`` will be interpreted as a specific date and time at which the
100- job should run.
101- ``datetime.time`` will be interpreted as a specific time at which the job should
102- run. This could be either today or, if the time has already passed, tomorrow.
98+
99+ * ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
100+ should run.
101+ * ``datetime.timedelta`` will be interpreted as "time from now" in which the job
102+ should run.
103+ * ``datetime.datetime`` will be interpreted as a specific date and time at which
104+ the job should run.
105+ * ``datetime.time`` will be interpreted as a specific time of day at which the job
106+ should run. This could be either today or, if the time has already passed,
107+ tomorrow.
103108 last_t (Optional[float]): Timestamp of the time when ``job`` was scheduled for in the
104- last ``put`` call. If provided, it will used to calculate the next timestamp more
105- accurately by accounting for the execution time of the job (and possibly others).
106- By default, the current timestamp is used.
107- """
108- if job .job_queue is not self :
109- job .job_queue = self
109+ last ``put`` call. If provided, it will be used to calculate the next timestamp
110+ more accurately by accounting for the execution time of the job (and possibly
111+ others). If None, `now` will be assumed.
110112
113+ """
111114 if next_t is None :
112115 next_t = job .interval
116+ if next_t is None :
117+ raise ValueError ('next_t is None' )
113118
114119 if isinstance (next_t , datetime .datetime ):
115- next_t = next_t - datetime .datetime .now ()
120+ next_t = ( next_t - datetime .datetime .now ()). total_seconds ()
116121
117122 elif isinstance (next_t , datetime .time ):
118123 next_datetime = datetime .datetime .combine (datetime .date .today (), next_t )
119124
120125 if datetime .datetime .now ().time () > next_t :
121126 next_datetime += datetime .timedelta (days = 1 )
122127
123- next_t = next_datetime - datetime .datetime .now ()
128+ next_t = ( next_datetime - datetime .datetime .now ()). total_seconds ()
124129
125- if isinstance (next_t , datetime .timedelta ):
130+ elif isinstance (next_t , datetime .timedelta ):
126131 next_t = next_t .total_seconds ()
127132
128133 next_t += last_t or time .time ()
@@ -145,22 +150,26 @@ def run_once(self, callback, when, context=None, name=None):
145150 when (int, float, datetime.timedelta, datetime.datetime, datetime.time):
146151 Time in or at which the job should run. This parameter will be interpreted
147152 depending on its type.
148- ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
149- should run.
150- ``datetime.timedelta`` will be interpreted as "time from now" in which the job
151- should run.
152- ``datetime.datetime`` will be interpreted as a specific date and time at which the
153- job should run.
154- ``datetime.time`` will be interpreted as a specific time at which the job should
155- run. This could be either today or, if the time has already passed, tomorrow.
153+
154+ * ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
155+ should run.
156+ * ``datetime.timedelta`` will be interpreted as "time from now" in which the job
157+ should run.
158+ * ``datetime.datetime`` will be interpreted as a specific date and time at which
159+ the job should run.
160+ * ``datetime.time`` will be interpreted as a specific time of day at which the job
161+ should run. This could be either today or, if the time has already passed,
162+ tomorrow.
163+
156164 context (Optional[object]): Additional data needed for the callback function. Can be
157165 accessed through ``job.context`` in the callback. Defaults to ``None``
158166 name (Optional[str]): The name of the new job. Defaults to ``callback.__name__``
159167
160168 Returns:
161169 Job: The new ``Job`` instance that has been added to the job queue.
170+
162171 """
163- job = Job (callback , repeat = False , context = context , name = name )
172+ job = Job (callback , repeat = False , context = context , name = name , job_queue = self )
164173 self ._put (job , next_t = when )
165174 return job
166175
@@ -175,25 +184,32 @@ def run_repeating(self, callback, interval, first=None, context=None, name=None)
175184 interval (int, float, datetime.timedelta): The interval in which the job will run.
176185 If it is an ``int`` or a ``float``, it will be interpreted as seconds.
177186 first (int, float, datetime.timedelta, datetime.datetime, datetime.time):
178- Time in or at which the job should run for the first time. This parameter will be
179- interpreted depending on its type.
180- ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
181- should run.
182- ``datetime.timedelta`` will be interpreted as "time from now" in which the job
183- should run.
184- ``datetime.datetime`` will be interpreted as a specific date and time at which the
185- job should run.
186- ``datetime.time`` will be interpreted as a specific time at which the job should
187- run. This could be either today or, if the time has already passed, tomorrow.
187+
188+ * ``int`` or ``float`` will be interpreted as "seconds from now" in which the job
189+ should run.
190+ * ``datetime.timedelta`` will be interpreted as "time from now" in which the job
191+ should run.
192+ * ``datetime.datetime`` will be interpreted as a specific date and time at which
193+ the job should run.
194+ * ``datetime.time`` will be interpreted as a specific time of day at which the job
195+ should run. This could be either today or, if the time has already passed,
196+ tomorrow.
197+
188198 Defaults to ``interval``
189199 context (Optional[object]): Additional data needed for the callback function. Can be
190200 accessed through ``job.context`` in the callback. Defaults to ``None``
191201 name (Optional[str]): The name of the new job. Defaults to ``callback.__name__``
192202
193203 Returns:
194204 Job: The new ``Job`` instance that has been added to the job queue.
205+
195206 """
196- job = Job (callback , interval = interval , repeat = True , context = context , name = name )
207+ job = Job (callback ,
208+ interval = interval ,
209+ repeat = True ,
210+ context = context ,
211+ name = name ,
212+ job_queue = self )
197213 self ._put (job , next_t = first )
198214 return job
199215
@@ -213,13 +229,15 @@ def run_daily(self, callback, time, days=Days.EVERY_DAY, context=None, name=None
213229
214230 Returns:
215231 Job: The new ``Job`` instance that has been added to the job queue.
232+
216233 """
217234 job = Job (callback ,
218235 interval = datetime .timedelta (days = 1 ),
219236 repeat = True ,
220237 days = days ,
221238 context = context ,
222- name = name )
239+ name = name ,
240+ job_queue = self )
223241 self ._put (job , next_t = time )
224242 return job
225243
@@ -245,7 +263,6 @@ def tick(self):
245263 while True :
246264 try :
247265 t , job = self .queue .get (False )
248-
249266 except Empty :
250267 break
251268
@@ -262,9 +279,8 @@ def tick(self):
262279 self ._set_next_peek (t )
263280 break
264281
265- if job ._remove . is_set ():
282+ if job .is_removed ():
266283 self .logger .debug ('Removing job %s' , job .name )
267- job .job_queue = None
268284 continue
269285
270286 if job .enabled :
@@ -277,16 +293,13 @@ def tick(self):
277293 except :
278294 self .logger .exception ('An uncaught error was raised while executing job %s' ,
279295 job .name )
280-
281296 else :
282297 self .logger .debug ('Skipping disabled job %s' , job .name )
283298
284- if job .repeat :
299+ if job .repeat and not job . is_removed () :
285300 self ._put (job , last_t = t )
286-
287301 else :
288- self .logger .debug ('Dropping non-repeating job %s' , job .name )
289- job .job_queue = None
302+ self .logger .debug ('Dropping non-repeating or removed job %s' , job .name )
290303
291304 def start (self ):
292305 """
@@ -301,7 +314,6 @@ def start(self):
301314 self .__thread = Thread (target = self ._main_loop , name = "job_queue" )
302315 self .__thread .start ()
303316 self .logger .debug ('%s thread started' , self .__class__ .__name__ )
304-
305317 else :
306318 self .__start_lock .release ()
307319
@@ -314,7 +326,7 @@ def _main_loop(self):
314326 while self ._running :
315327 # self._next_peek may be (re)scheduled during self.tick() or self.put()
316328 with self .__next_peek_lock :
317- tmout = self ._next_peek and self . _next_peek - time .time ()
329+ tmout = self ._next_peek - time .time () if self . _next_peek else None
318330 self ._next_peek = None
319331 self .__tick .clear ()
320332
@@ -372,6 +384,9 @@ class Job(object):
372384 days (Optional[tuple[int]]): Defines on which days of the week the job should run.
373385 Defaults to ``Days.EVERY_DAY``
374386 name (Optional[str]): The name of this job. Defaults to ``callback.__name__``
387+ job_queue (Optional[class:`telegram.ext.JobQueue`]): The ``JobQueue`` this job belongs to.
388+ Only optional for backward compatibility with ``JobQueue.put()``.
389+
375390 """
376391
377392 def __init__ (self ,
@@ -380,7 +395,8 @@ def __init__(self,
380395 repeat = True ,
381396 context = None ,
382397 days = Days .EVERY_DAY ,
383- name = None ):
398+ name = None ,
399+ job_queue = None ):
384400
385401 self .callback = callback
386402 self .context = context
@@ -394,7 +410,7 @@ def __init__(self,
394410 self ._days = None
395411 self .days = days
396412
397- self ._job_queue = None
413+ self ._job_queue = weakref . proxy ( job_queue ) if job_queue is not None else None
398414
399415 self ._remove = Event ()
400416 self ._enabled = Event ()
@@ -411,6 +427,9 @@ def schedule_removal(self):
411427 """
412428 self ._remove .set ()
413429
430+ def is_removed (self ):
431+ return self ._remove .is_set ()
432+
414433 @property
415434 def enabled (self ):
416435 return self ._enabled .is_set ()
@@ -431,8 +450,7 @@ def interval(self, interval):
431450 if interval is None and self .repeat :
432451 raise ValueError ("The 'interval' can not be 'None' when 'repeat' is set to 'True'" )
433452
434- if not (interval is None or isinstance (interval , Number ) or
435- isinstance (interval , datetime .timedelta )):
453+ if not (interval is None or isinstance (interval , (Number , datetime .timedelta ))):
436454 raise ValueError ("The 'interval' must be of type 'datetime.timedelta',"
437455 " 'int' or 'float'" )
438456
@@ -480,10 +498,11 @@ def job_queue(self):
480498
481499 @job_queue .setter
482500 def job_queue (self , job_queue ):
483- if not self ._job_queue or not job_queue :
484- self ._job_queue = job_queue
501+ # Property setter for backward compatibility with JobQueue.put()
502+ if not self ._job_queue :
503+ self ._job_queue = weakref .proxy (job_queue )
485504 else :
486- raise ValueError ("The 'job_queue' attribute can only be set once." )
505+ raise RuntimeError ("The 'job_queue' attribute can only be set once." )
487506
488507 def __lt__ (self , other ):
489508 return False
0 commit comments