Created
February 16, 2016 13:27
-
-
Save bbengfort/b5c059e352b3b04cfc4d to your computer and use it in GitHub Desktop.
Event/Event Listener that implements the observer pattern (from Mortar).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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