Skip to content

Instantly share code, notes, and snippets.

@bbengfort
Created February 16, 2016 13:27
Show Gist options
  • Save bbengfort/b5c059e352b3b04cfc4d to your computer and use it in GitHub Desktop.
Save bbengfort/b5c059e352b3b04cfc4d to your computer and use it in GitHub Desktop.
Event/Event Listener that implements the observer pattern (from Mortar).
from copy import copy, deepcopy
from collections import defaultdict
class Event(object):
"""
Basis for all events that are dispatched by the EventDispatcher.
"""
# Internal event "types"
ACTIVATE = "activate"
DEACTIVATE = "deactivate"
ADDED = "added'"
CANCEL = "cancel"
CHANGE = "change"
OPEN = "open"
CLOSE = "close"
COMPLETE = "complete"
CONNECT = "connect"
DISCONNECT = "disconnect"
def __init__(self, etype, data=None, target=None):
self.type = etype # A string representing the event type
self.target = target # An object that triggered the event
self.data = data # Additional payload data
def __str__(self):
return "Event: {} from {}".format(self.type, self.target)
def clone(self):
"""
As events are passed to callbacks, new events are cloned to ensure
that each callback gets original data rather than manipulated data.
"""
return copy(self)
class EventDispatcher(object):
"""
This is the base subject class that allows the registration of listeners
and dispatches events accordingly. Note that this pattern does not allow
for events to be canceled or modified, all callbacks get the same data.
"""
def __init__(self):
self.callbacks = defaultdict(list)
def register(self, etypes, callback):
"""
Add an event listener (any callable) and bind it to a specific event
type or to a list of event types.
"""
if isinstance(etypes, basestring):
etypes = [etypes,]
for etype in etypes:
self.callbacks[etype].append(callback)
def deregister(self, callback, etype=None):
"""
Remove a callback from a specific event type, a list of event types.
If etype is None, then the callback is removed from all events.
"""
if isinstance(etypes, basestring):
etypes = [etypes,]
if etypes is None:
etypes = self.callbacks.keys()
for etype in etypes:
self.callbacks[etype].remove(callback)
def dispatch(self, event):
"""
Dispatches an event to all listeners.
"""
if event.target is None:
event.target = self
# Don't do anything if we don't have callbacks for the event!
if event.type not in callbacks:
return
# Clone callbacks to ensure that a callback can't register itself
# again and cause an infinite loop of event handling!
callbacks = deepcopy(self.callbacks)
for callback in callbacks[event.type]:
callback(event.clone())
def has_event(self, event):
"""
Checks if the event has a callback registered to it.
"""
if isinstance(event, Event):
event = event.type
return event in self.callbacks
def has_callback(self, callback, etypes=None):
"""
Checks if the callback is registered for a specific event, a list of
event types, or if etype is None, if it is registered for any event.
"""
if etypes is not None:
if isinstance(etypes, basestring):
etypes = [etypes,]
for etype in etypes:
if callback not in self.callbacks[etype]:
return False
return True
for callbacks in self.callbacks.values():
if callback in callbacks:
return True
def __len__(self):
"""
Returns the total number of callbacks registered.
"""
return sum(len(callbacks) for callbacks in self.callbacks.values())
def __str__(self):
return "{} with {} event types and {} callbacks".format(
self.__class__.__name__, len(self.callbacks), len(self)
)
def __contains__(self, obj):
return self.has_event(obj) or self.has_callback(obj)
def __iter__(self):
"""
Generates tuples of (event type, callback) pairs.
"""
for etype, callbacks in self.callbacks.iteritems():
for callback in callbacks:
yield (etype, callback)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment