Show More
@@ -0,0 +1,50 b'' | |||||
|
1 | // Copyright (c) IPython Development Team. | |||
|
2 | // Distributed under the terms of the Modified BSD License. | |||
|
3 | ||||
|
4 | define([ | |||
|
5 | "base/js/utils" | |||
|
6 | ], function(utils){ | |||
|
7 | ||||
|
8 | return { | |||
|
9 | widget_serialization: { | |||
|
10 | deserialize: function deserialize_models(value, model) { | |||
|
11 | /** | |||
|
12 | * Replace model ids with models recursively. | |||
|
13 | */ | |||
|
14 | var unpacked; | |||
|
15 | if ($.isArray(value)) { | |||
|
16 | unpacked = []; | |||
|
17 | _.each(value, function(sub_value, key) { | |||
|
18 | unpacked.push(deserialize_models(sub_value, model)); | |||
|
19 | }); | |||
|
20 | return Promise.all(unpacked); | |||
|
21 | } else if (value instanceof Object) { | |||
|
22 | unpacked = {}; | |||
|
23 | _.each(value, function(sub_value, key) { | |||
|
24 | unpacked[key] = deserialize_models(sub_value, model); | |||
|
25 | }); | |||
|
26 | return utils.resolve_promises_dict(unpacked); | |||
|
27 | } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") { | |||
|
28 | // get_model returns a promise already | |||
|
29 | return model.widget_manager.get_model(value.slice(10, value.length)); | |||
|
30 | } else { | |||
|
31 | return Promise.resolve(value); | |||
|
32 | } | |||
|
33 | }, | |||
|
34 | }, | |||
|
35 | ||||
|
36 | list_of_numbers: { | |||
|
37 | deserialize: function (value, model) { | |||
|
38 | /* value is a DataView */ | |||
|
39 | /* create a float64 typed array */ | |||
|
40 | return new Float64Array(value.buffer) | |||
|
41 | }, | |||
|
42 | serialize: function (value, model) { | |||
|
43 | return value; | |||
|
44 | }, | |||
|
45 | } | |||
|
46 | } | |||
|
47 | ||||
|
48 | ||||
|
49 | ||||
|
50 | }); |
@@ -32,6 +32,7 b' define(["widgets/js/manager",' | |||||
32 | this.state_lock = null; |
|
32 | this.state_lock = null; | |
33 | this.id = model_id; |
|
33 | this.id = model_id; | |
34 | this.views = {}; |
|
34 | this.views = {}; | |
|
35 | this.serializers = {}; | |||
35 | this._resolve_received_state = {}; |
|
36 | this._resolve_received_state = {}; | |
36 |
|
37 | |||
37 | if (comm !== undefined) { |
|
38 | if (comm !== undefined) { | |
@@ -62,13 +63,13 b' define(["widgets/js/manager",' | |||||
62 | return Backbone.Model.apply(this); |
|
63 | return Backbone.Model.apply(this); | |
63 | }, |
|
64 | }, | |
64 |
|
65 | |||
65 | send: function (content, callbacks) { |
|
66 | send: function (content, callbacks, buffers) { | |
66 | /** |
|
67 | /** | |
67 | * Send a custom msg over the comm. |
|
68 | * Send a custom msg over the comm. | |
68 | */ |
|
69 | */ | |
69 | if (this.comm !== undefined) { |
|
70 | if (this.comm !== undefined) { | |
70 | var data = {method: 'custom', content: content}; |
|
71 | var data = {method: 'custom', content: content}; | |
71 | this.comm.send(data, callbacks); |
|
72 | this.comm.send(data, callbacks, {}, buffers); | |
72 | this.pending_msgs++; |
|
73 | this.pending_msgs++; | |
73 | } |
|
74 | } | |
74 | }, |
|
75 | }, | |
@@ -136,12 +137,37 b' define(["widgets/js/manager",' | |||||
136 | * Handle incoming comm msg. |
|
137 | * Handle incoming comm msg. | |
137 | */ |
|
138 | */ | |
138 | var method = msg.content.data.method; |
|
139 | var method = msg.content.data.method; | |
|
140 | ||||
139 | var that = this; |
|
141 | var that = this; | |
140 | switch (method) { |
|
142 | switch (method) { | |
141 | case 'update': |
|
143 | case 'update': | |
142 | this.state_change = this.state_change |
|
144 | this.state_change = this.state_change | |
143 | .then(function() { |
|
145 | .then(function() { | |
144 |
r |
|
146 | var state = msg.content.data.state || {}; | |
|
147 | var buffer_keys = msg.content.data.buffers || []; | |||
|
148 | var buffers = msg.buffers || []; | |||
|
149 | var metadata = msg.content.data.metadata || {}; | |||
|
150 | var i,k; | |||
|
151 | for (var i=0; i<buffer_keys.length; i++) { | |||
|
152 | k = buffer_keys[i]; | |||
|
153 | state[k] = buffers[i]; | |||
|
154 | } | |||
|
155 | ||||
|
156 | // for any metadata specifying a deserializer, set the | |||
|
157 | // state to a promise that resolves to the deserialized version | |||
|
158 | // also, store the serialization function for the attribute | |||
|
159 | var keys = Object.keys(metadata); | |||
|
160 | for (var i=0; i<keys.length; i++) { | |||
|
161 | k = keys[i]; | |||
|
162 | if (metadata[k] && metadata[k].serialization) { | |||
|
163 | that.serializers[k] = utils.load_class.apply(that, | |||
|
164 | metadata[k].serialization); | |||
|
165 | state[k] = that.deserialize(that.serializers[k], state[k]); | |||
|
166 | } | |||
|
167 | } | |||
|
168 | return utils.resolve_promises_dict(state); | |||
|
169 | }).then(function(state) { | |||
|
170 | return that.set_state(state); | |||
145 | }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true)) |
|
171 | }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true)) | |
146 | .then(function() { |
|
172 | .then(function() { | |
147 | var parent_id = msg.parent_header.msg_id; |
|
173 | var parent_id = msg.parent_header.msg_id; | |
@@ -152,7 +178,7 b' define(["widgets/js/manager",' | |||||
152 | }).catch(utils.reject("Couldn't resolve state request promise", true)); |
|
178 | }).catch(utils.reject("Couldn't resolve state request promise", true)); | |
153 | break; |
|
179 | break; | |
154 | case 'custom': |
|
180 | case 'custom': | |
155 | this.trigger('msg:custom', msg.content.data.content); |
|
181 | this.trigger('msg:custom', msg.content.data.content, msg.buffers); | |
156 | break; |
|
182 | break; | |
157 | case 'display': |
|
183 | case 'display': | |
158 | this.state_change = this.state_change.then(function() { |
|
184 | this.state_change = this.state_change.then(function() { | |
@@ -162,30 +188,39 b' define(["widgets/js/manager",' | |||||
162 | } |
|
188 | } | |
163 | }, |
|
189 | }, | |
164 |
|
190 | |||
|
191 | deserialize: function(serializer, value) { | |||
|
192 | // given a serializer dict and a value, | |||
|
193 | // return a promise for the deserialized value | |||
|
194 | var that = this; | |||
|
195 | return serializer.then(function(s) { | |||
|
196 | if (s.deserialize) { | |||
|
197 | return s.deserialize(value, that); | |||
|
198 | } else { | |||
|
199 | return value; | |||
|
200 | } | |||
|
201 | }); | |||
|
202 | }, | |||
|
203 | ||||
165 | set_state: function (state) { |
|
204 | set_state: function (state) { | |
166 | var that = this; |
|
205 | var that = this; | |
167 | // Handle when a widget is updated via the python side. |
|
206 | // Handle when a widget is updated via the python side. | |
168 | return this._unpack_models(state).then(function(state) { |
|
207 | return new Promise(function(resolve, reject) { | |
169 | that.state_lock = state; |
|
208 | that.state_lock = state; | |
170 | try { |
|
209 | try { | |
171 | WidgetModel.__super__.set.call(that, state); |
|
210 | WidgetModel.__super__.set.call(that, state); | |
172 | } finally { |
|
211 | } finally { | |
173 | that.state_lock = null; |
|
212 | that.state_lock = null; | |
174 | } |
|
213 | } | |
|
214 | resolve(); | |||
175 | }).catch(utils.reject("Couldn't set model state", true)); |
|
215 | }).catch(utils.reject("Couldn't set model state", true)); | |
176 | }, |
|
216 | }, | |
177 |
|
217 | |||
178 | get_state: function() { |
|
218 | get_state: function() { | |
179 | // Get the serializable state of the model. |
|
219 | // Get the serializable state of the model. | |
180 | var state = this.toJSON(); |
|
220 | // Equivalent to Backbone.Model.toJSON() | |
181 | for (var key in state) { |
|
221 | return _.clone(this.attributes); | |
182 | if (state.hasOwnProperty(key)) { |
|
|||
183 | state[key] = this._pack_models(state[key]); |
|
|||
184 | } |
|
|||
185 | } |
|
|||
186 | return state; |
|
|||
187 | }, |
|
222 | }, | |
188 |
|
223 | |||
189 | _handle_status: function (msg, callbacks) { |
|
224 | _handle_status: function (msg, callbacks) { | |
190 | /** |
|
225 | /** | |
191 | * Handle status msgs. |
|
226 | * Handle status msgs. | |
@@ -243,6 +278,19 b' define(["widgets/js/manager",' | |||||
243 | * Handle sync to the back-end. Called when a model.save() is called. |
|
278 | * Handle sync to the back-end. Called when a model.save() is called. | |
244 | * |
|
279 | * | |
245 | * Make sure a comm exists. |
|
280 | * Make sure a comm exists. | |
|
281 | ||||
|
282 | * Parameters | |||
|
283 | * ---------- | |||
|
284 | * method : create, update, patch, delete, read | |||
|
285 | * create/update always send the full attribute set | |||
|
286 | * patch - only send attributes listed in options.attrs, and if we are queuing | |||
|
287 | * up messages, combine with previous messages that have not been sent yet | |||
|
288 | * model : the model we are syncing | |||
|
289 | * will normally be the same as `this` | |||
|
290 | * options : dict | |||
|
291 | * the `attrs` key, if it exists, gives an {attr: value} dict that should be synced, | |||
|
292 | * otherwise, sync all attributes | |||
|
293 | * | |||
246 | */ |
|
294 | */ | |
247 | var error = options.error || function() { |
|
295 | var error = options.error || function() { | |
248 | console.error('Backbone sync error:', arguments); |
|
296 | console.error('Backbone sync error:', arguments); | |
@@ -252,8 +300,11 b' define(["widgets/js/manager",' | |||||
252 | return false; |
|
300 | return false; | |
253 | } |
|
301 | } | |
254 |
|
302 | |||
255 | // Delete any key value pairs that the back-end already knows about. |
|
303 | var attrs = (method === 'patch') ? options.attrs : model.get_state(options); | |
256 | var attrs = (method === 'patch') ? options.attrs : model.toJSON(options); |
|
304 | ||
|
305 | // the state_lock lists attributes that are currently be changed right now from a kernel message | |||
|
306 | // we don't want to send these non-changes back to the kernel, so we delete them out of attrs | |||
|
307 | // (but we only delete them if the value hasn't changed from the value stored in the state_lock | |||
257 | if (this.state_lock !== null) { |
|
308 | if (this.state_lock !== null) { | |
258 | var keys = Object.keys(this.state_lock); |
|
309 | var keys = Object.keys(this.state_lock); | |
259 | for (var i=0; i<keys.length; i++) { |
|
310 | for (var i=0; i<keys.length; i++) { | |
@@ -263,9 +314,7 b' define(["widgets/js/manager",' | |||||
263 | } |
|
314 | } | |
264 | } |
|
315 | } | |
265 | } |
|
316 | } | |
266 |
|
317 | |||
267 | // Only sync if there are attributes to send to the back-end. |
|
|||
268 | attrs = this._pack_models(attrs); |
|
|||
269 | if (_.size(attrs) > 0) { |
|
318 | if (_.size(attrs) > 0) { | |
270 |
|
319 | |||
271 | // If this message was sent via backbone itself, it will not |
|
320 | // If this message was sent via backbone itself, it will not | |
@@ -297,9 +346,7 b' define(["widgets/js/manager",' | |||||
297 | } else { |
|
346 | } else { | |
298 | // We haven't exceeded the throttle, send the message like |
|
347 | // We haven't exceeded the throttle, send the message like | |
299 | // normal. |
|
348 | // normal. | |
300 | var data = {method: 'backbone', sync_data: attrs}; |
|
349 | this.send_sync_message(attrs, callbacks); | |
301 | this.comm.send(data, callbacks); |
|
|||
302 | this.pending_msgs++; |
|
|||
303 | } |
|
350 | } | |
304 | } |
|
351 | } | |
305 | // Since the comm is a one-way communication, assume the message |
|
352 | // Since the comm is a one-way communication, assume the message | |
@@ -308,68 +355,71 b' define(["widgets/js/manager",' | |||||
308 | this._buffered_state_diff = {}; |
|
355 | this._buffered_state_diff = {}; | |
309 | }, |
|
356 | }, | |
310 |
|
357 | |||
311 | save_changes: function(callbacks) { |
|
|||
312 | /** |
|
|||
313 | * Push this model's state to the back-end |
|
|||
314 | * |
|
|||
315 | * This invokes a Backbone.Sync. |
|
|||
316 | */ |
|
|||
317 | this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks}); |
|
|||
318 | }, |
|
|||
319 |
|
358 | |||
320 |
|
|
359 | send_sync_message: function(attrs, callbacks) { | |
321 | /** |
|
360 | // prepare and send a comm message syncing attrs | |
322 | * Replace models with model ids recursively. |
|
|||
323 | */ |
|
|||
324 | var that = this; |
|
361 | var that = this; | |
325 | var packed; |
|
362 | // first, build a state dictionary with key=the attribute and the value | |
326 | if (value instanceof Backbone.Model) { |
|
363 | // being the value or the promise of the serialized value | |
327 | return "IPY_MODEL_" + value.id; |
|
364 | var state_promise_dict = {}; | |
328 |
|
365 | var keys = Object.keys(attrs); | ||
329 | } else if ($.isArray(value)) { |
|
366 | for (var i=0; i<keys.length; i++) { | |
330 | packed = []; |
|
367 | // bind k and v locally; needed since we have an inner async function using v | |
331 |
|
|
368 | (function(k,v) { | |
332 | packed.push(that._pack_models(sub_value)); |
|
369 | if (that.serializers[k]) { | |
333 | }); |
|
370 | state_promise_dict[k] = that.serializers[k].then(function(f) { | |
334 | return packed; |
|
371 | if (f.serialize) { | |
335 | } else if (value instanceof Date || value instanceof String) { |
|
372 | return f.serialize(v, that); | |
336 | return value; |
|
373 | } else { | |
337 | } else if (value instanceof Object) { |
|
374 | return v; | |
338 |
|
|
375 | } | |
339 | _.each(value, function(sub_value, key) { |
|
376 | }) | |
340 | packed[key] = that._pack_models(sub_value); |
|
377 | } else { | |
341 | }); |
|
378 | state_promise_dict[k] = v; | |
342 |
|
|
379 | } | |
343 |
|
380 | })(keys[i], attrs[keys[i]]) | ||
344 |
} |
|
381 | } | |
345 | return value; |
|
382 | utils.resolve_promises_dict(state_promise_dict).then(function(state) { | |
|
383 | // get binary values, then send | |||
|
384 | var keys = Object.keys(state); | |||
|
385 | var buffers = []; | |||
|
386 | var buffer_keys = []; | |||
|
387 | for (var i=0; i<keys.length; i++) { | |||
|
388 | var key = keys[i]; | |||
|
389 | var value = state[key]; | |||
|
390 | if (value.buffer instanceof ArrayBuffer | |||
|
391 | || value instanceof ArrayBuffer) { | |||
|
392 | buffers.push(value); | |||
|
393 | buffer_keys.push(key); | |||
|
394 | delete state[key]; | |||
|
395 | } | |||
|
396 | } | |||
|
397 | that.comm.send({method: 'backbone', sync_data: state, buffer_keys: buffer_keys}, callbacks, {}, buffers); | |||
|
398 | that.pending_msgs++; | |||
|
399 | }) | |||
|
400 | }, | |||
|
401 | ||||
|
402 | serialize: function(model, attrs) { | |||
|
403 | // Serialize the attributes into a sync message | |||
|
404 | var keys = Object.keys(attrs); | |||
|
405 | var key, value; | |||
|
406 | var buffers, metadata, buffer_keys, serialize; | |||
|
407 | for (var i=0; i<keys.length; i++) { | |||
|
408 | key = keys[i]; | |||
|
409 | serialize = model.serializers[key]; | |||
|
410 | if (serialize && serialize.serialize) { | |||
|
411 | attrs[key] = serialize.serialize(attrs[key]); | |||
|
412 | } | |||
346 | } |
|
413 | } | |
347 | }, |
|
414 | }, | |
348 |
|
415 | |||
349 |
|
|
416 | save_changes: function(callbacks) { | |
350 | /** |
|
417 | /** | |
351 | * Replace model ids with models recursively. |
|
418 | * Push this model's state to the back-end | |
|
419 | * | |||
|
420 | * This invokes a Backbone.Sync. | |||
352 | */ |
|
421 | */ | |
353 | var that = this; |
|
422 | this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks}); | |
354 | var unpacked; |
|
|||
355 | if ($.isArray(value)) { |
|
|||
356 | unpacked = []; |
|
|||
357 | _.each(value, function(sub_value, key) { |
|
|||
358 | unpacked.push(that._unpack_models(sub_value)); |
|
|||
359 | }); |
|
|||
360 | return Promise.all(unpacked); |
|
|||
361 | } else if (value instanceof Object) { |
|
|||
362 | unpacked = {}; |
|
|||
363 | _.each(value, function(sub_value, key) { |
|
|||
364 | unpacked[key] = that._unpack_models(sub_value); |
|
|||
365 | }); |
|
|||
366 | return utils.resolve_promises_dict(unpacked); |
|
|||
367 | } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") { |
|
|||
368 | // get_model returns a promise already |
|
|||
369 | return this.widget_manager.get_model(value.slice(10, value.length)); |
|
|||
370 | } else { |
|
|||
371 | return Promise.resolve(value); |
|
|||
372 | } |
|
|||
373 | }, |
|
423 | }, | |
374 |
|
424 | |||
375 | on_some_change: function(keys, callback, context) { |
|
425 | on_some_change: function(keys, callback, context) { | |
@@ -386,7 +436,7 b' define(["widgets/js/manager",' | |||||
386 | } |
|
436 | } | |
387 | }, this); |
|
437 | }, this); | |
388 |
|
438 | |||
389 | }, |
|
439 | }, | |
390 | }); |
|
440 | }); | |
391 | widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel); |
|
441 | widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel); | |
392 |
|
442 | |||
@@ -444,11 +494,11 b' define(["widgets/js/manager",' | |||||
444 | */ |
|
494 | */ | |
445 | }, |
|
495 | }, | |
446 |
|
496 | |||
447 | send: function (content) { |
|
497 | send: function (content, buffers) { | |
448 | /** |
|
498 | /** | |
449 | * Send a custom msg associated with this view. |
|
499 | * Send a custom msg associated with this view. | |
450 | */ |
|
500 | */ | |
451 | this.model.send(content, this.callbacks()); |
|
501 | this.model.send(content, this.callbacks(), buffers); | |
452 | }, |
|
502 | }, | |
453 |
|
503 | |||
454 | touch: function () { |
|
504 | touch: function () { |
@@ -89,6 +89,47 b' def register(key=None):' | |||||
89 | return wrap |
|
89 | return wrap | |
90 |
|
90 | |||
91 |
|
91 | |||
|
92 | def _widget_to_json(x): | |||
|
93 | if isinstance(x, dict): | |||
|
94 | return {k: _widget_to_json(v) for k, v in x.items()} | |||
|
95 | elif isinstance(x, (list, tuple)): | |||
|
96 | return [_widget_to_json(v) for v in x] | |||
|
97 | elif isinstance(x, Widget): | |||
|
98 | return "IPY_MODEL_" + x.model_id | |||
|
99 | else: | |||
|
100 | return x | |||
|
101 | ||||
|
102 | def _json_to_widget(x): | |||
|
103 | if isinstance(x, dict): | |||
|
104 | return {k: _json_to_widget(v) for k, v in x.items()} | |||
|
105 | elif isinstance(x, (list, tuple)): | |||
|
106 | return [_json_to_widget(v) for v in x] | |||
|
107 | elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets: | |||
|
108 | return Widget.widgets[x[10:]] | |||
|
109 | else: | |||
|
110 | return x | |||
|
111 | ||||
|
112 | widget_serialization = { | |||
|
113 | 'from_json': _json_to_widget, | |||
|
114 | 'to_json': lambda x: (_widget_to_json(x), {'serialization': ('widget_serialization', 'widgets/js/types')}) | |||
|
115 | } | |||
|
116 | ||||
|
117 | def _to_binary_list(x): | |||
|
118 | import numpy | |||
|
119 | return memoryview(numpy.array(x, dtype=float)), {'serialization': ('list_of_numbers', 'widgets/js/types')} | |||
|
120 | ||||
|
121 | def _from_binary_list(x): | |||
|
122 | import numpy | |||
|
123 | a = numpy.frombuffer(x.tobytes(), dtype=float) | |||
|
124 | return list(a) | |||
|
125 | ||||
|
126 | list_of_numbers = { | |||
|
127 | 'from_json': _from_binary_list, | |||
|
128 | 'to_json': _to_binary_list | |||
|
129 | } | |||
|
130 | ||||
|
131 | ||||
|
132 | ||||
92 | class Widget(LoggingConfigurable): |
|
133 | class Widget(LoggingConfigurable): | |
93 | #------------------------------------------------------------------------- |
|
134 | #------------------------------------------------------------------------- | |
94 | # Class attributes |
|
135 | # Class attributes | |
@@ -216,10 +257,13 b' class Widget(LoggingConfigurable):' | |||||
216 | key : unicode, or iterable (optional) |
|
257 | key : unicode, or iterable (optional) | |
217 | A single property's name or iterable of property names to sync with the front-end. |
|
258 | A single property's name or iterable of property names to sync with the front-end. | |
218 | """ |
|
259 | """ | |
219 | self._send({ |
|
260 | state, buffer_keys, buffers, metadata = self.get_state(key=key) | |
220 |
|
|
261 | msg = {"method": "update", "state": state} | |
221 | "state" : self.get_state(key=key) |
|
262 | if buffer_keys: | |
222 | }) |
|
263 | msg['buffers'] = buffer_keys | |
|
264 | if metadata: | |||
|
265 | msg['metadata'] = metadata | |||
|
266 | self._send(msg, buffers=buffers) | |||
223 |
|
267 | |||
224 | def get_state(self, key=None): |
|
268 | def get_state(self, key=None): | |
225 | """Gets the widget state, or a piece of it. |
|
269 | """Gets the widget state, or a piece of it. | |
@@ -228,6 +272,16 b' class Widget(LoggingConfigurable):' | |||||
228 | ---------- |
|
272 | ---------- | |
229 | key : unicode or iterable (optional) |
|
273 | key : unicode or iterable (optional) | |
230 | A single property's name or iterable of property names to get. |
|
274 | A single property's name or iterable of property names to get. | |
|
275 | ||||
|
276 | Returns | |||
|
277 | ------- | |||
|
278 | state : dict of states | |||
|
279 | buffer_keys : list of strings | |||
|
280 | the values that are stored in buffers | |||
|
281 | buffers : list of binary memoryviews | |||
|
282 | values to transmit in binary | |||
|
283 | metadata : dict | |||
|
284 | metadata for each field: {key: metadata} | |||
231 | """ |
|
285 | """ | |
232 | if key is None: |
|
286 | if key is None: | |
233 | keys = self.keys |
|
287 | keys = self.keys | |
@@ -238,11 +292,21 b' class Widget(LoggingConfigurable):' | |||||
238 | else: |
|
292 | else: | |
239 | raise ValueError("key must be a string, an iterable of keys, or None") |
|
293 | raise ValueError("key must be a string, an iterable of keys, or None") | |
240 | state = {} |
|
294 | state = {} | |
|
295 | buffers = [] | |||
|
296 | buffer_keys = [] | |||
|
297 | metadata = {} | |||
241 | for k in keys: |
|
298 | for k in keys: | |
242 | f = self.trait_metadata(k, 'to_json', self._trait_to_json) |
|
299 | f = self.trait_metadata(k, 'to_json', self._trait_to_json) | |
243 | value = getattr(self, k) |
|
300 | value = getattr(self, k) | |
244 |
s |
|
301 | serialized, md = f(value) | |
245 | return state |
|
302 | if isinstance(serialized, memoryview): | |
|
303 | buffers.append(serialized) | |||
|
304 | buffer_keys.append(k) | |||
|
305 | else: | |||
|
306 | state[k] = serialized | |||
|
307 | if md is not None: | |||
|
308 | metadata[k] = md | |||
|
309 | return state, buffer_keys, buffers, metadata | |||
246 |
|
310 | |||
247 | def set_state(self, sync_data): |
|
311 | def set_state(self, sync_data): | |
248 | """Called when a state is received from the front-end.""" |
|
312 | """Called when a state is received from the front-end.""" | |
@@ -253,15 +317,17 b' class Widget(LoggingConfigurable):' | |||||
253 | with self._lock_property(name, json_value): |
|
317 | with self._lock_property(name, json_value): | |
254 | setattr(self, name, from_json(json_value)) |
|
318 | setattr(self, name, from_json(json_value)) | |
255 |
|
319 | |||
256 | def send(self, content): |
|
320 | def send(self, content, buffers=None): | |
257 | """Sends a custom msg to the widget model in the front-end. |
|
321 | """Sends a custom msg to the widget model in the front-end. | |
258 |
|
322 | |||
259 | Parameters |
|
323 | Parameters | |
260 | ---------- |
|
324 | ---------- | |
261 | content : dict |
|
325 | content : dict | |
262 | Content of the message to send. |
|
326 | Content of the message to send. | |
|
327 | buffers : list of binary buffers | |||
|
328 | Binary buffers to send with message | |||
263 | """ |
|
329 | """ | |
264 | self._send({"method": "custom", "content": content}) |
|
330 | self._send({"method": "custom", "content": content}, buffers=buffers) | |
265 |
|
331 | |||
266 | def on_msg(self, callback, remove=False): |
|
332 | def on_msg(self, callback, remove=False): | |
267 | """(Un)Register a custom msg receive callback. |
|
333 | """(Un)Register a custom msg receive callback. | |
@@ -269,9 +335,9 b' class Widget(LoggingConfigurable):' | |||||
269 | Parameters |
|
335 | Parameters | |
270 | ---------- |
|
336 | ---------- | |
271 | callback: callable |
|
337 | callback: callable | |
272 |
callback will be passed t |
|
338 | callback will be passed three arguments when a message arrives:: | |
273 |
|
339 | |||
274 | callback(widget, content) |
|
340 | callback(widget, content, buffers) | |
275 |
|
341 | |||
276 | remove: bool |
|
342 | remove: bool | |
277 | True if the callback should be unregistered.""" |
|
343 | True if the callback should be unregistered.""" | |
@@ -346,7 +412,10 b' class Widget(LoggingConfigurable):' | |||||
346 | # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one. |
|
412 | # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one. | |
347 | if method == 'backbone': |
|
413 | if method == 'backbone': | |
348 | if 'sync_data' in data: |
|
414 | if 'sync_data' in data: | |
|
415 | # get binary buffers too | |||
349 | sync_data = data['sync_data'] |
|
416 | sync_data = data['sync_data'] | |
|
417 | for i,k in enumerate(data.get('buffer_keys', [])): | |||
|
418 | sync_data[k] = msg['buffers'][i] | |||
350 | self.set_state(sync_data) # handles all methods |
|
419 | self.set_state(sync_data) # handles all methods | |
351 |
|
420 | |||
352 | # Handle a state request. |
|
421 | # Handle a state request. | |
@@ -356,15 +425,15 b' class Widget(LoggingConfigurable):' | |||||
356 | # Handle a custom msg from the front-end. |
|
425 | # Handle a custom msg from the front-end. | |
357 | elif method == 'custom': |
|
426 | elif method == 'custom': | |
358 | if 'content' in data: |
|
427 | if 'content' in data: | |
359 | self._handle_custom_msg(data['content']) |
|
428 | self._handle_custom_msg(data['content'], msg['buffers']) | |
360 |
|
429 | |||
361 | # Catch remainder. |
|
430 | # Catch remainder. | |
362 | else: |
|
431 | else: | |
363 | self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method) |
|
432 | self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method) | |
364 |
|
433 | |||
365 | def _handle_custom_msg(self, content): |
|
434 | def _handle_custom_msg(self, content, buffers): | |
366 | """Called when a custom msg is received.""" |
|
435 | """Called when a custom msg is received.""" | |
367 | self._msg_callbacks(self, content) |
|
436 | self._msg_callbacks(self, content, buffers) | |
368 |
|
437 | |||
369 | def _notify_trait(self, name, old_value, new_value): |
|
438 | def _notify_trait(self, name, old_value, new_value): | |
370 | """Called when a property has been changed.""" |
|
439 | """Called when a property has been changed.""" | |
@@ -391,30 +460,14 b' class Widget(LoggingConfigurable):' | |||||
391 | Traverse lists/tuples and dicts and serialize their values as well. |
|
460 | Traverse lists/tuples and dicts and serialize their values as well. | |
392 | Replace any widgets with their model_id |
|
461 | Replace any widgets with their model_id | |
393 | """ |
|
462 | """ | |
394 | if isinstance(x, dict): |
|
463 | return x, None | |
395 | return {k: self._trait_to_json(v) for k, v in x.items()} |
|
|||
396 | elif isinstance(x, (list, tuple)): |
|
|||
397 | return [self._trait_to_json(v) for v in x] |
|
|||
398 | elif isinstance(x, Widget): |
|
|||
399 | return "IPY_MODEL_" + x.model_id |
|
|||
400 | else: |
|
|||
401 | return x # Value must be JSON-able |
|
|||
402 |
|
464 | |||
403 | def _trait_from_json(self, x): |
|
465 | def _trait_from_json(self, x): | |
404 | """Convert json values to objects |
|
466 | """Convert json values to objects | |
405 |
|
467 | |||
406 | Replace any strings representing valid model id values to Widget references. |
|
468 | Replace any strings representing valid model id values to Widget references. | |
407 | """ |
|
469 | """ | |
408 | if isinstance(x, dict): |
|
470 | return x | |
409 | return {k: self._trait_from_json(v) for k, v in x.items()} |
|
|||
410 | elif isinstance(x, (list, tuple)): |
|
|||
411 | return [self._trait_from_json(v) for v in x] |
|
|||
412 | elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets: |
|
|||
413 | # we want to support having child widgets at any level in a hierarchy |
|
|||
414 | # trusting that a widget UUID will not appear out in the wild |
|
|||
415 | return Widget.widgets[x[10:]] |
|
|||
416 | else: |
|
|||
417 | return x |
|
|||
418 |
|
471 | |||
419 | def _ipython_display_(self, **kwargs): |
|
472 | def _ipython_display_(self, **kwargs): | |
420 | """Called when `IPython.display.display` is called on the widget.""" |
|
473 | """Called when `IPython.display.display` is called on the widget.""" | |
@@ -423,9 +476,9 b' class Widget(LoggingConfigurable):' | |||||
423 | self._send({"method": "display"}) |
|
476 | self._send({"method": "display"}) | |
424 | self._handle_displayed(**kwargs) |
|
477 | self._handle_displayed(**kwargs) | |
425 |
|
478 | |||
426 | def _send(self, msg): |
|
479 | def _send(self, msg, buffers=None): | |
427 | """Sends a message to the model in the front-end.""" |
|
480 | """Sends a message to the model in the front-end.""" | |
428 | self.comm.send(msg) |
|
481 | self.comm.send(data=msg, buffers=buffers) | |
429 |
|
482 | |||
430 |
|
483 | |||
431 | class DOMWidget(Widget): |
|
484 | class DOMWidget(Widget): |
@@ -6,7 +6,7 b' Represents a container that can be used to group other widgets.' | |||||
6 | # Copyright (c) IPython Development Team. |
|
6 | # Copyright (c) IPython Development Team. | |
7 | # Distributed under the terms of the Modified BSD License. |
|
7 | # Distributed under the terms of the Modified BSD License. | |
8 |
|
8 | |||
9 | from .widget import DOMWidget, register |
|
9 | from .widget import DOMWidget, register, widget_serialization | |
10 | from IPython.utils.traitlets import Unicode, Tuple, TraitError, Int, CaselessStrEnum |
|
10 | from IPython.utils.traitlets import Unicode, Tuple, TraitError, Int, CaselessStrEnum | |
11 | from IPython.utils.warn import DeprecatedClass |
|
11 | from IPython.utils.warn import DeprecatedClass | |
12 |
|
12 | |||
@@ -18,7 +18,9 b' class Box(DOMWidget):' | |||||
18 | # Child widgets in the container. |
|
18 | # Child widgets in the container. | |
19 | # Using a tuple here to force reassignment to update the list. |
|
19 | # Using a tuple here to force reassignment to update the list. | |
20 | # When a proper notifying-list trait exists, that is what should be used here. |
|
20 | # When a proper notifying-list trait exists, that is what should be used here. | |
21 | children = Tuple(sync=True) |
|
21 | # TODO: make this tuple serialize models | |
|
22 | # TODO: enforce that tuples here have a single datatype | |||
|
23 | children = Tuple(sync=True, **widget_serialization) | |||
22 |
|
24 | |||
23 | _overflow_values = ['visible', 'hidden', 'scroll', 'auto', 'initial', 'inherit', ''] |
|
25 | _overflow_values = ['visible', 'hidden', 'scroll', 'auto', 'initial', 'inherit', ''] | |
24 | overflow_x = CaselessStrEnum( |
|
26 | overflow_x = CaselessStrEnum( |
@@ -67,7 +67,7 b' class Button(DOMWidget):' | |||||
67 | Set to true to remove the callback from the list of callbacks.""" |
|
67 | Set to true to remove the callback from the list of callbacks.""" | |
68 | self._click_handlers.register_callback(callback, remove=remove) |
|
68 | self._click_handlers.register_callback(callback, remove=remove) | |
69 |
|
69 | |||
70 | def _handle_button_msg(self, _, content): |
|
70 | def _handle_button_msg(self, _, content, buffers): | |
71 | """Handle a msg from the front-end. |
|
71 | """Handle a msg from the front-end. | |
72 |
|
72 | |||
73 | Parameters |
|
73 | Parameters |
General Comments 0
You need to be logged in to leave comments.
Login now