Skip to content

Commit 33bee75

Browse files
committed
Merge pull request #62 from SylvainCorlay/live_comms
Widget persistence: only create comm for widgets having valid widget ids
2 parents 1239184 + 9330746 commit 33bee75

File tree

2 files changed

+94
-33
lines changed

2 files changed

+94
-33
lines changed

ipywidgets/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,15 @@ def _handle_ipython():
3434
load_ipython_extension(ip)
3535

3636
_handle_ipython()
37+
38+
39+
# Workaround for the absence of a comm_info_[request/reply] shell message
40+
class CommInfo(Widget):
41+
"""CommInfo widgets are is typically instantiated by the front-end. As soon as it is instantiated, it sends the collection of valid comms, and kills itself. It is a workaround to the absence of comm_info shell message."""
42+
43+
def __init__(self, **kwargs):
44+
super(CommInfo, self).__init__(**kwargs)
45+
self.send(dict(comms={
46+
k: dict(target_name=v.target_name) for (k, v) in self.comm.kernel.comm_manager.comms.items()
47+
}))
48+
self.close()

ipywidgets/static/widgets/js/manager.js

Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,22 @@ define([
293293
};
294294

295295
WidgetManager.prototype.create_model = function (options) {
296+
/**
297+
* For backward compatibility. Custom widgets may be relying on the fact
298+
* that create_model was creating a comm if none was provided in options.
299+
* Unlike the old version of create_model, if no comm is passed,
300+
* options.model_id is used to create the new comm.
301+
*/
302+
console.warn('WidgetManager.create_model is deprecated. Use WidgetManager.new_model');
303+
if (!options.comm) {
304+
options.comm = this.comm_manager.new_comm('ipython.widget',
305+
{'widget_class': options.widget_class},
306+
options.model_id);
307+
}
308+
return this.new_model(options);
309+
}
310+
311+
WidgetManager.prototype.new_model = function (options) {
296312
/**
297313
* Create and return a promise for a new widget model
298314
*
@@ -302,7 +318,7 @@ define([
302318
* Example
303319
* --------
304320
* JS:
305-
* IPython.notebook.kernel.widget_manager.create_model({
321+
* IPython.notebook.kernel.widget_manager.new_model({
306322
* model_name: 'WidgetModel',
307323
* widget_class: 'ipywidgets.IntSlider'
308324
* })
@@ -320,19 +336,19 @@ define([
320336
* widget_class: (optional) string
321337
* Target name of the widget in the back-end.
322338
* comm: (optional) Comm
339+
* Comm object associated with the widget.
340+
* model_id: (optional) string
341+
* If not provided, the comm id is used.
323342
*
324-
* Create a comm if it wasn't provided.
343+
* Either a comm or a model_id must be provided.
325344
*/
326-
var comm = options.comm;
327-
if (!comm) {
328-
comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class});
329-
}
330-
331345
var that = this;
332-
var model_id = comm.comm_id;
333-
var model_promise = utils.load_class(options.model_name, options.model_module, WidgetManager._model_types)
346+
var model_id = options.model_id || options.comm.comm_id;
347+
var model_promise = utils.load_class(options.model_name,
348+
options.model_module,
349+
WidgetManager._model_types)
334350
.then(function(ModelType) {
335-
var widget_model = new ModelType(that, model_id, comm);
351+
var widget_model = new ModelType(that, model_id, options.comm);
336352
widget_model.once('comm:close', function () {
337353
delete that._models[model_id];
338354
});
@@ -423,30 +439,34 @@ define([
423439
return this._get_connected_kernel().then(function(kernel) {
424440

425441
// Recreate all the widget models for the given notebook state.
426-
var all_models = Promise.all(_.map(Object.keys(state), function (model_id) {
427-
// Recreate a comm using the widget's model id (model_id == comm_id).
428-
var new_comm = new comm.Comm(kernel.widget_manager.comm_target_name, model_id);
429-
kernel.comm_manager.register_comm(new_comm);
430-
431-
// Create the model using the recreated comm. When the model is
432-
// created we don't know yet if the comm is valid so set_comm_live
433-
// false. Once we receive the first state push from the back-end
434-
// we know the comm is alive.
435-
return kernel.widget_manager.create_model({
436-
comm: new_comm,
437-
model_name: state[model_id].model_name,
438-
model_module: state[model_id].model_module,
439-
}).then(function(model) {
440-
model.set_comm_live(false);
441-
return model._deserialize_state(state[model.id].state).then(function(state) {
442-
model.set_state(state);
443-
return model.request_state().then(function() {
444-
model.set_comm_live(true);
445-
return model;
442+
var all_models = that._get_comm_info(kernel).then(function(live_comms) {
443+
return Promise.all(_.map(Object.keys(state), function (model_id) {
444+
if (live_comms.hasOwnProperty(model_id)) { // live comm
445+
var new_comm = new comm.Comm(kernel.widget_manager.comm_target_name, model_id);
446+
kernel.comm_manager.register_comm(new_comm);
447+
return kernel.widget_manager.new_model({
448+
comm: new_comm,
449+
model_name: state[model_id].model_name,
450+
model_module: state[model_id].model_module,
451+
}).then(function(model) {
452+
return model.request_state().then(function() {
453+
return model;
454+
});
446455
});
447-
});
448-
});
449-
}, this));
456+
} else { // dead comm
457+
return kernel.widget_manager.new_model({
458+
model_id: model_id,
459+
model_name: state[model_id].model_name,
460+
model_module: state[model_id].model_module,
461+
}).then(function(model) {
462+
return model._deserialize_state(state[model_id].state).then(function(state) {
463+
model.set_state(state);
464+
return model;
465+
});
466+
});
467+
}
468+
}));
469+
});
450470

451471
// Display all the views
452472
return all_models.then(function(models) {
@@ -480,6 +500,35 @@ define([
480500
});
481501
};
482502

503+
WidgetManager.prototype._get_comm_info = function(kernel) {
504+
/**
505+
* Gets a promise for the open comms in the backend
506+
*/
507+
508+
// Version using the comm_list_[request/reply] shell message.
509+
/*var that = this;
510+
return new Promise(function(resolve, reject) {
511+
kernel.comm_info(function(msg) {
512+
resolve(msg['content']['comms']);
513+
});
514+
});*/
515+
516+
// Workaround for absence of comm_list_[request/reply] shell message.
517+
// Create a new widget that gives the comm list and commits suicide.
518+
var that = this;
519+
return new Promise(function(resolve, reject) {
520+
var comm = that.comm_manager.new_comm('ipython.widget',
521+
{'widget_class': 'ipywidgets.CommInfo'},
522+
'comm_info');
523+
comm.on_msg(function(msg) {
524+
var data = msg.content.data;
525+
if (data.content && data.method === 'custom') {
526+
resolve(data.content.comms);
527+
}
528+
});
529+
});
530+
};
531+
483532
// Backwards compatibility.
484533
IPython.WidgetManager = WidgetManager;
485534

0 commit comments

Comments
 (0)