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_selection",
|
||||||
"widgets/js/widget_selectioncontainer",
|
"widgets/js/widget_selectioncontainer",
|
||||||
"widgets/js/widget_string",
|
"widgets/js/widget_string",
|
||||||
], function(widgetmanager, linkModels) {
|
], function(widgetmanager) {
|
||||||
for (var target_name in linkModels) {
|
|
||||||
if (linkModels.hasOwnProperty(target_name)) {
|
|
||||||
widgetmanager.WidgetManager.register_widget_model(target_name, linkModels[target_name]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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]) {
|
* From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
|
||||||
if (arguments[i].hasOwnProperty(target_name)) {
|
* Can be removed with the string endsWith function is implemented in major browsers
|
||||||
widgetmanager.WidgetManager.register_widget_view(target_name, arguments[i][target_name]);
|
*/
|
||||||
|
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.state_lock = null;
|
||||||
this.id = model_id;
|
this.id = model_id;
|
||||||
this.views = {};
|
this.views = {};
|
||||||
this.serializers = {};
|
|
||||||
this._resolve_received_state = {};
|
this._resolve_received_state = {};
|
||||||
|
|
||||||
if (comm !== undefined) {
|
if (comm !== undefined) {
|
||||||
@ -146,23 +145,17 @@ define(["widgets/js/manager",
|
|||||||
var state = msg.content.data.state || {};
|
var state = msg.content.data.state || {};
|
||||||
var buffer_keys = msg.content.data.buffers || [];
|
var buffer_keys = msg.content.data.buffers || [];
|
||||||
var buffers = msg.buffers || [];
|
var buffers = msg.buffers || [];
|
||||||
var metadata = msg.content.data.metadata || {};
|
|
||||||
var i,k;
|
|
||||||
for (var i=0; i<buffer_keys.length; i++) {
|
for (var i=0; i<buffer_keys.length; i++) {
|
||||||
k = buffer_keys[i];
|
state[buffer_keys[i]] = buffers[i];
|
||||||
state[k] = buffers[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// for any metadata specifying a deserializer, set the
|
// deserialize fields that have custom deserializers
|
||||||
// state to a promise that resolves to the deserialized version
|
var serializers = that.constructor.serializers;
|
||||||
// also, store the serialization function for the attribute
|
if (serializers) {
|
||||||
var keys = Object.keys(metadata);
|
for (var k in state) {
|
||||||
for (var i=0; i<keys.length; i++) {
|
if (serializers[k] && serializers[k].deserialize) {
|
||||||
k = keys[i];
|
state[k] = (serializers[k].deserialize)(state[k], that);
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return utils.resolve_promises_dict(state);
|
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) {
|
set_state: function (state) {
|
||||||
var that = this;
|
var that = this;
|
||||||
// Handle when a widget is updated via the python side.
|
// Handle when a widget is updated via the python side.
|
||||||
@ -362,25 +342,15 @@ define(["widgets/js/manager",
|
|||||||
var that = this;
|
var that = this;
|
||||||
// first, build a state dictionary with key=the attribute and the value
|
// first, build a state dictionary with key=the attribute and the value
|
||||||
// being the value or the promise of the serialized value
|
// being the value or the promise of the serialized value
|
||||||
var state_promise_dict = {};
|
var serializers = this.constructor.serializers;
|
||||||
var keys = Object.keys(attrs);
|
if (serializers) {
|
||||||
for (var i=0; i<keys.length; i++) {
|
for (k in attrs) {
|
||||||
// bind k and v locally; needed since we have an inner async function using v
|
if (serializers[k] && serializers[k].serialize) {
|
||||||
(function(k,v) {
|
attrs[k] = (serializers[k].serialize)(attrs[k], this);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
})(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
|
// get binary values, then send
|
||||||
var keys = Object.keys(state);
|
var keys = Object.keys(state);
|
||||||
var buffers = [];
|
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) {
|
save_changes: function(callbacks) {
|
||||||
/**
|
/**
|
||||||
* Push this model's state to the back-end
|
* Push this model's state to the back-end
|
||||||
|
@ -4,10 +4,41 @@
|
|||||||
define([
|
define([
|
||||||
"widgets/js/widget",
|
"widgets/js/widget",
|
||||||
"jqueryui",
|
"jqueryui",
|
||||||
|
"underscore",
|
||||||
"base/js/utils",
|
"base/js/utils",
|
||||||
"bootstrap",
|
"bootstrap",
|
||||||
], function(widget, $, utils){
|
], function(widget, $, _, utils){
|
||||||
"use strict";
|
"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({
|
var BoxView = widget.DOMWidgetView.extend({
|
||||||
initialize: function(){
|
initialize: function(){
|
||||||
@ -148,6 +179,7 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'BoxModel': BoxModel,
|
||||||
'BoxView': BoxView,
|
'BoxView': BoxView,
|
||||||
'FlexBoxView': FlexBoxView,
|
'FlexBoxView': FlexBoxView,
|
||||||
};
|
};
|
||||||
|
@ -89,31 +89,6 @@ def register(key=None):
|
|||||||
return wrap
|
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 Widget(LoggingConfigurable):
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
# Class attributes
|
# Class attributes
|
||||||
@ -241,12 +216,10 @@ class Widget(LoggingConfigurable):
|
|||||||
key : unicode, or iterable (optional)
|
key : unicode, or iterable (optional)
|
||||||
A single property's name or iterable of property names to sync with the front-end.
|
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}
|
msg = {"method": "update", "state": state}
|
||||||
if buffer_keys:
|
if buffer_keys:
|
||||||
msg['buffers'] = buffer_keys
|
msg['buffers'] = buffer_keys
|
||||||
if metadata:
|
|
||||||
msg['metadata'] = metadata
|
|
||||||
self._send(msg, buffers=buffers)
|
self._send(msg, buffers=buffers)
|
||||||
|
|
||||||
def get_state(self, key=None):
|
def get_state(self, key=None):
|
||||||
@ -278,19 +251,16 @@ class Widget(LoggingConfigurable):
|
|||||||
state = {}
|
state = {}
|
||||||
buffers = []
|
buffers = []
|
||||||
buffer_keys = []
|
buffer_keys = []
|
||||||
metadata = {}
|
|
||||||
for k in keys:
|
for k in keys:
|
||||||
f = self.trait_metadata(k, 'to_json', self._trait_to_json)
|
f = self.trait_metadata(k, 'to_json', self._trait_to_json)
|
||||||
value = getattr(self, k)
|
value = getattr(self, k)
|
||||||
serialized, md = f(value)
|
serialized = f(value)
|
||||||
if isinstance(serialized, memoryview):
|
if isinstance(serialized, memoryview):
|
||||||
buffers.append(serialized)
|
buffers.append(serialized)
|
||||||
buffer_keys.append(k)
|
buffer_keys.append(k)
|
||||||
else:
|
else:
|
||||||
state[k] = serialized
|
state[k] = serialized
|
||||||
if md is not None:
|
return state, buffer_keys, buffers
|
||||||
metadata[k] = md
|
|
||||||
return state, buffer_keys, buffers, metadata
|
|
||||||
|
|
||||||
def set_state(self, sync_data):
|
def set_state(self, sync_data):
|
||||||
"""Called when a state is received from the front-end."""
|
"""Called when a state is received from the front-end."""
|
||||||
@ -439,11 +409,8 @@ class Widget(LoggingConfigurable):
|
|||||||
self._display_callbacks(self, **kwargs)
|
self._display_callbacks(self, **kwargs)
|
||||||
|
|
||||||
def _trait_to_json(self, x):
|
def _trait_to_json(self, x):
|
||||||
"""Convert a trait value to json.
|
"""Convert a trait value to json."""
|
||||||
|
return x
|
||||||
Metadata (the second return value) is not sent
|
|
||||||
"""
|
|
||||||
return x, None
|
|
||||||
|
|
||||||
def _trait_from_json(self, x):
|
def _trait_from_json(self, x):
|
||||||
"""Convert json values to objects."""
|
"""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.
|
# Copyright (c) IPython Development Team.
|
||||||
# Distributed under the terms of the Modified BSD License.
|
# 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.traitlets import Unicode, Tuple, TraitError, Int, CaselessStrEnum
|
||||||
from IPython.utils.warn import DeprecatedClass
|
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')
|
@register('IPython.Box')
|
||||||
class Box(DOMWidget):
|
class Box(DOMWidget):
|
||||||
"""Displays multiple widgets in a group."""
|
"""Displays multiple widgets in a group."""
|
||||||
|
_model_name = Unicode('BoxModel', sync=True)
|
||||||
_view_name = Unicode('BoxView', sync=True)
|
_view_name = Unicode('BoxView', sync=True)
|
||||||
|
|
||||||
# Child widgets in the container.
|
# Child widgets in the container.
|
||||||
|
Loading…
Reference in New Issue
Block a user