##// 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 213 var that = this;
214 214 var packed;
215 215 if (value instanceof Backbone.Model) {
216 return value.id;
216 return "IPY_MODEL_" + value.id;
217 217
218 218 } else if ($.isArray(value)) {
219 219 packed = [];
@@ -252,13 +252,15 b' define(["widgets/js/manager",'
252 252 });
253 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 262 } else {
256 var model = this.widget_manager.get_model(value);
257 if (model) {
258 return model;
259 } else {
260 263 return value;
261 }
262 264 }
263 265 },
264 266
@@ -195,8 +195,15 b' class Widget(LoggingConfigurable):'
195 195 A single property's name to get.
196 196 """
197 197 keys = self.keys if key is None else [key]
198 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
199
198 state = {}
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 207 def send(self, content):
201 208 """Sends a custom msg to the widget model in the front-end.
202 209
@@ -280,7 +287,10 b' class Widget(LoggingConfigurable):'
280 287 """Called when a state is received from the front-end."""
281 288 for name in self.keys:
282 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 294 with self._lock_property(name, value):
285 295 setattr(self, name, value)
286 296
@@ -299,31 +309,34 b' class Widget(LoggingConfigurable):'
299 309 """Called when a view has been displayed for this widget instance"""
300 310 self._display_callbacks(self, **kwargs)
301 311
302 def _pack_widgets(self, x):
303 """Recursively converts all widget instances to model id strings.
312 def _trait_to_json(self, x):
313 """Convert a trait value to json
304 314
305 Children widgets will be stored and transmitted to the front-end by
306 their model ids. Return value must be JSON-able."""
315 Traverse lists/tuples and dicts and serialize their values as well.
316 Replace any widgets with their model_id
317 """
307 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 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 322 elif isinstance(x, Widget):
312 return x.model_id
323 return "IPY_MODEL_" + x.model_id
313 324 else:
314 325 return x # Value must be JSON-able
315 326
316 def _unpack_widgets(self, x):
317 """Recursively converts all model id strings to widget instances.
327 def _trait_from_json(self, x):
328 """Convert json values to objects
318 329
319 Children widgets will be stored and transmitted to the front-end by
320 their model ids."""
330 Replace any strings representing valid model id values to Widget references.
331 """
321 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 334 elif isinstance(x, (list, tuple)):
324 return [self._unpack_widgets(v) for v in x]
325 elif isinstance(x, string_types):
326 return x if x not in Widget.widgets else Widget.widgets[x]
335 return [self._trait_from_json(v) for v in x]
336 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
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 340 else:
328 341 return x
329 342
@@ -560,6 +560,27 b' class TestInstance(TestCase):'
560 560 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
561 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 584 def test_unique_default_value(self):
564 585 class Foo(object): pass
565 586 class A(HasTraits):
@@ -809,8 +809,12 b' class Instance(ClassBasedTraitType):'
809 809 """A trait whose value must be an instance of a specified class.
810 810
811 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 818 def __init__(self, klass=None, args=None, kw=None,
815 819 allow_none=True, **metadata ):
816 820 """Construct an Instance trait.
@@ -836,14 +840,17 b' class Instance(ClassBasedTraitType):'
836 840 -----
837 841 If both ``args`` and ``kw`` are None, then the default value is None.
838 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
840 not (but not both), None is replace by ``()`` or ``{}``.
843 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
844 None, the None is replaced by ``()`` or ``{}``, respectively.
841 845 """
842
843 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types))):
844 raise TraitError('The klass argument must be a class'
845 ' you gave: %r' % klass)
846 self.klass = klass
846 if klass is None:
847 klass = self.klass
848
849 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
850 self.klass = klass
851 else:
852 raise TraitError('The klass attribute must be a class'
853 ' not: %r' % klass)
847 854
848 855 # self.klass is a class, so handle default_value
849 856 if args is None and kw is None:
General Comments 0
You need to be logged in to leave comments. Login now