Created
June 12, 2014 16:38
-
-
Save takluyver/9619942351cdc571a302 to your computer and use it in GitHub Desktop.
Eventful data structures for IPython widgets
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
import time | |
class EventfulDict(dict): | |
"""Eventful dictionary""" | |
def __init__(self, *args, **kwargs): | |
"""Sleep is an optional float that allows you to tell the | |
dictionary to hang for the given amount of seconds on each | |
event. This is usefull for animations.""" | |
self._sleep = kwargs.get('sleep', 0.0) | |
self._add_callbacks = [] | |
self._del_callbacks = [] | |
self._set_callbacks = [] | |
dict.__init__(self, *args, **kwargs) | |
def on_add(self, callback, remove=False): | |
self._register_callback(self._add_callbacks, callback, remove) | |
def on_del(self, callback, remove=False): | |
self._register_callback(self._del_callbacks, callback, remove) | |
def on_set(self, callback, remove=False): | |
self._register_callback(self._set_callbacks, callback, remove) | |
def _register_callback(self, callback_list, callback, remove=False): | |
if callable(callback): | |
if remove and callback in callback_list: | |
callback_list.remove(callback) | |
elif not remove and not callback in callback_list: | |
callback_list.append(callback) | |
else: | |
raise Exception('Callback must be callable.') | |
def _handle_add(self, key, value): | |
self._try_callbacks(self._add_callbacks, key, value) | |
self._try_sleep() | |
def _handle_del(self, key): | |
self._try_callbacks(self._del_callbacks, key) | |
self._try_sleep() | |
def _handle_set(self, key, value): | |
self._try_callbacks(self._set_callbacks, key, value) | |
self._try_sleep() | |
def _try_callbacks(self, callback_list, *pargs, **kwargs): | |
for callback in callback_list: | |
callback(*pargs, **kwargs) | |
def _try_sleep(self): | |
if self._sleep > 0.0: | |
time.sleep(self._sleep) | |
def __setitem__(self, key, value): | |
return_val = None | |
exists = False | |
if key in self: | |
exists = True | |
# If the user sets the property to a new dict, make the dict | |
# eventful and listen to the changes of it ONLY if it is not | |
# already eventful. Any modification to this new dict will | |
# fire a set event of the parent dict. | |
if isinstance(value, dict) and not isinstance(value, EventfulDict): | |
new_dict = EventfulDict(value) | |
def handle_change(*pargs, **kwargs): | |
self._try_callbacks(self._set_callbacks, key, dict.__getitem__(self, key)) | |
new_dict.on_add(handle_change) | |
new_dict.on_del(handle_change) | |
new_dict.on_set(handle_change) | |
return_val = dict.__setitem__(self, key, new_dict) | |
else: | |
return_val = dict.__setitem__(self, key, value) | |
if exists: | |
self._handle_set(key, value) | |
else: | |
self._handle_add(key, value) | |
return return_val | |
def __delitem__(self, key): | |
return_val = dict.__delitem__(self, key) | |
self._handle_del(key) | |
return return_val | |
def pop(self, key): | |
return_val = dict.pop(self, key) | |
if key in self: | |
self._handle_del(key) | |
return return_val | |
def popitem(self): | |
popped = dict.popitem(self) | |
if popped is not None and popped[0] is not None: | |
self._handle_del(popped[0]) | |
return popped | |
def update(self, other_dict): | |
for (key, value) in other_dict.items(): | |
self[key] = value | |
def clear(self): | |
for key in list(self.keys()): | |
del self[key] |
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
"""NetworkX graphs do not have events that can be listened to. In order to | |
watch the NetworkX graph object for changes a custom eventful graph object must | |
be created. The custom eventful graph object will inherit from the base graph | |
object and use special eventful dictionaries instead of standard Python dict | |
instances. Because NetworkX nests dictionaries inside dictionaries, it's | |
important that the eventful dictionary is capable of recognizing when a | |
dictionary value is set to another dictionary instance. When this happens, the | |
eventful dictionary needs to also make the new dictionary an eventful | |
dictionary. This allows the eventful dictionary to listen to changes made to | |
dictionaries within dictionaries.""" | |
import networkx | |
from networkx.generators.classic import empty_graph | |
from eventful_dict import EventfulDict | |
class EventfulGraph(networkx.Graph): | |
_constructed_callback = None | |
@staticmethod | |
def on_constructed(callback): | |
"""Register a callback to be called when a graph is constructed.""" | |
if callback is None or callable(callback): | |
EventfulGraph._constructed_callback = callback | |
def __init__(self, *pargs, **kwargs): | |
"""Initialize a graph with edges, name, graph attributes. | |
Parameters | |
sleep: float | |
optional float that allows you to tell the | |
dictionary to hang for the given amount of seconds on each | |
event. This is usefull for animations.""" | |
super(EventfulGraph, self).__init__(*pargs, **kwargs) | |
# Override internal dictionaries with custom eventful ones. | |
sleep = kwargs.get('sleep', 0.0) | |
self.graph = EventfulDict(self.graph, sleep=sleep) | |
self.node = EventfulDict(self.node, sleep=sleep) | |
self.adj = EventfulDict(self.adj, sleep=sleep) | |
# Notify callback of construction event. | |
if EventfulGraph._constructed_callback: | |
EventfulGraph._constructed_callback(self) | |
def empty_eventfulgraph_hook(*pargs, **kwargs): | |
def wrapped(*wpargs, **wkwargs): | |
"""Wrapper for networkx.generators.classic.empty_graph(...)""" | |
wkwargs['create_using'] = EventfulGraph(*pargs, **kwargs) | |
return empty_graph(*wpargs, **wkwargs) | |
return wrapped |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment