A fairly decent way to bind together some IPython widgets for quick prototyping. Made of evil python __magic__
.
Bubbles: What the hell is that? McNulty: Baltimore knot. Bubbles: Baltimore knot? What the hell is a Baltimore knot? McNulty: I dunno, but it's never the same thing twice.
– The Wire: Undertow
from IPython.html import widgets
from IPython.utils import traitlets
from IPython.display import display
class Chase(object):
"""
An organizer for Wires.
"""
def __init__(self, **kwargs):
self.__dict__["_widgets"] = dict([
(name, Wire(widget))
for name, widget in kwargs.items()
])
def __call__(self, wired, **kwargs):
"""
Decorator-style.
"""
def wrapper(func):
wire, trait = wired
setattr(wire, trait, (kwargs, func))
return wrapper
def __dir__(self):
return self._widgets.keys()
def __getattr__(self, name):
return self._widgets[name]
def __setattr__(self, name, widget):
self._widgets[name] = Wire(widget)
class Wire(object):
"""
A wrapper for a widget that lets us talk about its traits easily.
"""
def __init__(self, widget):
self.__dict__["_widget"] = widget
def __dir__(self):
return self._widget.trait_names()
def __getattr__(self, name):
return (self, name)
def __setattr__(self, name, value):
if hasattr(value[-1], '__call__'):
fn = value[-1]
sources = value[:-1]
if len(sources) == 1 and isinstance(sources[0], dict):
sources = sources[0]
def _change(changed, old, new):
args = dict([(arg, getattr(t[0]._widget, t[1]))
for arg, t in sources.items()])
setattr(self._widget, name, fn(**args))
[t[0]._widget.on_trait_change(_change, t[1])
for arg, t in sources.items()]
else:
def _change(name, old, new):
args = [getattr(w._widget, t) for w, t in sources]
setattr(self._widget, name, fn(*args))
[w._widget.on_trait_change(_change, t) for w, t in sources]
else:
w, t = value
w = w._widget
traitlets.link((self._widget, name), (w, t))
First, we set up some widgets.
texts = dict([
(i, widgets.TextWidget(description=i, value=i))
for i in "abcdef"
])
wire = Chase(**texts)
widgets.ContainerWidget(children=sorted(texts.values()))
Setting one trait pointer to another will make one value equal another, bidirectionally.
wire.a.value = wire.b.value
Now try changing the a
and b
boxes.
If one trait is derived from another trait, give the trait that changes and a transformer. They will be linked, but only forwards.
def but_reversed(b):
return b[::-1]
wire.c.value = wire.b.value, but_reversed
This is just a tuple, and any number of traits can be watched.
def joined_and_reversed(a, b):
return a + b[::-1]
wire.d.value = wire.a.value, wire.b.value, joined_and_reversed
Since this happens kinda often, you can also use a decorator form:
@wire(wire.e.value, a=wire.a.value)
def thrice(a):
return a * 3
Secretly, this is using a dictionary instead of a tuple in assignment, or:
def joined_differently(a, c):
return c + a
wire.f.value = dict(a=wire.a.value, c=wire.c.value), joined_differently
We proxy through what's connected, so you can autocomplete to see what widgets are wired.
dir(wire)
['a', 'b', 'c', 'd', 'e', 'f']
Also the traits on a particular widget.
dir(wire.a)
['_css', '_display_callbacks', '_model_name', '_msg_callbacks', '_property_lock', '_send_state_lock', '_states_to_send', '_view_name', 'comm', 'config', 'description', 'disabled', 'keys', 'log', 'msg_throttle', 'parent', 'placeholder', 'value', 'visible']