From ebb8db2fa2f89368207f4f9573935bb6ddb15121 Mon Sep 17 00:00:00 2001 From: Pierrick Brun Date: Thu, 26 Dec 2024 14:02:08 +0100 Subject: [PATCH] fix: :bug: do not crash when there is no next rrule occurence --- rq_scheduler/scheduler.py | 2 ++ rq_scheduler/utils.py | 18 +++++++++++++----- tests/test_scheduler.py | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/rq_scheduler/scheduler.py b/rq_scheduler/scheduler.py index 64cafa9..8985d06 100644 --- a/rq_scheduler/scheduler.py +++ b/rq_scheduler/scheduler.py @@ -306,6 +306,8 @@ def rrule(self, rrule_string, func, args=None, kwargs=None, repeat=None, Schedule a recurring job via RRule """ scheduled_time = get_next_rrule_scheduled_time(rrule_string) + if not scheduled_time: + return None job = self._create_job(func, args=args, kwargs=kwargs, commit=False, result_ttl=result_ttl, ttl=ttl, id=id, queue_name=queue_name, diff --git a/rq_scheduler/utils.py b/rq_scheduler/utils.py index 4fbd404..b0d0a3c 100644 --- a/rq_scheduler/utils.py +++ b/rq_scheduler/utils.py @@ -37,12 +37,20 @@ def get_next_rrule_scheduled_time(rrule_string): with a rrule string""" timezone = dateutil.tz.UTC ruleset = dateutil.rrule.rrulestr(rrule_string, forceset=True) - if ruleset[0].tzinfo is None: - now = datetime.now() # naive datetime + any_occurence = None + for occur in ruleset: + any_occurence = occur + break + if any_occurence is None: + return None + if any_occurence.tzinfo is None: + now = datetime.now() else: - now = datetime.now(tz=timezone) # aware datetime - next_time = ruleset.after(now) - return next_time.astimezone(timezone) + now = datetime.now(tz=timezone) + next_occurence = ruleset.after(now) + if next_occurence is None: + return None + return next_occurence.astimezone(timezone) def setup_loghandlers(level='INFO'): diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index a98a504..2ca8334 100644 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -926,6 +926,20 @@ def test_rrule_schedules_correctly(self): expected_scheduled_time = (now + timedelta(hours=1, minutes=5)).astimezone(UTC) self.assertEqual(to_unix(expected_scheduled_time), to_unix(next_scheduled_time), f"{next_scheduled_time} should be {expected_scheduled_time}") + def test_rrule_without_upcoming_occurences(self): + # Create a job with a rrulejob_string that has no occurence in the future + now = datetime.fromisoformat("2024-12-26T12:00:00") + with freezegun.freeze_time(now): + job = self.scheduler.rrule("RRULE:FREQ=HOURLY;WKST=MO;BYMINUTE=5;BYSECOND=0;UNTIL=20241225T120000Z", say_hello) + with mock.patch.object(self.scheduler, 'enqueue_job', wraps=self.scheduler.enqueue_job) as enqueue_job: + self.assertEqual(0, self.scheduler.count()) + self.scheduler.enqueue_jobs() + self.assertEqual(0, enqueue_job.call_count) + + jobs = self.scheduler.get_jobs(with_times=True) + for j in jobs: + self.assertFalse() + def test_rrule_sets_timeout(self): """ Ensure that a job scheduled via rrule can be created with