diff --git a/IPython/html/static/widgets/js/widget.js b/IPython/html/static/widgets/js/widget.js
index 29193e6..9b2fa4d 100644
--- a/IPython/html/static/widgets/js/widget.js
+++ b/IPython/html/static/widgets/js/widget.js
@@ -213,7 +213,7 @@ define(["widgets/js/manager",
var that = this;
var packed;
if (value instanceof Backbone.Model) {
- return value.id;
+ return "IPY_MODEL_" + value.id;
} else if ($.isArray(value)) {
packed = [];
@@ -252,13 +252,15 @@ define(["widgets/js/manager",
});
return unpacked;
+ } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
+ var model = this.widget_manager.get_model(value.slice(10, value.length));
+ if (model) {
+ return model;
+ } else {
+ return value;
+ }
} else {
- var model = this.widget_manager.get_model(value);
- if (model) {
- return model;
- } else {
return value;
- }
}
},
diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py
index 4131826..345a4c7 100644
--- a/IPython/html/widgets/widget.py
+++ b/IPython/html/widgets/widget.py
@@ -195,8 +195,15 @@ class Widget(LoggingConfigurable):
A single property's name to get.
"""
keys = self.keys if key is None else [key]
- return {k: self._pack_widgets(getattr(self, k)) for k in keys}
-
+ state = {}
+ for k in keys:
+ f = self.trait_metadata(k, 'to_json')
+ if f is None:
+ f = self._trait_to_json
+ value = getattr(self, k)
+ state[k] = f(value)
+ return state
+
def send(self, content):
"""Sends a custom msg to the widget model in the front-end.
@@ -280,7 +287,10 @@ class Widget(LoggingConfigurable):
"""Called when a state is received from the front-end."""
for name in self.keys:
if name in sync_data:
- value = self._unpack_widgets(sync_data[name])
+ f = self.trait_metadata(name, 'from_json')
+ if f is None:
+ f = self._trait_from_json
+ value = f(sync_data[name])
with self._lock_property(name, value):
setattr(self, name, value)
@@ -299,31 +309,34 @@ class Widget(LoggingConfigurable):
"""Called when a view has been displayed for this widget instance"""
self._display_callbacks(self, **kwargs)
- def _pack_widgets(self, x):
- """Recursively converts all widget instances to model id strings.
+ def _trait_to_json(self, x):
+ """Convert a trait value to json
- Children widgets will be stored and transmitted to the front-end by
- their model ids. Return value must be JSON-able."""
+ Traverse lists/tuples and dicts and serialize their values as well.
+ Replace any widgets with their model_id
+ """
if isinstance(x, dict):
- return {k: self._pack_widgets(v) for k, v in x.items()}
+ return {k: self._trait_to_json(v) for k, v in x.items()}
elif isinstance(x, (list, tuple)):
- return [self._pack_widgets(v) for v in x]
+ return [self._trait_to_json(v) for v in x]
elif isinstance(x, Widget):
- return x.model_id
+ return "IPY_MODEL_" + x.model_id
else:
return x # Value must be JSON-able
- def _unpack_widgets(self, x):
- """Recursively converts all model id strings to widget instances.
+ def _trait_from_json(self, x):
+ """Convert json values to objects
- Children widgets will be stored and transmitted to the front-end by
- their model ids."""
+ Replace any strings representing valid model id values to Widget references.
+ """
if isinstance(x, dict):
- return {k: self._unpack_widgets(v) for k, v in x.items()}
+ return {k: self._trait_from_json(v) for k, v in x.items()}
elif isinstance(x, (list, tuple)):
- return [self._unpack_widgets(v) for v in x]
- elif isinstance(x, string_types):
- return x if x not in Widget.widgets else Widget.widgets[x]
+ return [self._trait_from_json(v) for v in x]
+ elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
+ # we want to support having child widgets at any level in a hierarchy
+ # trusting that a widget UUID will not appear out in the wild
+ return Widget.widgets[x]
else:
return x
diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py
index 9dbeb91..8992345 100644
--- a/IPython/utils/tests/test_traitlets.py
+++ b/IPython/utils/tests/test_traitlets.py
@@ -560,6 +560,27 @@ class TestInstance(TestCase):
self.assertRaises(TraitError, setattr, a, 'inst', Bar)
self.assertRaises(TraitError, setattr, a, 'inst', Bah())
+ def test_default_klass(self):
+ class Foo(object): pass
+ class Bar(Foo): pass
+ class Bah(object): pass
+
+ class FooInstance(Instance):
+ klass = Foo
+
+ class A(HasTraits):
+ inst = FooInstance()
+
+ a = A()
+ self.assertTrue(a.inst is None)
+ a.inst = Foo()
+ self.assertTrue(isinstance(a.inst, Foo))
+ a.inst = Bar()
+ self.assertTrue(isinstance(a.inst, Foo))
+ self.assertRaises(TraitError, setattr, a, 'inst', Foo)
+ self.assertRaises(TraitError, setattr, a, 'inst', Bar)
+ self.assertRaises(TraitError, setattr, a, 'inst', Bah())
+
def test_unique_default_value(self):
class Foo(object): pass
class A(HasTraits):
diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py
index 5234713..1a68b04 100644
--- a/IPython/utils/traitlets.py
+++ b/IPython/utils/traitlets.py
@@ -809,8 +809,12 @@ class Instance(ClassBasedTraitType):
"""A trait whose value must be an instance of a specified class.
The value can also be an instance of a subclass of the specified class.
+
+ Subclasses can declare default classes by overriding the klass attribute
"""
+ klass = None
+
def __init__(self, klass=None, args=None, kw=None,
allow_none=True, **metadata ):
"""Construct an Instance trait.
@@ -836,14 +840,17 @@ class Instance(ClassBasedTraitType):
-----
If both ``args`` and ``kw`` are None, then the default value is None.
If ``args`` is a tuple and ``kw`` is a dict, then the default is
- created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
- not (but not both), None is replace by ``()`` or ``{}``.
+ created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
+ None, the None is replaced by ``()`` or ``{}``, respectively.
"""
-
- if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types))):
- raise TraitError('The klass argument must be a class'
- ' you gave: %r' % klass)
- self.klass = klass
+ if klass is None:
+ klass = self.klass
+
+ if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
+ self.klass = klass
+ else:
+ raise TraitError('The klass attribute must be a class'
+ ' not: %r' % klass)
# self.klass is a class, so handle default_value
if args is None and kw is None: