##// END OF EJS Templates
Merge pull request #6128 from jasongrout/widget-trait-serialization...
Brian E. Granger -
r17330:3c3d1ae5 merge
parent child Browse files
Show More
@@ -213,7 +213,7 b' define(["widgets/js/manager",'
213 var that = this;
213 var that = this;
214 var packed;
214 var packed;
215 if (value instanceof Backbone.Model) {
215 if (value instanceof Backbone.Model) {
216 return value.id;
216 return "IPY_MODEL_" + value.id;
217
217
218 } else if ($.isArray(value)) {
218 } else if ($.isArray(value)) {
219 packed = [];
219 packed = [];
@@ -252,13 +252,15 b' define(["widgets/js/manager",'
252 });
252 });
253 return unpacked;
253 return unpacked;
254
254
255 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
256 var model = this.widget_manager.get_model(value.slice(10, value.length));
257 if (model) {
258 return model;
259 } else {
260 return value;
261 }
255 } else {
262 } else {
256 var model = this.widget_manager.get_model(value);
257 if (model) {
258 return model;
259 } else {
260 return value;
263 return value;
261 }
262 }
264 }
263 },
265 },
264
266
@@ -195,8 +195,15 b' class Widget(LoggingConfigurable):'
195 A single property's name to get.
195 A single property's name to get.
196 """
196 """
197 keys = self.keys if key is None else [key]
197 keys = self.keys if key is None else [key]
198 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
198 state = {}
199
199 for k in keys:
200 f = self.trait_metadata(k, 'to_json')
201 if f is None:
202 f = self._trait_to_json
203 value = getattr(self, k)
204 state[k] = f(value)
205 return state
206
200 def send(self, content):
207 def send(self, content):
201 """Sends a custom msg to the widget model in the front-end.
208 """Sends a custom msg to the widget model in the front-end.
202
209
@@ -280,7 +287,10 b' class Widget(LoggingConfigurable):'
280 """Called when a state is received from the front-end."""
287 """Called when a state is received from the front-end."""
281 for name in self.keys:
288 for name in self.keys:
282 if name in sync_data:
289 if name in sync_data:
283 value = self._unpack_widgets(sync_data[name])
290 f = self.trait_metadata(name, 'from_json')
291 if f is None:
292 f = self._trait_from_json
293 value = f(sync_data[name])
284 with self._lock_property(name, value):
294 with self._lock_property(name, value):
285 setattr(self, name, value)
295 setattr(self, name, value)
286
296
@@ -299,31 +309,34 b' class Widget(LoggingConfigurable):'
299 """Called when a view has been displayed for this widget instance"""
309 """Called when a view has been displayed for this widget instance"""
300 self._display_callbacks(self, **kwargs)
310 self._display_callbacks(self, **kwargs)
301
311
302 def _pack_widgets(self, x):
312 def _trait_to_json(self, x):
303 """Recursively converts all widget instances to model id strings.
313 """Convert a trait value to json
304
314
305 Children widgets will be stored and transmitted to the front-end by
315 Traverse lists/tuples and dicts and serialize their values as well.
306 their model ids. Return value must be JSON-able."""
316 Replace any widgets with their model_id
317 """
307 if isinstance(x, dict):
318 if isinstance(x, dict):
308 return {k: self._pack_widgets(v) for k, v in x.items()}
319 return {k: self._trait_to_json(v) for k, v in x.items()}
309 elif isinstance(x, (list, tuple)):
320 elif isinstance(x, (list, tuple)):
310 return [self._pack_widgets(v) for v in x]
321 return [self._trait_to_json(v) for v in x]
311 elif isinstance(x, Widget):
322 elif isinstance(x, Widget):
312 return x.model_id
323 return "IPY_MODEL_" + x.model_id
313 else:
324 else:
314 return x # Value must be JSON-able
325 return x # Value must be JSON-able
315
326
316 def _unpack_widgets(self, x):
327 def _trait_from_json(self, x):
317 """Recursively converts all model id strings to widget instances.
328 """Convert json values to objects
318
329
319 Children widgets will be stored and transmitted to the front-end by
330 Replace any strings representing valid model id values to Widget references.
320 their model ids."""
331 """
321 if isinstance(x, dict):
332 if isinstance(x, dict):
322 return {k: self._unpack_widgets(v) for k, v in x.items()}
333 return {k: self._trait_from_json(v) for k, v in x.items()}
323 elif isinstance(x, (list, tuple)):
334 elif isinstance(x, (list, tuple)):
324 return [self._unpack_widgets(v) for v in x]
335 return [self._trait_from_json(v) for v in x]
325 elif isinstance(x, string_types):
336 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
326 return x if x not in Widget.widgets else Widget.widgets[x]
337 # we want to support having child widgets at any level in a hierarchy
338 # trusting that a widget UUID will not appear out in the wild
339 return Widget.widgets[x]
327 else:
340 else:
328 return x
341 return x
329
342
@@ -560,6 +560,27 b' class TestInstance(TestCase):'
560 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
560 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
561 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
561 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
562
562
563 def test_default_klass(self):
564 class Foo(object): pass
565 class Bar(Foo): pass
566 class Bah(object): pass
567
568 class FooInstance(Instance):
569 klass = Foo
570
571 class A(HasTraits):
572 inst = FooInstance()
573
574 a = A()
575 self.assertTrue(a.inst is None)
576 a.inst = Foo()
577 self.assertTrue(isinstance(a.inst, Foo))
578 a.inst = Bar()
579 self.assertTrue(isinstance(a.inst, Foo))
580 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
581 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
582 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
583
563 def test_unique_default_value(self):
584 def test_unique_default_value(self):
564 class Foo(object): pass
585 class Foo(object): pass
565 class A(HasTraits):
586 class A(HasTraits):
@@ -809,8 +809,12 b' class Instance(ClassBasedTraitType):'
809 """A trait whose value must be an instance of a specified class.
809 """A trait whose value must be an instance of a specified class.
810
810
811 The value can also be an instance of a subclass of the specified class.
811 The value can also be an instance of a subclass of the specified class.
812
813 Subclasses can declare default classes by overriding the klass attribute
812 """
814 """
813
815
816 klass = None
817
814 def __init__(self, klass=None, args=None, kw=None,
818 def __init__(self, klass=None, args=None, kw=None,
815 allow_none=True, **metadata ):
819 allow_none=True, **metadata ):
816 """Construct an Instance trait.
820 """Construct an Instance trait.
@@ -836,14 +840,17 b' class Instance(ClassBasedTraitType):'
836 -----
840 -----
837 If both ``args`` and ``kw`` are None, then the default value is None.
841 If both ``args`` and ``kw`` are None, then the default value is None.
838 If ``args`` is a tuple and ``kw`` is a dict, then the default is
842 If ``args`` is a tuple and ``kw`` is a dict, then the default is
839 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
843 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
840 not (but not both), None is replace by ``()`` or ``{}``.
844 None, the None is replaced by ``()`` or ``{}``, respectively.
841 """
845 """
842
846 if klass is None:
843 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types))):
847 klass = self.klass
844 raise TraitError('The klass argument must be a class'
848
845 ' you gave: %r' % klass)
849 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
846 self.klass = klass
850 self.klass = klass
851 else:
852 raise TraitError('The klass attribute must be a class'
853 ' not: %r' % klass)
847
854
848 # self.klass is a class, so handle default_value
855 # self.klass is a class, so handle default_value
849 if args is None and kw is None:
856 if args is None and kw is None:
General Comments 0
You need to be logged in to leave comments. Login now