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