|  | @@ -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)
 |