Skip to content

Commit 6adae92

Browse files
authored
Merge pull request #1958 from jasongrout/callbackid
Add output callback overrides
2 parents 32f92b4 + 1864fe5 commit 6adae92

File tree

4 files changed

+301
-28
lines changed

4 files changed

+301
-28
lines changed

notebook/static/services/kernels/comm.js

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

130130
this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) {
131-
try {
132-
comm.handle_msg(msg);
133-
} catch (e) {
134-
console.log("Exception handling comm msg: ", e, e.stack, msg);
135-
}
136-
return comm;
131+
return (Promise.resolve(comm.handle_msg(msg))
132+
.catch(utils.reject('Exception handling comm message'))
133+
.then(function() {return comm;}));
137134
});
138135
return this.comms[content.comm_id];
139136
};
@@ -193,15 +190,15 @@ define([
193190
var callback = this['_' + key + '_callback'];
194191
if (callback) {
195192
try {
196-
callback(msg);
193+
return callback(msg);
197194
} catch (e) {
198195
console.log("Exception in Comm callback", e, e.stack, msg);
199196
}
200197
}
201198
};
202199

203200
Comm.prototype.handle_msg = function (msg) {
204-
this._callback('msg', msg);
201+
return this._callback('msg', msg);
205202
};
206203

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

notebook/static/services/kernels/kernel.js

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ define([
4141
this.username = "username";
4242
this.session_id = utils.uuid();
4343
this._msg_callbacks = {};
44+
this._msg_callbacks_overrides = {};
4445
this._display_id_to_parent_ids = {};
4546
this._msg_queue = Promise.resolve();
4647
this.info_reply = {}; // kernel_info_reply stored here after starting
@@ -301,6 +302,7 @@ define([
301302
this.events.trigger('kernel_restarting.Kernel', {kernel: this});
302303
this.stop_channels();
303304
this._msg_callbacks = {};
305+
this._msg_callbacks_overrides = {};
304306
this._display_id_to_parent_ids = {};
305307

306308
var that = this;
@@ -850,6 +852,35 @@ define([
850852
}
851853
};
852854

855+
/**
856+
* Get output callbacks for a specific message.
857+
*
858+
* @function get_output_callbacks_for_msg
859+
*
860+
* Since output callbacks can be overridden, we first check the override stack.
861+
*/
862+
Kernel.prototype.get_output_callbacks_for_msg = function (msg_id) {
863+
return this.get_callbacks_for_msg(this.get_output_callback_id(msg_id));
864+
};
865+
866+
867+
/**
868+
* Get the output callback id for a message
869+
*
870+
* Since output callbacks can be redirected, this may not be the same as
871+
* the msg_id.
872+
*
873+
* @function get_output_callback_id
874+
*/
875+
Kernel.prototype.get_output_callback_id = function (msg_id) {
876+
var callback_id = msg_id;
877+
var overrides = this._msg_callbacks_overrides[msg_id];
878+
if (overrides && overrides.length > 0) {
879+
callback_id = overrides[overrides.length-1];
880+
}
881+
return callback_id
882+
}
883+
853884
/**
854885
* Clear callbacks for a specific message.
855886
*
@@ -913,10 +944,16 @@ define([
913944
*
914945
* }
915946
*
947+
* If the third parameter is truthy, the callback is set as the last
948+
* callback registered.
949+
*
916950
* @function set_callbacks_for_msg
917951
*/
918-
Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
919-
this.last_msg_id = msg_id;
952+
Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks, save) {
953+
var remember = save || true;
954+
if (remember) {
955+
this.last_msg_id = msg_id;
956+
}
920957
if (callbacks) {
921958
// shallow-copy mapping, because we will modify it at the top level
922959
var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
@@ -931,11 +968,31 @@ define([
931968
// default to clear-on-done
932969
cbcopy.clear_on_done = true;
933970
}
934-
} else {
971+
} else if (remember) {
935972
this.last_msg_callbacks = {};
936973
}
937974
};
938-
975+
976+
/**
977+
* Override output callbacks for a particular msg_id
978+
*/
979+
Kernel.prototype.output_callback_overrides_push = function(msg_id, callback_id) {
980+
var output_callbacks = this._msg_callbacks_overrides[msg_id];
981+
if (output_callbacks === void 0) {
982+
this._msg_callbacks_overrides[msg_id] = output_callbacks = [];
983+
}
984+
output_callbacks.push(callback_id);
985+
}
986+
987+
Kernel.prototype.output_callback_overrides_pop = function(msg_id) {
988+
var callback_ids = this._msg_callbacks_overrides[msg_id];
989+
if (!callback_ids) {
990+
console.error("Popping callback overrides, but none registered", msg_id);
991+
return;
992+
}
993+
return callback_ids.pop();
994+
}
995+
939996
Kernel.prototype._handle_ws_message = function (e) {
940997
var that = this;
941998
this._msg_queue = this._msg_queue.then(function() {
@@ -1060,7 +1117,7 @@ define([
10601117
* @function _handle_clear_output
10611118
*/
10621119
Kernel.prototype._handle_clear_output = function (msg) {
1063-
var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
1120+
var callbacks = this.get_output_callbacks_for_msg(msg.parent_header.msg_id);
10641121
if (!callbacks || !callbacks.iopub) {
10651122
return;
10661123
}
@@ -1078,7 +1135,7 @@ define([
10781135
Kernel.prototype._handle_output_message = function (msg) {
10791136
var that = this;
10801137
var msg_id = msg.parent_header.msg_id;
1081-
var callbacks = this.get_callbacks_for_msg(msg_id);
1138+
var callbacks = this.get_output_callbacks_for_msg(msg_id);
10821139
if (['display_data', 'update_display_data', 'execute_result'].indexOf(msg.header.msg_type) > -1) {
10831140
// display_data messages may re-route based on their display_id
10841141
var display_id = (msg.content.transient || {}).display_id;
@@ -1110,8 +1167,9 @@ define([
11101167
if (this._display_id_to_parent_ids[display_id] === undefined) {
11111168
this._display_id_to_parent_ids[display_id] = [];
11121169
}
1113-
if (this._display_id_to_parent_ids[display_id].indexOf(msg_id) === -1) {
1114-
this._display_id_to_parent_ids[display_id].push(msg_id);
1170+
var callback_id = this.get_output_callback_id(msg_id);
1171+
if (this._display_id_to_parent_ids[display_id].indexOf(callback_id) === -1) {
1172+
this._display_id_to_parent_ids[display_id].push(callback_id);
11151173
}
11161174
// and in callbacks for cleanup on clear_callbacks_for_msg
11171175
if (callbacks && callbacks.display_ids.indexOf(display_id) === -1) {

notebook/tests/notebook/display_id.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,89 @@ casper.notebook_test(function () {
7979
this.test.assertEquals(outputs2[2].data['text/plain'], '4', 'output[2][2]');
8080
});
8181

82+
this.then(function () {
83+
this.echo("Test output callback overrides work with display ids");
84+
});
85+
86+
this.thenEvaluate(function () {
87+
Jupyter.notebook.insert_cell_at_index("code", 3);
88+
var cell = Jupyter.notebook.get_cell(3);
89+
cell.set_text([
90+
"display_with_id(5, 'here')",
91+
"display_with_id(6, 'here', update=True)",
92+
].join('\n'));
93+
cell.execute();
94+
var kernel = IPython.notebook.kernel;
95+
var msg_id = cell.last_msg_id;
96+
var callback_id = 'mycallbackid'
97+
cell.iopub_messages = [];
98+
var add_msg = function(msg) {
99+
msg.content.output_type = msg.msg_type;
100+
cell.iopub_messages.push(msg.content);
101+
};
102+
kernel.set_callbacks_for_msg(callback_id, {
103+
iopub: {
104+
output: add_msg,
105+
clear_output: add_msg,
106+
}
107+
}, false);
108+
kernel.output_callback_overrides_push(msg_id, callback_id);
109+
});
110+
111+
this.wait_for_idle();
112+
113+
this.then(function () {
114+
var returned = this.evaluate(function () {
115+
var cell = IPython.notebook.get_cell(3);
116+
return [cell.output_area.outputs, cell.iopub_messages];
117+
});
118+
var cell_results = returned[0];
119+
var callback_results = returned[1];
120+
this.test.assertEquals(cell_results.length, 0, "correct number of cell outputs");
121+
this.test.assertEquals(callback_results.length, 2, "correct number of callback outputs");
122+
this.test.assertEquals(callback_results[0].output_type, 'display_data', 'check output_type 0');
123+
this.test.assertEquals(callback_results[0].transient.display_id, 'here', 'check display id 0');
124+
this.test.assertEquals(callback_results[0].data['text/plain'], '5', 'value');
125+
this.test.assertEquals(callback_results[1].output_type, 'update_display_data', 'check output_type 1');
126+
this.test.assertEquals(callback_results[1].transient.display_id, 'here', 'display id 1');
127+
this.test.assertEquals(callback_results[1].data['text/plain'], '6', 'value');
128+
});
129+
130+
this.thenEvaluate(function () {
131+
Jupyter.notebook.insert_cell_at_index("code", 4);
132+
var cell = Jupyter.notebook.get_cell(4);
133+
cell.set_text([
134+
"display_with_id(7, 'here')",
135+
"display_with_id(8, 'here', update=True)",
136+
].join('\n'));
137+
cell.execute();
138+
});
139+
140+
this.wait_for_idle();
141+
142+
this.then(function () {
143+
var returned = JSON.parse(this.evaluate(function () {
144+
var cell3 = Jupyter.notebook.get_cell(3);
145+
var cell4 = Jupyter.notebook.get_cell(4);
146+
return JSON.stringify([cell4.output_area.outputs, cell3.iopub_messages]);
147+
}));
148+
var cell_results = returned[0];
149+
var callback_results = returned[1];
150+
this.test.assertEquals(cell_results.length, 1, "correct number of cell outputs");
151+
this.test.assertEquals(callback_results.length, 4, "correct number of callback outputs");
152+
this.test.assertEquals(callback_results[0].output_type, 'display_data', 'check output_type 0');
153+
this.test.assertEquals(callback_results[0].transient.display_id, 'here', 'check display id 0');
154+
this.test.assertEquals(callback_results[0].data['text/plain'], '5', 'value');
155+
this.test.assertEquals(callback_results[1].output_type, 'update_display_data', 'check output_type 1');
156+
this.test.assertEquals(callback_results[1].transient.display_id, 'here', 'display id 1');
157+
this.test.assertEquals(callback_results[1].data['text/plain'], '6', 'value');
158+
this.test.assertEquals(callback_results[2].output_type, 'display_data', 'check output_type 2');
159+
this.test.assertEquals(callback_results[2].transient.display_id, 'here', 'check display id 2');
160+
this.test.assertEquals(callback_results[2].data['text/plain'], '7', 'value');
161+
this.test.assertEquals(callback_results[3].output_type, 'update_display_data', 'check output_type 3');
162+
this.test.assertEquals(callback_results[3].transient.display_id, 'here', 'display id 3');
163+
this.test.assertEquals(callback_results[3].data['text/plain'], '8', 'value');
164+
});
165+
166+
82167
});

0 commit comments

Comments
 (0)