PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/tempora/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/tempora/schedule.py |
""" Classes for calling functions a schedule. Has time zone support. For example, to run a job at 08:00 every morning in 'Asia/Calcutta': >>> job = lambda: print("time is now", datetime.datetime()) >>> time = datetime.time(8, tzinfo=pytz.timezone('Asia/Calcutta')) >>> cmd = PeriodicCommandFixedDelay.daily_at(time, job) >>> sched = InvokeScheduler() >>> sched.add(cmd) >>> while True: # doctest: +SKIP ... sched.run_pending() ... time.sleep(.1) """ import datetime import numbers import abc import bisect import pytz def now(): """ Provide the current timezone-aware datetime. A client may override this function to change the default behavior, such as to use local time or timezone-naïve times. """ return datetime.datetime.now(pytz.utc) def from_timestamp(ts): """ Convert a numeric timestamp to a timezone-aware datetime. A client may override this function to change the default behavior, such as to use local time or timezone-naïve times. """ return datetime.datetime.fromtimestamp(ts, pytz.utc) class DelayedCommand(datetime.datetime): """ A command to be executed after some delay (seconds or timedelta). """ @classmethod def from_datetime(cls, other): return cls( other.year, other.month, other.day, other.hour, other.minute, other.second, other.microsecond, other.tzinfo, ) @classmethod def after(cls, delay, target): if not isinstance(delay, datetime.timedelta): delay = datetime.timedelta(seconds=delay) due_time = now() + delay cmd = cls.from_datetime(due_time) cmd.delay = delay cmd.target = target return cmd @staticmethod def _from_timestamp(input): """ If input is a real number, interpret it as a Unix timestamp (seconds sinc Epoch in UTC) and return a timezone-aware datetime object. Otherwise return input unchanged. """ if not isinstance(input, numbers.Real): return input return from_timestamp(input) @classmethod def at_time(cls, at, target): """ Construct a DelayedCommand to come due at `at`, where `at` may be a datetime or timestamp. """ at = cls._from_timestamp(at) cmd = cls.from_datetime(at) cmd.delay = at - now() cmd.target = target return cmd def due(self): return now() >= self class PeriodicCommand(DelayedCommand): """ Like a delayed command, but expect this command to run every delay seconds. """ def _next_time(self): """ Add delay to self, localized """ return self._localize(self + self.delay) @staticmethod def _localize(dt): """ Rely on pytz.localize to ensure new result honors DST. """ try: tz = dt.tzinfo return tz.localize(dt.replace(tzinfo=None)) except AttributeError: return dt def next(self): cmd = self.__class__.from_datetime(self._next_time()) cmd.delay = self.delay cmd.target = self.target return cmd def __setattr__(self, key, value): if key == 'delay' and not value > datetime.timedelta(): raise ValueError( "A PeriodicCommand must have a positive, " "non-zero delay." ) super().__setattr__(key, value) class PeriodicCommandFixedDelay(PeriodicCommand): """ Like a periodic command, but don't calculate the delay based on the current time. Instead use a fixed delay following the initial run. """ @classmethod def at_time(cls, at, delay, target): """ >>> cmd = PeriodicCommandFixedDelay.at_time(0, 30, None) >>> cmd.delay.total_seconds() 30.0 """ at = cls._from_timestamp(at) cmd = cls.from_datetime(at) if isinstance(delay, numbers.Number): delay = datetime.timedelta(seconds=delay) cmd.delay = delay cmd.target = target return cmd @classmethod def daily_at(cls, at, target): """ Schedule a command to run at a specific time each day. >>> from tempora import utc >>> noon = utc.time(12, 0) >>> cmd = PeriodicCommandFixedDelay.daily_at(noon, None) >>> cmd.delay.total_seconds() 86400.0 """ daily = datetime.timedelta(days=1) # convert when to the next datetime matching this time when = datetime.datetime.combine(datetime.date.today(), at) when -= daily while when < now(): when += daily return cls.at_time(cls._localize(when), daily, target) class Scheduler: """ A rudimentary abstract scheduler accepting DelayedCommands and dispatching them on schedule. """ def __init__(self): self.queue = [] def add(self, command): assert isinstance(command, DelayedCommand) bisect.insort(self.queue, command) def run_pending(self): while self.queue: command = self.queue[0] if not command.due(): break self.run(command) if isinstance(command, PeriodicCommand): self.add(command.next()) del self.queue[0] @abc.abstractmethod def run(self, command): """ Run the command """ class InvokeScheduler(Scheduler): """ Command targets are functions to be invoked on schedule. """ def run(self, command): command.target() class CallbackScheduler(Scheduler): """ Command targets are passed to a dispatch callable on schedule. """ def __init__(self, dispatch): super().__init__() self.dispatch = dispatch def run(self, command): self.dispatch(command.target)