|
@@ -0,0 +1,213 @@
|
|
|
+"""timer2 - Scheduler for Python functions."""
|
|
|
+
|
|
|
+from __future__ import generators
|
|
|
+
|
|
|
+import atexit
|
|
|
+import heapq
|
|
|
+import sys
|
|
|
+import traceback
|
|
|
+import warnings
|
|
|
+
|
|
|
+from threading import Thread, Event
|
|
|
+from time import time, sleep, mktime
|
|
|
+
|
|
|
+from datetime import datetime, timedelta
|
|
|
+
|
|
|
+VERSION = (0, 1, 0)
|
|
|
+__version__ = ".".join(map(str, VERSION))
|
|
|
+__author__ = "Ask Solem"
|
|
|
+__contact__ = "ask@celeryproject.org"
|
|
|
+__homepage__ = "http://github.com/ask/timer/"
|
|
|
+__docformat__ = "restructuredtext"
|
|
|
+
|
|
|
+DEFAULT_MAX_INTERVAL = 2
|
|
|
+
|
|
|
+class TimedFunctionFailed(UserWarning):
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+class Entry(object):
|
|
|
+ cancelled = False
|
|
|
+
|
|
|
+ def __init__(self, fun, args, kwargs):
|
|
|
+ self.fun = fun
|
|
|
+ self.args = args or []
|
|
|
+ self.kwargs = kwargs or {}
|
|
|
+ self.tref = self
|
|
|
+
|
|
|
+ def __call__(self):
|
|
|
+ return self.fun(*self.args, **self.kwargs)
|
|
|
+
|
|
|
+ def cancel(self):
|
|
|
+ self.tref.cancelled = True
|
|
|
+
|
|
|
+
|
|
|
+class Schedule(object):
|
|
|
+ """ETA scheduler."""
|
|
|
+ on_error = None
|
|
|
+
|
|
|
+ def __init__(self, max_interval=DEFAULT_MAX_INTERVAL, on_error=None):
|
|
|
+ self.max_interval = float(max_interval)
|
|
|
+ self.on_error = on_error or self.on_error
|
|
|
+ self._queue = []
|
|
|
+
|
|
|
+ def handle_error(self, exc_info):
|
|
|
+ if self.on_error:
|
|
|
+ self.on_error(exc_info)
|
|
|
+ return True
|
|
|
+
|
|
|
+ def enter(self, entry, eta=None, priority=0):
|
|
|
+ """Enter function into the scheduler.
|
|
|
+
|
|
|
+ :param entry: Item to enter.
|
|
|
+ :keyword eta: Scheduled time as a :class:`datetime.datetime` object.
|
|
|
+ :keyword priority: Unused.
|
|
|
+
|
|
|
+ """
|
|
|
+ if isinstance(eta, datetime):
|
|
|
+ try:
|
|
|
+ eta = mktime(eta.timetuple())
|
|
|
+ except OverflowError:
|
|
|
+ self.handle_error(sys.exc_info())
|
|
|
+ eta = eta or time()
|
|
|
+ heapq.heappush(self._queue, (eta, priority, entry))
|
|
|
+ return entry
|
|
|
+
|
|
|
+ def __iter__(self):
|
|
|
+ """The iterator yields the time to sleep for between runs."""
|
|
|
+
|
|
|
+ # localize variable access
|
|
|
+ nowfun = time
|
|
|
+ pop = heapq.heappop
|
|
|
+
|
|
|
+ while 1:
|
|
|
+ if self._queue:
|
|
|
+ eta, priority, entry = verify = self._queue[0]
|
|
|
+ now = nowfun()
|
|
|
+
|
|
|
+ if now < eta:
|
|
|
+ yield min(eta - now, self.max_interval)
|
|
|
+ else:
|
|
|
+ event = pop(self._queue)
|
|
|
+
|
|
|
+ if event is verify:
|
|
|
+ if not entry.cancelled:
|
|
|
+ try:
|
|
|
+ entry()
|
|
|
+ except Exception, exc:
|
|
|
+ typ, val, tb = einfo = sys.exc_info()
|
|
|
+ if not self.handle_error(einfo):
|
|
|
+ warnings.warn(repr(exc),
|
|
|
+ TimedFunctionFailed)
|
|
|
+ traceback.print_exception(typ, val, tb)
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ heapq.heappush(self._queue, event)
|
|
|
+ yield None
|
|
|
+
|
|
|
+ def empty(self):
|
|
|
+ """Is the schedule empty?"""
|
|
|
+ return not self._queue
|
|
|
+
|
|
|
+ def clear(self):
|
|
|
+ self._queue = []
|
|
|
+
|
|
|
+ def info(self):
|
|
|
+ return ({"eta": eta, "priority": priority, "item": item}
|
|
|
+ for eta, priority, item in self.queue)
|
|
|
+
|
|
|
+ @property
|
|
|
+ def queue(self):
|
|
|
+ events = list(self._queue)
|
|
|
+ return map(heapq.heappop, [events]*len(events))
|
|
|
+
|
|
|
+
|
|
|
+class Timer(Thread):
|
|
|
+ Entry = Entry
|
|
|
+
|
|
|
+ precision = 0.3
|
|
|
+ running = False
|
|
|
+ on_tick = None
|
|
|
+
|
|
|
+ def __init__(self, schedule=None, precision=None, on_error=None,
|
|
|
+ on_tick=None):
|
|
|
+ if precision is not None:
|
|
|
+ self.precision = precision
|
|
|
+ self.schedule = schedule or Schedule(on_error=on_error)
|
|
|
+ self.on_tick = on_tick or self.on_tick
|
|
|
+
|
|
|
+ Thread.__init__(self)
|
|
|
+ self._shutdown = Event()
|
|
|
+ self._stopped = Event()
|
|
|
+ self.setDaemon(True)
|
|
|
+
|
|
|
+ def run(self):
|
|
|
+ self.running = True
|
|
|
+ scheduler = iter(self.schedule)
|
|
|
+ while not self._shutdown.isSet():
|
|
|
+ delay = scheduler.next() or self.precision
|
|
|
+ if self.on_tick:
|
|
|
+ self.on_tick(delay)
|
|
|
+ sleep(delay)
|
|
|
+ self._stopped.set()
|
|
|
+
|
|
|
+ def stop(self):
|
|
|
+ if self.running:
|
|
|
+ self._shutdown.set()
|
|
|
+ self._stopped.wait()
|
|
|
+ self.join(1e100)
|
|
|
+
|
|
|
+ def enter(self, entry, eta, priority=None):
|
|
|
+ if not self.running:
|
|
|
+ self.start()
|
|
|
+ return self.schedule.enter(entry, eta, priority)
|
|
|
+
|
|
|
+ def apply_at(self, eta, fun, args=(), kwargs={}, priority=0):
|
|
|
+ return self.enter(self.Entry(fun, args, kwargs), eta, priority)
|
|
|
+
|
|
|
+ def enter_after(self, msecs, entry, priority=0):
|
|
|
+ eta = datetime.now() + timedelta(seconds=msecs / 1000.0)
|
|
|
+ return self.enter(entry, eta, priority)
|
|
|
+
|
|
|
+ def apply_after(self, msecs, fun, args=(), kwargs={}, priority=0):
|
|
|
+ return self.enter_after(msecs, Entry(fun, args, kwargs), priority)
|
|
|
+
|
|
|
+ def apply_interval(self, msecs, fun, args=(), kwargs={}, priority=0):
|
|
|
+ tref = Entry(fun, args, kwargs)
|
|
|
+
|
|
|
+ def _reschedules(*args, **kwargs):
|
|
|
+ try:
|
|
|
+ return fun(*args, **kwargs)
|
|
|
+ finally:
|
|
|
+ self.enter_after(msecs, tref, priority)
|
|
|
+
|
|
|
+ tref.fun = _reschedules
|
|
|
+ return self.enter_after(msecs, tref, priority)
|
|
|
+
|
|
|
+ def exit_after(self, msecs, priority=10):
|
|
|
+ self.apply_after(msecs, sys.exit, priority)
|
|
|
+
|
|
|
+ def cancel(self, tref):
|
|
|
+ tref.cancel()
|
|
|
+
|
|
|
+ def clear(self):
|
|
|
+ self.schedule.clear()
|
|
|
+
|
|
|
+ def empty(self):
|
|
|
+ return self.schedule.empty()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def queue(self):
|
|
|
+ return self.schedule.queue
|
|
|
+
|
|
|
+_default_timer = Timer()
|
|
|
+apply_after = _default_timer.apply_after
|
|
|
+apply_at = _default_timer.apply_at
|
|
|
+apply_interval = _default_timer.apply_interval
|
|
|
+enter_after = _default_timer.enter_after
|
|
|
+enter = _default_timer.enter
|
|
|
+exit_after = _default_timer.exit_after
|
|
|
+cancel = _default_timer.cancel
|
|
|
+clear = _default_timer.clear
|
|
|
+
|
|
|
+atexit.register(_default_timer.stop)
|