class DerivedValue(Readable): """'DerivedValue(formula, *values)' - a value derived from other values Usage:: # 'derived' changes whenever x or y change derived = DerivedValue(lambda: x()+y(), x, y) A 'DerivedValue' fires an event equal to 'formula()' whenever any of the supplied 'values' fire, and the value of 'formula()' is not equal to its last known value (if any). """ __slots__ = '_formula' protocols.advise(instancesProvide=[IValue]) def __init__(self, formula, *values): self._formula = formula super(DerivedValue, self).__init__() subscribe(AnyOf(*[adapt(v, IValue) for v in values]), self._set) # def __call__(self): """Get current value of 'formula()'""" return self._formula() def _set(self, source, event): value = self._formula() if not hasattr(self, '_value') or self._value <> value: self._value = value self._fire(value)
class NullClass(object): """Null condition: never fires, never has a value other than None""" __slots__ = () protocols.advise(instancesProvide=[IConditional, IValue]) value = property(lambda self: self) def __call__(self): pass def __repr__(self): return 'events.Null' def conjuncts(self): return () def disjuncts(self): return () def __invert__(self): return self def __and__(self, other): return other def __or__(self, other): return other def addCallback(self, cb): return lambda: None # we'll never fire, and so can't call back def nextAction(self, task=None, state=None): pass # always suspend
class Value(Writable): """Broadcast changes in a variable to all observers 'events.Value()' instances implement a near-trivial version of the 'Observer' pattern: callbacks can be informed that a change has been made to the 'Value'. Example:: aValue = events.Value(42) assert aValue()==42 aValue.set(78) # fires any registered callbacks aValue.set(78) # doesn't fire, value hasn't changed aValue.set(78,force=True) # force firing even though value's the same Events are broadcast to all callbacks, whether they "accept" or "reject" the event, and tasks yielding to a 'Value' are suspended until the next event is broadcast. The current value of the 'Value' is supplied to callbacks and tasks via the 'event' parameter, and the 'Value' itself is supplied as the 'source'. (See 'events.IEventSink'.) """ __slots__ = () protocols.advise(instancesProvide=[IValue]) defaultValue = NOT_GIVEN def __init__(self, value=NOT_GIVEN): if value is NOT_GIVEN: value = self.defaultValue self._value = value super(Value, self).__init__() isTrue = property(lambda self: adapt(self, IConditional)) isFalse = property(lambda self: ~adapt(self, IConditional))
class SignalEvents(binding.Singleton): """Global signal manager""" protocols.advise(classProvides=[ISignalSource]) _events = {None: sources.Broadcaster()} # Null signal def signals(self, *signames): """'IEventSource' that triggers whenever any of named signals occur""" if len(signames) == 1: signum = signals.get(signames[0]) try: return self._events[signum] except KeyError: e = self._events[signum] = SignalEvent(signum) return e else: return sources.AnyOf(*[self.signals(n) for n in signames]) def haveSignal(self, signame): """Return true if signal named 'signame' exists""" return signame in signals def __call__(self, *args): return self
class _STask(Task): """'events.Task' that handles errors better, by relying on a scheduler""" __slots__ = 'scheduler', 'aborted' protocols.advise(instancesProvide=[IScheduledTask]) def __init__(self, iterator, scheduler): self.scheduler = scheduler self.aborted = Condition() Task.__init__(self, iterator) def _start(self): super(_STask, self).step() def _uncaughtError(self): condition = self.aborted try: try: raise except SystemExit, v: condition = self.isFinished return True finally: self.scheduler._callAt(lambda e, s: condition.set(True), self.scheduler.now()) def step(self, source=None, event=NOT_GIVEN): """See 'events.IScheduledTask.step'""" self.scheduler._callAt( lambda e, s: super(_STask, self).step(source, event), self.scheduler.now())
class TaskState(object): """Tracks the state of a task; see 'events.ITaskState' for details""" __slots__ = 'lastEvent', 'handlingError', 'stack' protocols.advise(instancesProvide=[ITaskState]) def __init__(self): self.lastEvent = NOT_GIVEN self.handlingError = False self.stack = [] def CALL(self, iterator): self.stack.append(adapt(iterator, IProcedure)) def YIELD(self, value): self.lastEvent = value def RETURN(self): self.stack.pop() def THROW(self): self.handlingError = True self.lastEvent = NOT_GIVEN def CATCH(self): self.handlingError = False
class ReadableAsCondition(AbstractConditional): """Wrap an 'IReadableSource' as an 'IConditional'""" __slots__ = 'value' protocols.advise(instancesProvide=[IConditional], asAdapterForProtocols=[IValue]) singleFire = False def __init__(self, subject): self.value = subject super(ReadableAsCondition, self).__init__() subscribe(subject, self._set) self.cmpval = self.__class__, subject def __call__(self): return self.value() def derive(self, func): return self.value.derive(func) def _set(self, src, evt): if evt: self._fire(evt)
class Semaphore(Condition): """Allow up to 'n' tasks to proceed simultaneously A 'Semaphore' is like a 'Condition', except that it does not broadcast its events. Each event is supplied to callbacks only until one "accepts" the event (by returning a true value). 'Semaphore' instances also have 'put()' and 'take()' methods that respectively increase or decrease their value by 1. Note that 'Semaphore' does not automatically decrease its count due to a callback or task resumption. You must explicitly 'take()' the semaphore in your task or callback to reduce its count. """ __slots__ = () protocols.advise(instancesProvide=[ISemaphore]) singleFire = True defaultValue = 0 def put(self): """See 'events.ICondition.put()'""" self.set(self() + 1) def take(self): """See 'events.ICondition.take()'""" self.set(self() - 1)
class WritableAsCondition(ReadableAsCondition): """Wrap an 'IWritableValue' as an 'IConditional'""" protocols.advise(instancesProvide=[IWritableSource, IConditional], asAdapterForProtocols=[IWritableValue]) def set(self, value, force=False): self.value.set(value, force)
class Scheduler(object): """Time-based event sources; see 'events.IScheduler' for interface""" protocols.advise(instancesProvide=[IScheduler]) def __init__(self, time=time.time): self.now = time self._appointments = [] self.isEmpty = Condition(True) def time_available(self): if self._appointments: return max(0, self._appointments[0][0] - self.now()) def tick(self, stop=None): now = self.now() while self._appointments and self._appointments[0][ 0] <= now and not stop: self._appointments.pop(0)[1](self, now) self.isEmpty.set(not self._appointments) def sleep(self, secs=0): return _Sleeper(self, secs) def until(self, time): c = Condition() if time <= self.now(): c.set(True) else: self._callAt(lambda s, e: c.set(True), time) return c def timeout(self, secs): return self.until(self.now() + secs) def spawn(self, iterator): return _STask(iterator, self) def alarm(self, iterator, timeout, errorType=TimeoutError): return Interrupt(iterator, self.timeout(timeout), errorType) def _callAt(self, what, when): self.isEmpty.set(False) appts = self._appointments item = (when, what) lo = 0 hi = len(appts) while lo < hi: mid = (lo + hi) // 2 if when < appts[mid][0]: hi = mid else: lo = mid + 1 appts.insert(lo, item) return lambda: (item in appts) and appts.remove(item)
class ProcedureAsTaskSwitch(protocols.Adapter): protocols.advise(instancesProvide=[ITaskSwitch], asAdapterForProtocols=[IProcedure]) def nextAction(self, task=None, state=None): if state is not None: state.CALL(self.subject) return True
class Writable(Readable): """Base class for an 'IWritableSource' -- adds a 'set()' method""" __slots__ = () protocols.advise(instancesProvide=[IWritableSource]) def set(self, value, force=False): """See 'events.IWritableSource.set()'""" if force or value <> self._value: self._value = value self._fire(value)
class Task(Observable): """Thread-like "task" that pauses and resumes in response to events Usage:: def aGenerator(someArg): yield untilSomeCondition; events.resume() # ... do something ... events.Task(aGenerator(someValue)) # create a task When created, tasks run until the first 'yield' operation yielding an 'ITaskSwitch' results in the task being suspended. The task will then be resumed when the waited-on event fires its callbacks, and so on. Tasks offer an 'isFinished' 'ICondition' that can be waited on, or checked to find out whether the task has successfully completed. See 'events.ITask' for more details.""" __slots__ = 'isFinished', '_state' protocols.advise( instancesProvide=[ITask], asAdapterForProtocols=[IProcedure], factoryMethod='fromProcedure', ) singleFire = False # Broadcast results to listeners overrunOK = False # Results are not bufferable def __init__(self, iterator): Observable.__init__(self) self.isFinished = Condition() self._state = TaskState() self._state.CALL(iterator) self._start() def fromProcedure(klass, ob): return klass(ob) fromProcedure = classmethod(fromProcedure) def _start(self): """Hook for subclasses to start the task""" self.step() def _uncaughtError(self): """Hook for subclasses to catch exceptions that escape the task""" try: raise except SystemExit, v: self.isFinished.set(True) return True
class Readable(Observable): """Base class for an 'IReadableSource' -- adds a '_value' and '__call__'""" __slots__ = '_value' singleFire = False protocols.advise(instancesProvide=[IReadableSource]) def __call__(self): """See 'events.IReadableSource.__call__()'""" return self._value def derive(self, func): return DerivedValue(lambda: func(self()), self)
class _Sleeper(object): protocols.advise(instancesProvide=[IEventSource]) def __init__(self, scheduler, delay): self.scheduler = scheduler self.delay = delay def nextAction(self, task=None, state=None): if task is not None: self.addCallback(task.step) def addCallback(self, func): return self.scheduler._callAt(lambda s, e: func(self, e), self.scheduler.now() + self.delay)
class EventLoop(binding.Component): """All-in-one event source and loop runner""" protocols.advise(instancesProvide=[IEventLoop]) sigsrc = binding.Obtain(ISignalSource) haveSignal = signals = binding.Delegate('sigsrc') scheduler = binding.Obtain(IScheduler) spawn = now = tick = sleep = until = timeout = time_available \ = binding.Delegate('scheduler') selector = binding.Obtain(ISelector) readable = writable = exceptional = binding.Delegate('selector') log = binding.Obtain('logger:peak.events.loop') def runUntil(self, eventSource, suppressErrors=False, idle=sleep): running = [True] exit = [] tick = self.tick time_available = self.time_available adapt(eventSource, IEventSource).addCallback( lambda s, e: [running.pop(), exit.append(e)]) if suppressErrors: def tick(exit, doTick=tick): try: doTick(exit) except: self.log.exception("Unexpected error in event loop:") while running: tick(exit) if running: delay = time_available() if delay is None: raise StopIteration("Nothing scheduled to execute") if delay > 0: idle(delay) return exit.pop()
class AnyOf(object): """Union of multiple event sources Example usage:: timedOut = scheduler.timeout(30) untilSomethingHappens = events.AnyOf(stream.dataRcvd, timedOut) while not timedOut(): yield untilSomethingHappens; src,evt = events.resume() if src is stream.dataRcvd: data = event print data 'AnyOf' fires callbacks whenever any of its actual event sources fire (and allows tasks to continue if any of its actual event sources allow it). The 'event' it supplies to its user is actually a '(source,event)' tuple as shown above, so you can distinguish which of the actual event sources fired, as well as receive the event from it. Note that callbacks registered with an 'AnyOf' instance will fire at most once, even if more than one of the original event sources fires. Thus, you should not assume in a callback or task that the event you received is the only one that has occurred. This is especially true in scheduled tasks, where many things may happen between the triggering of an event and the resumption of a task that was waiting for the event. """ __slots__ = '_sources' protocols.advise(instancesProvide=[IEventSource]) def __new__(klass, *sources): if sources: # Flatten sources srclist = [] for src in adapt(sources, protocols.sequenceOf(IEventSource)): if isinstance(src, AnyOf): srclist.extend(src._sources) else: srclist.append(src) # Eliminate duplicates srcids = {} sources = tuple([ src for src in srclist if src not in srcids and srcids.setdefault(src, True) ]) # Return the result if len(sources) == 1: return adapt(sources[0], IEventSource) else: self = object.__new__(klass) self._sources = sources return self raise ValueError, "AnyOf must be called with one or more IEventSources" def nextAction(self, task=None, state=None): """See 'events.ITaskSwitch.nextAction()'""" for source in self._sources: action = source.nextAction() if action: flag = source.nextAction(task, state) if state is not None: state.YIELD((source, state.lastEvent)) return flag if task is not None: self.addCallback(task.step) def addCallback(self, func): """See 'events.IEventSource.addCallback()'""" cancels = [] def onceOnly(source, event): if cancels: while cancels: cancels.pop()() return func(self, (source, event)) cancels = [source.addCallback(onceOnly) for source in self._sources] return lambda: [(c(), cancels.remove(c)) for c in cancels]
class Selector(binding.Component): """Simple task-based implementation of an ISelector""" protocols.advise(instancesProvide=[ISelector]) sigsrc = binding.Obtain(ISignalSource) haveSignal = signals = binding.Delegate('sigsrc') cache = binding.Make([dict, dict, dict]) rwe = binding.Make([dict, dict, dict]) count = binding.Make(lambda: sources.Semaphore(0)) scheduler = binding.Obtain(IScheduler) checkInterval = binding.Obtain( PropertyName('peak.running.reactor.checkInterval')) sleep = binding.Obtain('import:time.sleep') select = binding.Obtain('import:select.select') _error = binding.Obtain('import:select.error') def readable(self, stream): """'IEventSource' that fires when 'stream' is readable""" return self._getEvent(0, stream) def writable(self, stream): """'IEventSource' that fires when 'stream' is writable""" return self._getEvent(1, stream) def exceptional(self, stream): """'IEventSource' that fires when 'stream' is in error/out-of-band""" return self._getEvent(2, stream) def monitor(self): r, w, e = self.rwe count = self.count sleep = self.scheduler.sleep() time_available = self.scheduler.time_available select = self.select error = self._error while True: yield count resume() # wait until there are selectables yield sleep resume() # ensure we are in top-level loop delay = time_available() if delay is None: delay = self.checkInterval try: fr, fw, fe = self.select(r.keys(), w.keys(), e.keys(), delay) except error, v: if v.args[0] == EINTR: continue # signal received during select, try again else: raise for fired, events in (fe, e), (fr, r), (fw, w): for stream in fired: events[stream].send(True)
class Interrupt(object): """Interrupt a task with an error if specified event occurs Usage:: try: yield events.Interrupt( stream.readline(), scheduler.timeout(5) ) line = events.resume() except events.Interruption: print "readline() took more than 5 seconds" An 'Interrupt' object is an 'events.ITaskSwitch', so you can only use it within a task, and you cannot set callbacks on it. If the supplied generator/iterator exits for any reason, the interruption is cancelled. Also note that because generator objects are not reusable, neither are 'Interrupt' objects. You must create an 'Interrupt' for each desired invocation of the applicable generator. However, the called generator need not create an additional 'Interrupt' for any nested generator calls, even though multiple interrupts may be active at the same time. This allows you to do things like e.g. set one timeout for each line of data being received, and another timeout for receiving an entire email. """ __slots__ = 'iterator', 'source', 'errorType' protocols.advise(instancesProvide=[ITaskSwitch], ) def __init__(self, iterator, eventSource, errorType=Interruption): """'Interrupt(iterator,eventSource,errorType=Interruption)' Wrap execution of 'iterator' so that it will raise 'errorType' if 'eventSource' fires before 'iterator' exits (or aborts), assuming that the 'Interrupt' is yielded to a task.""" self.iterator = adapt(iterator, IProcedure) self.source = adapt(eventSource, IEventSource) self.errorType = errorType def nextAction(self, task=None, state=None): if state is not None: cancelled = Condition(False) def canceller(): yield self.iterator cancelled.set(True) yield resume() def interrupt(source, event): if not cancelled(): state.CALL(doInterrupt()) task.step(source, event) def doInterrupt(): raise self.errorType(resume()) yield None state.CALL(canceller()) self.source.addCallback(interrupt) return True
class Observable(object): """Base class for a generic event source You may subclass this class to create other kinds of event sources: change the 'singleFire' class attribute to 'False' in your subclass if you would like for events to be broadcast to all callbacks, whether they accept or reject the event. """ __slots__ = '_callbacks', '__weakref__', '_disabled', '_savedEvents' singleFire = True overrunOK = True protocols.advise(instancesProvide=[IEventSource]) def __init__(self): self._callbacks = [] self._disabled = 0 def nextAction(self, task=None, state=None): """See 'events.ITaskSwitch.nextAction()'""" if task is not None: self.addCallback(task.step) def addCallback(self, func): """See 'events.IEventSource.addCallback()'""" cb = self._callbacks item = (func, object()) cb.append(item) return lambda: (item in cb) and cb.remove(item) def disable(self): """Pause event callbacks""" if not self._disabled: self._savedEvents = [] self._disabled += 1 def enable(self): """Resume and send saved events""" if not self._disabled: raise ValueError("More enable() calls than disable() calls", self) self._disabled -= 1 if not self._disabled: while self._savedEvents: self._fire(self._savedEvents.pop(0)) del self._savedEvents def _fire(self, event): if self._disabled: self._buffer(event) return callbacks = self._callbacks count = len(callbacks) if self.singleFire: while count and callbacks: if callbacks.pop(0)[0](self, event): return count -= 1 else: while count and callbacks: callbacks.pop(0)[0](self, event) count -= 1 def _buffer(self, event): saved = self._savedEvents if self.overrunOK: if saved: del saved[0] elif len(saved) >= len(self._callbacks): raise ValueError("Can't buffer event", self, event) saved.append(event)