def test_it_parses_stars(self): w = CronSim("* * * * *", NOW) self.assertEqual(w.minutes, set(range(0, 60))) self.assertEqual(w.hours, set(range(0, 24))) self.assertEqual(w.days, set(range(1, 32))) self.assertEqual(w.months, set(range(1, 13))) self.assertEqual(w.weekdays, set(range(0, 8)))
def get_grace_start(self, with_started=True): """ Return the datetime when the grace period starts. If the check is currently new, paused or down, return None. """ # NEVER is a constant sentinel value (year 3000). # Using None instead would make the min() logic clunky. result = NEVER if self.kind == "simple" and self.status == "up": result = self.last_ping + self.timeout elif self.kind == "cron" and self.status == "up": # The complex case, next ping is expected based on cron schedule. # Don't convert to naive datetimes (and so avoid ambiguities around # DST transitions). cronsim will handle the timezone-aware datetimes. last_local = self.last_ping.astimezone(ZoneInfo(self.tz)) result = next(CronSim(self.schedule, last_local)) if with_started and self.last_start and self.status != "down": result = min(result, self.last_start) if result != NEVER: return result
def validate(obj, schema, obj_name="value"): if schema.get("type") == "string": if not isinstance(obj, str): raise ValidationError("%s is not a string" % obj_name) if "minLength" in schema and len(obj) < schema["minLength"]: raise ValidationError("%s is too short" % obj_name) if "maxLength" in schema and len(obj) > schema["maxLength"]: raise ValidationError("%s is too long" % obj_name) if schema.get("format") == "cron": try: # Does it have 5 components? if len(obj.split()) != 5: raise ValueError() # Does cronsim accept the schedule? it = CronSim(obj, datetime(2000, 1, 1)) # Can it calculate the next datetime? next(it) except: raise ValidationError("%s is not a valid cron expression" % obj_name) if schema.get("format") == "timezone" and obj not in all_timezones: raise ValidationError("%s is not a valid timezone" % obj_name) elif schema.get("type") == "number": if not isinstance(obj, int): raise ValidationError("%s is not a number" % obj_name) if "minimum" in schema and obj < schema["minimum"]: raise ValidationError("%s is too small" % obj_name) if "maximum" in schema and obj > schema["maximum"]: raise ValidationError("%s is too large" % obj_name) elif schema.get("type") == "boolean": if not isinstance(obj, bool): raise ValidationError("%s is not a boolean" % obj_name) elif schema.get("type") == "array": if not isinstance(obj, list): raise ValidationError("%s is not an array" % obj_name) for v in obj: validate(v, schema["items"], "an item in '%s'" % obj_name) elif schema.get("type") == "object": if not isinstance(obj, dict): raise ValidationError("%s is not an object" % obj_name) properties = schema.get("properties", {}) for key, spec in properties.items(): if key in obj: validate(obj[key], spec, obj_name=key) for key in schema.get("required", []): if key not in obj: raise ValidationError("key %s absent in %s" % (key, obj_name)) if "enum" in schema: if obj not in schema["enum"]: raise ValidationError("%s has unexpected value" % obj_name)
def __call__(self, value): # Expect 5 components- if len(value.split()) != 5: raise ValidationError(message=self.message) try: # Does cronsim accept the schedule? it = CronSim(value, datetime(2000, 1, 1)) # Can it calculate the next datetime? next(it) except: raise ValidationError(message=self.message)
def test_it_rejects_bad_values(self): patterns = ( "%s * * * *", "* %s * * *", "* * %s * *", "* * * %s * ", "* * * * %s", "* * * * * %s", "1-%s * * * *", "%s-60 * * * *", "* * * %s-DEC *", "* * * JAN-%s *", "* * * * %s-SUN", "* * * * MON-%s", ) bad_values = ("-1", "61", "ABC", "2/", "/2", "2#", "#2", "1##1", "1//2") for pattern, s in product(patterns, bad_values): with self.assertRaises(CronSimError): CronSim(pattern % s, NOW)
def test_it_parses_sun_tue(self): w = CronSim("* * * * sun-tue", NOW) self.assertEqual(w.weekdays, {0, 1, 2})
def test_it_parses_symbolic_month(self): w = CronSim("* * * JAN *", NOW) self.assertEqual(w.months, {1})
def test_it_parses_symbolic_weekday(self): w = CronSim("* * * * MON", NOW) self.assertEqual(w.weekdays, {1})
def test_it_parses_restricted_day_unrestricted_dow(self): w = CronSim("* * 1 * *", NOW) self.assertEqual(w.days, {1}) self.assertEqual(w.weekdays, set())
def test_it_parses_start_with_step(self): w = CronSim("5/15 * * * *", NOW) self.assertEqual(w.minutes, {5, 20, 35, 50})
def test_it_parses_step(self): w = CronSim("*/15 * * * *", NOW) self.assertEqual(w.minutes, {0, 15, 30, 45})
def test_it_rejects_zero_step(self): with self.assertRaises(CronSimError): CronSim("*/0 * * * *", NOW)
def test_it_rejects_underscores(self): with self.assertRaises(CronSimError): CronSim("1-1_0 * * * *", NOW)
def test_it_rejects_lopsided_range(self): with self.assertRaises(CronSimError): CronSim("* * 5-1 * *", NOW)
def test_it_rejects_4_components(self): with self.assertRaises(CronSimError): CronSim("* * * *", NOW)
def test_it_parses_interval(self): w = CronSim("1-3 * * * *", NOW) self.assertEqual(w.minutes, {1, 2, 3})
def test_it_parses_two_intervals(self): w = CronSim("1-3,7-9 * * * *", NOW) self.assertEqual(w.minutes, {1, 2, 3, 7, 8, 9})
def test_it_rejects_big_nth(self): with self.assertRaises(CronSimError): CronSim("* * * * 1#6", NOW)
def test_it_parses_interval_with_step(self): w = CronSim("0-10/2 * * * *", NOW) self.assertEqual(w.minutes, {0, 2, 4, 6, 8, 10})
def test_it_handles_l(self): dt = next(CronSim("1 1 L * *", NOW)) self.assertEqual(dt.isoformat(), "2020-01-31T01:01:00")
def test_it_parses_day_lowercase_l(self): w = CronSim("* * l * *", NOW) self.assertEqual(w.days, {CronSim.LAST})
def test_it_handles_nth_weekday(self): dt = next(CronSim("1 1 * * 1#2", NOW)) self.assertEqual(dt.isoformat(), "2020-01-13T01:01:00")
def test_it_parses_nth_weekday(self): w = CronSim("* * * * 1#2", NOW) self.assertEqual(w.weekdays, {(1, 2)})
def test_it_parses_numbers(self): w = CronSim("1 * * * *", NOW) self.assertEqual(w.minutes, {1})
def test_it_parses_lowercase_symbolic_weekday(self): w = CronSim("* * * * mon", NOW) self.assertEqual(w.weekdays, {1})
def test_it_parses_weekday(self): w = CronSim("* * * * 1", NOW) self.assertEqual(w.weekdays, {1})
def test_it_parses_weekday_range_from_zero(self): w = CronSim("* * * * 0-2", NOW) self.assertEqual(w.weekdays, {0, 1, 2})
def test_it_handles_0_sunday(self): w = CronSim("* * * * 0", NOW) self.assertEqual(w.weekdays, {0})
def test_it_starts_weekday_step_from_zero(self): w = CronSim("* * * * */2", NOW) self.assertEqual(w.weekdays, {0, 2, 4, 6})
def test_it_parses_list(self): w = CronSim("1,2,3 * * * *", NOW) self.assertEqual(w.minutes, {1, 2, 3})