Skip to content

Commit abb9f73

Browse files
SylvainCorlaygnestor
authored andcommitted
Merge pull request #2022 from jasongrout/callbackid-4.x
Backport #1958 to 4.x: Add an output callback override stack
2 parents b8c5392 + 77ba557 commit abb9f73

File tree

3 files changed

+215
-26
lines changed

3 files changed

+215
-26
lines changed

notebook/static/services/kernels/comm.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,9 @@ define([
129129
}
130130

131131
this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) {
132-
try {
133-
comm.handle_msg(msg);
134-
} catch (e) {
135-
console.log("Exception handling comm msg: ", e, e.stack, msg);
136-
}
137-
return comm;
132+
return (Promise.resolve(comm.handle_msg(msg))
133+
.catch(utils.reject('Exception handling comm message'))
134+
.then(function() {return comm;}));
138135
});
139136
return this.comms[content.comm_id];
140137
};
@@ -194,15 +191,15 @@ define([
194191
var callback = this['_' + key + '_callback'];
195192
if (callback) {
196193
try {
197-
callback(msg);
194+
return callback(msg);
198195
} catch (e) {
199196
console.log("Exception in Comm callback", e, e.stack, msg);
200197
}
201198
}
202199
};
203200

204201
Comm.prototype.handle_msg = function (msg) {
205-
this._callback('msg', msg);
202+
return this._callback('msg', msg);
206203
};
207204

208205
Comm.prototype.handle_close = function (msg) {

notebook/static/services/kernels/kernel.js

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ define([
4242
this.username = "username";
4343
this.session_id = utils.uuid();
4444
this._msg_callbacks = {};
45+
this._msg_callbacks_overrides = {};
4546
this._msg_queue = Promise.resolve();
4647
this.info_reply = {}; // kernel_info_reply stored here after starting
4748

@@ -301,6 +302,8 @@ define([
301302
this.events.trigger('kernel_restarting.Kernel', {kernel: this});
302303
this.stop_channels();
303304

305+
this._msg_callbacks = {};
306+
this._msg_callbacks_overrides = {};
304307
var that = this;
305308
var on_success = function (data, status, xhr) {
306309
that.events.trigger('kernel_created.Kernel', {kernel: that});
@@ -847,6 +850,35 @@ define([
847850
}
848851
};
849852

853+
/**
854+
* Get output callbacks for a specific message.
855+
*
856+
* @function get_output_callbacks_for_msg
857+
*
858+
* Since output callbacks can be overridden, we first check the override stack.
859+
*/
860+
Kernel.prototype.get_output_callbacks_for_msg = function (msg_id) {
861+
return this.get_callbacks_for_msg(this.get_output_callback_id(msg_id));
862+
};
863+
864+
865+
/**
866+
* Get the output callback id for a message
867+
*
868+
* Since output callbacks can be redirected, this may not be the same as
869+
* the msg_id.
870+
*
871+
* @function get_output_callback_id
872+
*/
873+
Kernel.prototype.get_output_callback_id = function (msg_id) {
874+
var callback_id = msg_id;
875+
var overrides = this._msg_callbacks_overrides[msg_id];
876+
if (overrides && overrides.length > 0) {
877+
callback_id = overrides[overrides.length-1];
878+
}
879+
return callback_id
880+
}
881+
850882
/**
851883
* Clear callbacks for a specific message.
852884
*
@@ -891,10 +923,16 @@ define([
891923
*
892924
* }
893925
*
926+
* If the third parameter is truthy, the callback is set as the last
927+
* callback registered.
928+
*
894929
* @function set_callbacks_for_msg
895930
*/
896-
Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
897-
this.last_msg_id = msg_id;
931+
Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks, save) {
932+
var remember = save || true;
933+
if (remember) {
934+
this.last_msg_id = msg_id;
935+
}
898936
if (callbacks) {
899937
// shallow-copy mapping, because we will modify it at the top level
900938
var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
@@ -903,11 +941,31 @@ define([
903941
cbcopy.input = callbacks.input;
904942
cbcopy.shell_done = (!callbacks.shell);
905943
cbcopy.iopub_done = (!callbacks.iopub);
906-
} else {
944+
} else if (remember) {
907945
this.last_msg_callbacks = {};
908946
}
909947
};
910-
948+
949+
/**
950+
* Override output callbacks for a particular msg_id
951+
*/
952+
Kernel.prototype.output_callback_overrides_push = function(msg_id, callback_id) {
953+
var output_callbacks = this._msg_callbacks_overrides[msg_id];
954+
if (output_callbacks === void 0) {
955+
this._msg_callbacks_overrides[msg_id] = output_callbacks = [];
956+
}
957+
output_callbacks.push(callback_id);
958+
}
959+
960+
Kernel.prototype.output_callback_overrides_pop = function(msg_id) {
961+
var callback_ids = this._msg_callbacks_overrides[msg_id];
962+
if (!callback_ids) {
963+
console.error("Popping callback overrides, but none registered", msg_id);
964+
return;
965+
}
966+
return callback_ids.pop();
967+
}
968+
911969
Kernel.prototype._handle_ws_message = function (e) {
912970
var that = this;
913971
this._msg_queue = this._msg_queue.then(function() {
@@ -1032,7 +1090,7 @@ define([
10321090
* @function _handle_clear_output
10331091
*/
10341092
Kernel.prototype._handle_clear_output = function (msg) {
1035-
var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
1093+
var callbacks = this.get_output_callbacks_for_msg(msg.parent_header.msg_id);
10361094
if (!callbacks || !callbacks.iopub) {
10371095
return;
10381096
}
@@ -1048,7 +1106,8 @@ define([
10481106
* @function _handle_output_message
10491107
*/
10501108
Kernel.prototype._handle_output_message = function (msg) {
1051-
var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
1109+
var msg_id = msg.parent_header.msg_id;
1110+
var callbacks = this.get_output_callbacks_for_msg(msg_id);
10521111
if (!callbacks || !callbacks.iopub) {
10531112
// The message came from another client. Let the UI decide what to
10541113
// do with it.

notebook/tests/notebook/output.js

Lines changed: 145 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44

55
casper.notebook_test(function () {
66

7+
this.compare_outputs = function(results, expected) {
8+
for (var i = 0; i < results.length; i++) {
9+
var r = results[i];
10+
var ex = expected[i];
11+
this.test.assertEquals(r.output_type, ex.output_type, "output " + i + " = " + r.output_type);
12+
if (r.output_type === 'stream') {
13+
this.test.assertEquals(r.name, ex.name, "stream " + i + " = " + r.name);
14+
this.test.assertEquals(r.text, ex.text, "content " + i);
15+
}
16+
}
17+
}
718
this.test_coalesced_output = function (msg, code, expected) {
819
this.then(function () {
920
this.echo("Test coalesced output: " + msg);
@@ -24,31 +35,23 @@ casper.notebook_test(function () {
2435
return cell.output_area.outputs;
2536
});
2637
this.test.assertEquals(results.length, expected.length, "correct number of outputs");
27-
for (var i = 0; i < results.length; i++) {
28-
var r = results[i];
29-
var ex = expected[i];
30-
this.test.assertEquals(r.output_type, ex.output_type, "output " + i);
31-
if (r.output_type === 'stream') {
32-
this.test.assertEquals(r.name, ex.name, "stream " + i);
33-
this.test.assertEquals(r.text, ex.text, "content " + i);
34-
}
35-
}
38+
this.compare_outputs(results, expected);
3639
});
3740

3841
};
39-
42+
4043
this.thenEvaluate(function () {
4144
IPython.notebook.insert_cell_at_index("code", 0);
4245
var cell = IPython.notebook.get_cell(0);
4346
cell.set_text([
4447
"from __future__ import print_function",
4548
"import sys",
46-
"from IPython.display import display"
49+
"from IPython.display import display, clear_output"
4750
].join("\n")
4851
);
4952
cell.execute();
5053
});
51-
54+
5255
this.test_coalesced_output("stdout", [
5356
"print(1)",
5457
"sys.stdout.flush()",
@@ -123,4 +126,134 @@ casper.notebook_test(function () {
123126
},
124127
}]
125128
);
129+
130+
this.then(function () {
131+
this.echo("Test output callback overrides");
132+
});
133+
134+
this.thenEvaluate(function () {
135+
IPython.notebook.insert_cell_at_index("code", 0);
136+
var cell = IPython.notebook.get_cell(0);
137+
cell.set_text(["print(1)",
138+
"sys.stdout.flush()",
139+
"print(2)",
140+
"sys.stdout.flush()",
141+
"print(3, file=sys.stderr)",
142+
"sys.stdout.flush()",
143+
"display(2)",
144+
"clear_output()",
145+
"sys.stdout.flush()",
146+
"print('remove handler')",
147+
"sys.stdout.flush()",
148+
"print('back to cell')",
149+
"sys.stdout.flush()",
150+
].join('\n'));
151+
cell.execute();
152+
var kernel = IPython.notebook.kernel;
153+
var msg_id = cell.last_msg_id;
154+
var callback_id = 'mycallbackid'
155+
cell.iopub_messages = [];
156+
var add_msg = function(msg) {
157+
if (msg.content.text==="remove handler\n") {
158+
kernel.output_callback_overrides_pop(msg_id);
159+
}
160+
msg.content.output_type = msg.msg_type;
161+
cell.iopub_messages.push(msg.content);
162+
};
163+
kernel.set_callbacks_for_msg(callback_id, {
164+
iopub: {
165+
output: add_msg,
166+
clear_output: add_msg,
167+
}
168+
}, false);
169+
kernel.output_callback_overrides_push(msg_id, callback_id);
170+
});
171+
172+
this.wait_for_idle();
173+
174+
this.then(function () {
175+
var expected_callback = [{
176+
output_type: "stream",
177+
name: "stdout",
178+
text: "1\n"
179+
}, {
180+
output_type: "stream",
181+
name: "stdout",
182+
text: "2\n"
183+
}, {
184+
output_type: "stream",
185+
name: "stderr",
186+
text: "3\n"
187+
},{
188+
output_type: "display_data",
189+
},{
190+
output_type: "clear_output",
191+
},{
192+
output_type: "stream",
193+
name: "stdout",
194+
text: "remove handler\n"
195+
},]
196+
var expected_cell = [{
197+
output_type: "stream",
198+
name: "stdout",
199+
text: "back to cell\n"
200+
}]
201+
var returned = this.evaluate(function () {
202+
var cell = IPython.notebook.get_cell(0);
203+
return [cell.output_area.outputs, cell.iopub_messages];
204+
});
205+
var cell_results = returned[0];
206+
var callback_results = returned[1];
207+
this.test.assertEquals(cell_results.length, expected_cell.length, "correct number of cell outputs");
208+
this.test.assertEquals(callback_results.length, expected_callback.length, "correct number of callback outputs");
209+
this.compare_outputs(cell_results, expected_cell);
210+
this.compare_outputs(callback_results, expected_callback);
211+
});
212+
213+
this.then(function () {
214+
this.echo("Test output callback overrides get execute_results messages too");
215+
});
216+
217+
this.thenEvaluate(function () {
218+
IPython.notebook.insert_cell_at_index("code", 0);
219+
var cell = IPython.notebook.get_cell(0);
220+
cell.set_text("'end'");
221+
cell.execute();
222+
var kernel = IPython.notebook.kernel;
223+
var msg_id = cell.last_msg_id;
224+
var callback_id = 'mycallbackid2'
225+
cell.iopub_messages = [];
226+
var add_msg = function(msg) {
227+
msg.content.output_type = msg.msg_type;
228+
cell.iopub_messages.push(msg.content);
229+
};
230+
kernel.set_callbacks_for_msg(callback_id, {
231+
iopub: {
232+
output: add_msg,
233+
clear_output: add_msg,
234+
}
235+
}, false);
236+
kernel.output_callback_overrides_push(msg_id, callback_id);
237+
});
238+
239+
this.wait_for_idle();
240+
241+
this.then(function () {
242+
var expected_callback = [{
243+
output_type: "execute_result",
244+
data: {
245+
"text/plain" : "'end'"
246+
}
247+
}];
248+
var expected_cell = [];
249+
var returned = this.evaluate(function () {
250+
var cell = IPython.notebook.get_cell(0);
251+
return [cell.output_area.outputs, cell.iopub_messages];
252+
});
253+
var cell_results = returned[0];
254+
var callback_results = returned[1];
255+
this.test.assertEquals(cell_results.length, expected_cell.length, "correct number of cell outputs");
256+
this.test.assertEquals(callback_results.length, expected_callback.length, "correct number of callback outputs");
257+
this.compare_outputs(callback_results, expected_callback);
258+
});
126259
});

0 commit comments

Comments
 (0)