Skip to content

Commit 992cdaf

Browse files
committed
Widgetsnbextension: Use control comm channel
1 parent d95b219 commit 992cdaf

File tree

1 file changed

+176
-62
lines changed

1 file changed

+176
-62
lines changed

python/widgetsnbextension/src/manager.js

Lines changed: 176 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ var embedWidgets = require('./embed_widgets');
1111

1212
var MIME_TYPE = 'application/vnd.jupyter.widget-view+json';
1313

14+
var CONTROL_COMM_TARGET = 'jupyter.widget.control';
15+
var CONTROL_COMM_VERSION = '1.0.0';
16+
1417
function polyfill_new_comm_buffers(
1518
manager,
1619
target_name,
@@ -99,9 +102,181 @@ export class WidgetManager extends ManagerBase {
99102
this.handle_comm_open.bind(this)
100103
);
101104

105+
this._loadFromKernel()
106+
.then(function () {
107+
// Now that we have mirrored any widgets from the kernel...
108+
// Restore any widgets from saved state that are not live (3)
109+
if (
110+
widget_md &&
111+
widget_md['application/vnd.jupyter.widget-state+json']
112+
) {
113+
var state =
114+
notebook.metadata.widgets[
115+
'application/vnd.jupyter.widget-state+json'
116+
];
117+
state = that.filterExistingModelState(state);
118+
return that.set_state(state);
119+
}
120+
})
121+
.then(function () {
122+
// Rerender cells that have widget data
123+
that.notebook.get_cells().forEach(function (cell) {
124+
var rerender =
125+
cell.output_area &&
126+
cell.output_area.outputs.find(function (output) {
127+
return output.data && output.data[MIME_TYPE];
128+
});
129+
if (rerender) {
130+
that.notebook.render_cell_output(cell);
131+
}
132+
});
133+
});
134+
135+
// Create the actions and menu
136+
this._init_actions();
137+
this._init_menu();
138+
}
139+
140+
loadClass(className, moduleName, moduleVersion) {
141+
const failure = () => {
142+
throw new Error(
143+
'Class ' + className + ' not found in module ' + moduleName
144+
);
145+
};
146+
if (moduleName === '@jupyter-widgets/controls') {
147+
return widgets[className]
148+
? Promise.resolve(widgets[className])
149+
: failure();
150+
} else if (moduleName === '@jupyter-widgets/base') {
151+
return base[className] ? Promise.resolve(base[className]) : failure();
152+
} else if (moduleName == '@jupyter-widgets/output') {
153+
return outputWidgets[className]
154+
? Promise.resolve(outputWidgets[className])
155+
: failure();
156+
} else {
157+
return new Promise(function (resolve, reject) {
158+
window.require([moduleName], resolve, reject);
159+
}).then(function (mod) {
160+
if (mod[className]) {
161+
return mod[className];
162+
} else {
163+
return failure();
164+
}
165+
});
166+
}
167+
}
168+
169+
/**
170+
* Fetch all widgets states using the control comm channel, or fallback to `_loadFromKernelSlow`
171+
* if the backend does not implement the control comm.
172+
*/
173+
_loadFromKernel() {
174+
const commId = uuid();
175+
const initComm = await this._create_comm(
176+
CONTROL_COMM_TARGET,
177+
commId,
178+
{ widgets: null },
179+
{ version: CONTROL_COMM_VERSION }
180+
);
181+
182+
// Try fetching all widget states through the control comm
183+
let data;
184+
let buffers;
185+
try {
186+
await new Promise(function (resolve, reject) {
187+
initComm.on_msg(function (msg) {
188+
data = msg['content']['data'];
189+
190+
if (data.method !== 'update_states') {
191+
console.warn(`
192+
Unknown ${data.method} message on the Control channel
193+
`);
194+
return;
195+
}
196+
197+
buffers = (msg.buffers || []).map(function (b) {
198+
if (b instanceof DataView) {
199+
return b;
200+
} else {
201+
return new DataView(b instanceof ArrayBuffer ? b : b.buffer);
202+
}
203+
});
204+
205+
resolve(null);
206+
});
207+
208+
initComm.on_close(reject);
209+
210+
// Send a states request msg
211+
initComm.send({ method: 'request_states' }, {});
212+
});
213+
} catch (error) {
214+
console.warn(
215+
'Failed to open "jupyter.widget.control" comm channel, fallback to slow fetching of widgets.',
216+
error
217+
);
218+
// Fallback to the old implementation for old ipywidgets backend versions (<=7.6)
219+
return this._loadFromKernelSlow();
220+
}
221+
222+
initComm.close();
223+
224+
const states = data.states;
225+
226+
// Extract buffer paths
227+
const bufferPaths = {};
228+
for (const bufferPath of data.buffer_paths) {
229+
if (!bufferPaths[bufferPath[0]]) {
230+
bufferPaths[bufferPath[0]] = [];
231+
}
232+
bufferPaths[bufferPath[0]].push(bufferPath.slice(1));
233+
}
234+
235+
// Start creating all widgets
236+
return Promise.all(Object.keys(states).map(function (widget_id) {
237+
const state = states[widget_id];
238+
239+
try {
240+
return this._create_comm('jupyter.widget', widget_id).then(function (comm) {
241+
// Put binary buffers
242+
if (widget_id in bufferPaths) {
243+
const nBuffers = bufferPaths[widget_id].length;
244+
base.put_buffers(
245+
state,
246+
bufferPaths[widget_id],
247+
buffers.splice(0, nBuffers)
248+
);
249+
}
250+
251+
return this.new_model(
252+
{
253+
model_name: state.model_name,
254+
model_module: state.model_module,
255+
model_module_version: state.model_module_version,
256+
model_id: widget_id,
257+
comm: comm,
258+
},
259+
state.state
260+
);
261+
});
262+
} catch (error) {
263+
// Failed to create a widget model, we continue creating other models so that
264+
// other widgets can render
265+
console.error(error);
266+
}
267+
}));
268+
269+
console.log('Successfully loaded widgets through control channel')
270+
}
271+
272+
/**
273+
* Old implementation of fetching widgets one by one using
274+
* the request_state message on each comm.
275+
*/
276+
_loadFromKernelSlow() {
102277
// Attempt to reconstruct any live comms by requesting them from the back-end (2).
103278
var that = this;
104-
this._get_comm_info().then(function (comm_ids) {
279+
return this._get_comm_info().then(function (comm_ids) {
105280
// Create comm class instances from comm ids (2).
106281
var comm_promises = Object.keys(comm_ids).map(function (comm_id) {
107282
return that._create_comm(that.comm_target_name, comm_id);
@@ -157,68 +332,7 @@ export class WidgetManager extends ManagerBase {
157332
})
158333
);
159334
})
160-
.then(function () {
161-
// Now that we have mirrored any widgets from the kernel...
162-
// Restore any widgets from saved state that are not live (3)
163-
if (
164-
widget_md &&
165-
widget_md['application/vnd.jupyter.widget-state+json']
166-
) {
167-
var state =
168-
notebook.metadata.widgets[
169-
'application/vnd.jupyter.widget-state+json'
170-
];
171-
state = that.filterExistingModelState(state);
172-
return that.set_state(state);
173-
}
174-
})
175-
.then(function () {
176-
// Rerender cells that have widget data
177-
that.notebook.get_cells().forEach(function (cell) {
178-
var rerender =
179-
cell.output_area &&
180-
cell.output_area.outputs.find(function (output) {
181-
return output.data && output.data[MIME_TYPE];
182-
});
183-
if (rerender) {
184-
that.notebook.render_cell_output(cell);
185-
}
186-
});
187-
});
188335
});
189-
190-
// Create the actions and menu
191-
this._init_actions();
192-
this._init_menu();
193-
}
194-
195-
loadClass(className, moduleName, moduleVersion) {
196-
const failure = () => {
197-
throw new Error(
198-
'Class ' + className + ' not found in module ' + moduleName
199-
);
200-
};
201-
if (moduleName === '@jupyter-widgets/controls') {
202-
return widgets[className]
203-
? Promise.resolve(widgets[className])
204-
: failure();
205-
} else if (moduleName === '@jupyter-widgets/base') {
206-
return base[className] ? Promise.resolve(base[className]) : failure();
207-
} else if (moduleName == '@jupyter-widgets/output') {
208-
return outputWidgets[className]
209-
? Promise.resolve(outputWidgets[className])
210-
: failure();
211-
} else {
212-
return new Promise(function (resolve, reject) {
213-
window.require([moduleName], resolve, reject);
214-
}).then(function (mod) {
215-
if (mod[className]) {
216-
return mod[className];
217-
} else {
218-
return failure();
219-
}
220-
});
221-
}
222336
}
223337

224338
/**

0 commit comments

Comments
 (0)