Skip to content

Instantly share code, notes, and snippets.

@takluyver
Created June 12, 2014 16:38
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save takluyver/9619942351cdc571a302 to your computer and use it in GitHub Desktop.
Save takluyver/9619942351cdc571a302 to your computer and use it in GitHub Desktop.
Eventful data structures for IPython widgets
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]
"""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