Browse Source

Remove hc.lib.cronsim, install it from PyPI

master
Pēteris Caune 3 years ago
parent
commit
3f9f219a58
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
6 changed files with 5 additions and 295 deletions
  1. +1
    -1
      hc/api/models.py
  2. +1
    -1
      hc/front/validators.py
  3. +1
    -1
      hc/front/views.py
  4. +0
    -291
      hc/lib/cronsim.py
  5. +1
    -1
      hc/lib/jsonschema.py
  6. +1
    -0
      requirements.txt

+ 1
- 1
hc/api/models.py View File

@ -6,6 +6,7 @@ import time
import uuid
from datetime import datetime, timedelta as td
from cronsim import CronSim
from django.conf import settings
from django.core.signing import TimestampSigner
from django.db import models
@ -15,7 +16,6 @@ from django.utils.text import slugify
from hc.accounts.models import Project
from hc.api import transports
from hc.lib import emails
from hc.lib.cronsim import CronSim
from hc.lib.date import month_boundaries
import pytz


+ 1
- 1
hc/front/validators.py View File

@ -1,7 +1,7 @@
from datetime import datetime
from cronsim import CronSim
from django.core.exceptions import ValidationError
from hc.lib.cronsim import CronSim
from urllib.parse import urlparse
from pytz import all_timezones


+ 1
- 1
hc/front/views.py View File

@ -7,6 +7,7 @@ from secrets import token_urlsafe
from urllib.parse import urlencode
from cron_descriptor import ExpressionDescriptor
from cronsim import CronSim
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
@ -49,7 +50,6 @@ from hc.front.templatetags.hc_extras import (
)
from hc.lib import jsonschema
from hc.lib.badges import get_badge_url
from hc.lib.cronsim import CronSim
import pytz
from pytz.exceptions import UnknownTimeZoneError
import requests


+ 0
- 291
hc/lib/cronsim.py View File

@ -1,291 +0,0 @@
import calendar
from datetime import datetime, timedelta as td, time
from enum import IntEnum
import pytz
RANGES = [
frozenset(range(0, 60)),
frozenset(range(0, 24)),
frozenset(range(1, 32)),
frozenset(range(1, 13)),
frozenset(range(0, 8)),
frozenset(range(0, 60)),
]
SYMBOLIC_DAYS = "SUN MON TUE WED THU FRI SAT".split()
SYMBOLIC_MONTHS = "JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC".split()
DAYS_IN_MONTH = [None, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
class CronSimError(Exception):
pass
def _int(value):
if not value.isdigit():
raise CronSimError("Bad value: %s" % value)
return int(value)
class Field(IntEnum):
MINUTE = 0
HOUR = 1
DAY = 2
MONTH = 3
DOW = 4
def int(self, s):
if self == Field.MONTH and s.upper() in SYMBOLIC_MONTHS:
return SYMBOLIC_MONTHS.index(s.upper()) + 1
if self == Field.DOW and s.upper() in SYMBOLIC_DAYS:
return SYMBOLIC_DAYS.index(s.upper())
v = _int(s)
if v not in RANGES[self]:
raise CronSimError("Bad value: %s" % s)
return v
def parse(self, s):
if s == "*":
return RANGES[self]
if "," in s:
result = set()
for term in s.split(","):
result.update(self.parse(term))
return result
if "#" in s and self == Field.DOW:
term, nth = s.split("#", maxsplit=1)
nth = _int(nth)
if nth < 1 or nth > 5:
raise CronSimError("Bad value: %s" % s)
spec = (self.int(term), nth)
return {spec}
if "/" in s:
term, step = s.split("/", maxsplit=1)
step = _int(step)
if step == 0:
raise CronSimError("Step cannot be zero")
items = sorted(self.parse(term))
if items == [CronSim.LAST]:
return items
if len(items) == 1:
start = items[0]
end = max(RANGES[self])
items = range(start, end + 1)
return set(items[::step])
if "-" in s:
start, end = s.split("-", maxsplit=1)
start = self.int(start)
end = self.int(end)
if end < start:
raise CronSimError("Range end cannot be smaller than start")
return set(range(start, end + 1))
if self == Field.DAY and s in ("L", "l"):
return {CronSim.LAST}
return {self.int(s)}
class NoTz(object):
def localize(self, dt, is_dst=None):
return dt
def normalize(self, dt):
return dt
class CronSim(object):
LAST = -1000
def __init__(self, expr, dt):
self.tz = dt.tzinfo or NoTz()
self.fixup_tz = None
self.dt = dt.replace(second=0, microsecond=0)
parts = expr.split()
if len(parts) != 5:
raise CronSimError("Wrong number of fields")
self.minutes = Field.MINUTE.parse(parts[0])
self.hours = Field.HOUR.parse(parts[1])
self.days = Field.DAY.parse(parts[2])
self.months = Field.MONTH.parse(parts[3])
self.weekdays = Field.DOW.parse(parts[4])
# If day is unrestricted but dow is restricted then match only with dow:
if self.days == RANGES[Field.DAY] and self.weekdays != RANGES[Field.DOW]:
self.days = set()
# If dow is unrestricted but day is restricted then match only with day:
if self.weekdays == RANGES[Field.DOW] and self.days != RANGES[Field.DAY]:
self.weekdays = set()
if len(self.days) and min(self.days) > 29:
# Check if we have any month with enough days
if min(self.days) > max(DAYS_IN_MONTH[month] for month in self.months):
raise CronSimError("Bad day-of-month")
if self.dt.tzinfo in (None, pytz.utc):
# No special DST handling for naive datetimes or UTC
pass
else:
# Special handling for jobs that run at specific time, or with
# a granularity greater than one hour (to mimic Debian cron).
# Convert to naive datetime, will convert back to the tz-aware
# in __next__, right before returning the value.
if not parts[0].startswith("*") and not parts[1].startswith("*"):
self.fixup_tz, self.tz = self.tz, NoTz()
self.dt = self.dt.replace(tzinfo=None)
def tick(self, minutes=1):
""" Roll self.dt forward by 1 or more minutes and fix timezone. """
self.dt = self.tz.normalize(self.dt + td(minutes=minutes))
def advance_minute(self):
"""Roll forward the minute component until it satisfies the constraints.
Return False if the minute meets contraints without modification.
Return True if self.dt was rolled forward.
"""
if self.dt.minute in self.minutes:
return False
if len(self.minutes) == 1:
# An optimization for the special case where self.minutes has exactly
# one element. Instead of advancing one minute per iteration,
# make a jump from the current minute to the target minute.
delta = (next(iter(self.minutes)) - self.dt.minute) % 60
self.tick(minutes=delta)
while self.dt.minute not in self.minutes:
self.tick()
if self.dt.minute == 0:
# Break out to re-check month, day and hour
break
return True
def advance_hour(self):
"""Roll forward the hour component until it satisfies the constraints.
Return False if the hour meets contraints without modification.
Return True if self.dt was rolled forward.
"""
if self.dt.hour in self.hours:
return False
self.dt = self.dt.replace(minute=0)
while self.dt.hour not in self.hours:
self.tick(minutes=60)
if self.dt.hour == 0:
# break out to re-check month and day
break
return True
def match_day(self, d):
# Does the day of the month match?
if d.day in self.days:
return True
if CronSim.LAST in self.days:
_, last = calendar.monthrange(d.year, d.month)
if d.day == last:
return True
# Does the day of the week match?
dow = d.weekday() + 1
if dow in self.weekdays or dow % 7 in self.weekdays:
return True
idx = (d.day + 6) // 7
if (dow, idx) in self.weekdays or (dow % 7, idx) in self.weekdays:
return True
def advance_day(self):
"""Roll forward the day component until it satisfies the constraints.
This method advances the date until it matches either the
day-of-month, or the day-of-week constraint.
Return False if the day meets contraints without modification.
Return True if self.dt was rolled forward.
"""
needle = self.dt.date()
if self.match_day(needle):
return False
while not self.match_day(needle):
needle += td(days=1)
if needle.day == 1:
# We're in a different month now, break out to re-check month
# This significantly speeds up the "0 0 * 2 MON#5" case
break
self.dt = self.tz.localize(datetime.combine(needle, time()))
return True
def advance_month(self):
"""Roll forward the month component until it satisfies the constraints. """
if self.dt.month in self.months:
return
needle = self.dt.date()
while needle.month not in self.months:
needle = (needle.replace(day=1) + td(days=32)).replace(day=1)
self.dt = self.tz.localize(datetime.combine(needle, time()))
def __iter__(self):
return self
def __next__(self):
self.tick()
while True:
self.advance_month()
if self.advance_day():
continue
if self.advance_hour():
continue
if self.advance_minute():
continue
# If all constraints are satisfied then we have the result.
# The last step is to see if self.fixup_dst is set. If it is,
# localize self.dt and handle conflicts.
if self.fixup_tz:
while True:
try:
return self.fixup_tz.localize(self.dt, is_dst=None)
except pytz.AmbiguousTimeError:
return self.fixup_tz.localize(self.dt, is_dst=True)
except pytz.NonExistentTimeError:
self.dt += td(minutes=1)
return self.dt

+ 1
- 1
hc/lib/jsonschema.py View File

@ -6,7 +6,7 @@ Supports only a tiny subset of jsonschema.
from datetime import datetime
from hc.lib.cronsim import CronSim
from cronsim import CronSim
from pytz import all_timezones


+ 1
- 0
requirements.txt View File

@ -1,4 +1,5 @@
cron-descriptor==1.2.24
cronsim==1.0
Django==3.2.8
django-compressor==2.4
fido2==0.9.1


Loading…
Cancel
Save