mirror of
https://github.com/jupyter/notebook.git
synced 2024-12-21 04:10:17 +08:00
Change custom serialization to use custom models, rather than transmitting the serializer name across the wire
This separates the kernel and the js much more cleanly, and doesn't use as much space on the wire as well!
This commit is contained in:
parent
d6def13dbd
commit
15c29f685c
@ -14,18 +14,33 @@ define([
|
||||
"widgets/js/widget_selection",
|
||||
"widgets/js/widget_selectioncontainer",
|
||||
"widgets/js/widget_string",
|
||||
], function(widgetmanager, linkModels) {
|
||||
for (var target_name in linkModels) {
|
||||
if (linkModels.hasOwnProperty(target_name)) {
|
||||
widgetmanager.WidgetManager.register_widget_model(target_name, linkModels[target_name]);
|
||||
}
|
||||
}
|
||||
], function(widgetmanager) {
|
||||
|
||||
// Register all of the loaded views with the widget manager.
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
for (var target_name in arguments[i]) {
|
||||
if (arguments[i].hasOwnProperty(target_name)) {
|
||||
widgetmanager.WidgetManager.register_widget_view(target_name, arguments[i][target_name]);
|
||||
|
||||
/**
|
||||
* From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
|
||||
* Can be removed with the string endsWith function is implemented in major browsers
|
||||
*/
|
||||
var endsWith = function(target, searchString, position) {
|
||||
var subjectString = target.toString();
|
||||
if (position === undefined || position > subjectString.length) {
|
||||
position = subjectString.length;
|
||||
}
|
||||
position -= searchString.length;
|
||||
var lastIndex = subjectString.indexOf(searchString, position);
|
||||
return lastIndex !== -1 && lastIndex === position;
|
||||
};
|
||||
|
||||
// Register all of the loaded models and views with the widget manager.
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var module = arguments[i];
|
||||
for (var target_name in module) {
|
||||
if (module.hasOwnProperty(target_name)) {
|
||||
if (endsWith(target_name, "View")) {
|
||||
widgetmanager.WidgetManager.register_widget_view(target_name, module[target_name]);
|
||||
} else if (endsWith(target_name, "Model")) {
|
||||
widgetmanager.WidgetManager.register_widget_model(target_name, module[target_name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ define(["widgets/js/manager",
|
||||
this.state_lock = null;
|
||||
this.id = model_id;
|
||||
this.views = {};
|
||||
this.serializers = {};
|
||||
this._resolve_received_state = {};
|
||||
|
||||
if (comm !== undefined) {
|
||||
@ -146,23 +145,17 @@ define(["widgets/js/manager",
|
||||
var state = msg.content.data.state || {};
|
||||
var buffer_keys = msg.content.data.buffers || [];
|
||||
var buffers = msg.buffers || [];
|
||||
var metadata = msg.content.data.metadata || {};
|
||||
var i,k;
|
||||
for (var i=0; i<buffer_keys.length; i++) {
|
||||
k = buffer_keys[i];
|
||||
state[k] = buffers[i];
|
||||
state[buffer_keys[i]] = buffers[i];
|
||||
}
|
||||
|
||||
// for any metadata specifying a deserializer, set the
|
||||
// state to a promise that resolves to the deserialized version
|
||||
// also, store the serialization function for the attribute
|
||||
var keys = Object.keys(metadata);
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
k = keys[i];
|
||||
if (metadata[k] && metadata[k].serialization) {
|
||||
that.serializers[k] = utils.load_class.apply(that,
|
||||
metadata[k].serialization);
|
||||
state[k] = that.deserialize(that.serializers[k], state[k]);
|
||||
// deserialize fields that have custom deserializers
|
||||
var serializers = that.constructor.serializers;
|
||||
if (serializers) {
|
||||
for (var k in state) {
|
||||
if (serializers[k] && serializers[k].deserialize) {
|
||||
state[k] = (serializers[k].deserialize)(state[k], that);
|
||||
}
|
||||
}
|
||||
}
|
||||
return utils.resolve_promises_dict(state);
|
||||
@ -188,19 +181,6 @@ define(["widgets/js/manager",
|
||||
}
|
||||
},
|
||||
|
||||
deserialize: function(serializer, value) {
|
||||
// given a serializer dict and a value,
|
||||
// return a promise for the deserialized value
|
||||
var that = this;
|
||||
return serializer.then(function(s) {
|
||||
if (s.deserialize) {
|
||||
return s.deserialize(value, that);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
set_state: function (state) {
|
||||
var that = this;
|
||||
// Handle when a widget is updated via the python side.
|
||||
@ -362,25 +342,15 @@ define(["widgets/js/manager",
|
||||
var that = this;
|
||||
// first, build a state dictionary with key=the attribute and the value
|
||||
// being the value or the promise of the serialized value
|
||||
var state_promise_dict = {};
|
||||
var keys = Object.keys(attrs);
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
// bind k and v locally; needed since we have an inner async function using v
|
||||
(function(k,v) {
|
||||
if (that.serializers[k]) {
|
||||
state_promise_dict[k] = that.serializers[k].then(function(f) {
|
||||
if (f.serialize) {
|
||||
return f.serialize(v, that);
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
})
|
||||
} else {
|
||||
state_promise_dict[k] = v;
|
||||
var serializers = this.constructor.serializers;
|
||||
if (serializers) {
|
||||
for (k in attrs) {
|
||||
if (serializers[k] && serializers[k].serialize) {
|
||||
attrs[k] = (serializers[k].serialize)(attrs[k], this);
|
||||
}
|
||||
})(keys[i], attrs[keys[i]])
|
||||
}
|
||||
}
|
||||
utils.resolve_promises_dict(state_promise_dict).then(function(state) {
|
||||
utils.resolve_promises_dict(attrs).then(function(state) {
|
||||
// get binary values, then send
|
||||
var keys = Object.keys(state);
|
||||
var buffers = [];
|
||||
@ -402,20 +372,6 @@ define(["widgets/js/manager",
|
||||
});
|
||||
},
|
||||
|
||||
serialize: function(model, attrs) {
|
||||
// Serialize the attributes into a sync message
|
||||
var keys = Object.keys(attrs);
|
||||
var key, value;
|
||||
var buffers, metadata, buffer_keys, serialize;
|
||||
for (var i=0; i<keys.length; i++) {
|
||||
key = keys[i];
|
||||
serialize = model.serializers[key];
|
||||
if (serialize && serialize.serialize) {
|
||||
attrs[key] = serialize.serialize(attrs[key]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
save_changes: function(callbacks) {
|
||||
/**
|
||||
* Push this model's state to the back-end
|
||||
|
@ -4,10 +4,41 @@
|
||||
define([
|
||||
"widgets/js/widget",
|
||||
"jqueryui",
|
||||
"underscore",
|
||||
"base/js/utils",
|
||||
"bootstrap",
|
||||
], function(widget, $, utils){
|
||||
], function(widget, $, _, utils){
|
||||
"use strict";
|
||||
var unpack_models = function unpack_models(value, model) {
|
||||
/**
|
||||
* Replace model ids with models recursively.
|
||||
*/
|
||||
var unpacked;
|
||||
if ($.isArray(value)) {
|
||||
unpacked = [];
|
||||
_.each(value, function(sub_value, key) {
|
||||
unpacked.push(unpack_models(sub_value, model));
|
||||
});
|
||||
return Promise.all(unpacked);
|
||||
} else if (value instanceof Object) {
|
||||
unpacked = {};
|
||||
_.each(value, function(sub_value, key) {
|
||||
unpacked[key] = unpack_models(sub_value, model);
|
||||
});
|
||||
return utils.resolve_promises_dict(unpacked);
|
||||
} else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
|
||||
// get_model returns a promise already
|
||||
return model.widget_manager.get_model(value.slice(10, value.length));
|
||||
} else {
|
||||
return Promise.resolve(value);
|
||||
}
|
||||
};
|
||||
|
||||
var BoxModel = widget.WidgetModel.extend({}, {
|
||||
serializers: _.extend({
|
||||
children: {deserialize: unpack_models}
|
||||
}, widget.WidgetModel.serializers)
|
||||
});
|
||||
|
||||
var BoxView = widget.DOMWidgetView.extend({
|
||||
initialize: function(){
|
||||
@ -148,6 +179,7 @@ define([
|
||||
});
|
||||
|
||||
return {
|
||||
'BoxModel': BoxModel,
|
||||
'BoxView': BoxView,
|
||||
'FlexBoxView': FlexBoxView,
|
||||
};
|
||||
|
@ -89,31 +89,6 @@ def register(key=None):
|
||||
return wrap
|
||||
|
||||
|
||||
def _widget_to_json(x):
|
||||
if isinstance(x, dict):
|
||||
return {k: _widget_to_json(v) for k, v in x.items()}
|
||||
elif isinstance(x, (list, tuple)):
|
||||
return [_widget_to_json(v) for v in x]
|
||||
elif isinstance(x, Widget):
|
||||
return "IPY_MODEL_" + x.model_id
|
||||
else:
|
||||
return x
|
||||
|
||||
def _json_to_widget(x):
|
||||
if isinstance(x, dict):
|
||||
return {k: _json_to_widget(v) for k, v in x.items()}
|
||||
elif isinstance(x, (list, tuple)):
|
||||
return [_json_to_widget(v) for v in x]
|
||||
elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
|
||||
return Widget.widgets[x[10:]]
|
||||
else:
|
||||
return x
|
||||
|
||||
widget_serialization = {
|
||||
'from_json': _json_to_widget,
|
||||
'to_json': lambda x: (_widget_to_json(x), {'serialization': ('models', 'widgets/js/types')})
|
||||
}
|
||||
|
||||
class Widget(LoggingConfigurable):
|
||||
#-------------------------------------------------------------------------
|
||||
# Class attributes
|
||||
@ -241,12 +216,10 @@ class Widget(LoggingConfigurable):
|
||||
key : unicode, or iterable (optional)
|
||||
A single property's name or iterable of property names to sync with the front-end.
|
||||
"""
|
||||
state, buffer_keys, buffers, metadata = self.get_state(key=key)
|
||||
state, buffer_keys, buffers = self.get_state(key=key)
|
||||
msg = {"method": "update", "state": state}
|
||||
if buffer_keys:
|
||||
msg['buffers'] = buffer_keys
|
||||
if metadata:
|
||||
msg['metadata'] = metadata
|
||||
self._send(msg, buffers=buffers)
|
||||
|
||||
def get_state(self, key=None):
|
||||
@ -278,19 +251,16 @@ class Widget(LoggingConfigurable):
|
||||
state = {}
|
||||
buffers = []
|
||||
buffer_keys = []
|
||||
metadata = {}
|
||||
for k in keys:
|
||||
f = self.trait_metadata(k, 'to_json', self._trait_to_json)
|
||||
value = getattr(self, k)
|
||||
serialized, md = f(value)
|
||||
serialized = f(value)
|
||||
if isinstance(serialized, memoryview):
|
||||
buffers.append(serialized)
|
||||
buffer_keys.append(k)
|
||||
else:
|
||||
state[k] = serialized
|
||||
if md is not None:
|
||||
metadata[k] = md
|
||||
return state, buffer_keys, buffers, metadata
|
||||
return state, buffer_keys, buffers
|
||||
|
||||
def set_state(self, sync_data):
|
||||
"""Called when a state is received from the front-end."""
|
||||
@ -439,11 +409,8 @@ class Widget(LoggingConfigurable):
|
||||
self._display_callbacks(self, **kwargs)
|
||||
|
||||
def _trait_to_json(self, x):
|
||||
"""Convert a trait value to json.
|
||||
|
||||
Metadata (the second return value) is not sent
|
||||
"""
|
||||
return x, None
|
||||
"""Convert a trait value to json."""
|
||||
return x
|
||||
|
||||
def _trait_from_json(self, x):
|
||||
"""Convert json values to objects."""
|
||||
|
@ -6,13 +6,40 @@ Represents a container that can be used to group other widgets.
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from .widget import DOMWidget, register, widget_serialization
|
||||
from .widget import DOMWidget, Widget, register
|
||||
from IPython.utils.traitlets import Unicode, Tuple, TraitError, Int, CaselessStrEnum
|
||||
from IPython.utils.warn import DeprecatedClass
|
||||
|
||||
def _widget_to_json(x):
|
||||
if isinstance(x, dict):
|
||||
return {k: _widget_to_json(v) for k, v in x.items()}
|
||||
elif isinstance(x, (list, tuple)):
|
||||
return [_widget_to_json(v) for v in x]
|
||||
elif isinstance(x, Widget):
|
||||
return "IPY_MODEL_" + x.model_id
|
||||
else:
|
||||
return x
|
||||
|
||||
def _json_to_widget(x):
|
||||
if isinstance(x, dict):
|
||||
return {k: _json_to_widget(v) for k, v in x.items()}
|
||||
elif isinstance(x, (list, tuple)):
|
||||
return [_json_to_widget(v) for v in x]
|
||||
elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
|
||||
return Widget.widgets[x[10:]]
|
||||
else:
|
||||
return x
|
||||
|
||||
widget_serialization = {
|
||||
'from_json': _json_to_widget,
|
||||
'to_json': _widget_to_json
|
||||
}
|
||||
|
||||
|
||||
@register('IPython.Box')
|
||||
class Box(DOMWidget):
|
||||
"""Displays multiple widgets in a group."""
|
||||
_model_name = Unicode('BoxModel', sync=True)
|
||||
_view_name = Unicode('BoxView', sync=True)
|
||||
|
||||
# Child widgets in the container.
|
||||
|
Loading…
Reference in New Issue
Block a user