##// END OF EJS Templates
Skip exceptions
Juergen Hasch -
Show More
@@ -1,672 +1,677 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3 /**
3 /**
4 *
4 *
5 *
5 *
6 * @module codecell
6 * @module codecell
7 * @namespace codecell
7 * @namespace codecell
8 * @class CodeCell
8 * @class CodeCell
9 */
9 */
10
10
11
11
12 define([
12 define([
13 'base/js/namespace',
13 'base/js/namespace',
14 'jquery',
14 'jquery',
15 'base/js/utils',
15 'base/js/utils',
16 'base/js/keyboard',
16 'base/js/keyboard',
17 'services/config',
17 'services/config',
18 'notebook/js/cell',
18 'notebook/js/cell',
19 'notebook/js/outputarea',
19 'notebook/js/outputarea',
20 'notebook/js/completer',
20 'notebook/js/completer',
21 'notebook/js/celltoolbar',
21 'notebook/js/celltoolbar',
22 'codemirror/lib/codemirror',
22 'codemirror/lib/codemirror',
23 'codemirror/mode/python/python',
23 'codemirror/mode/python/python',
24 'notebook/js/codemirror-ipython'
24 'notebook/js/codemirror-ipython'
25 ], function(IPython,
25 ], function(IPython,
26 $,
26 $,
27 utils,
27 utils,
28 keyboard,
28 keyboard,
29 configmod,
29 configmod,
30 cell,
30 cell,
31 outputarea,
31 outputarea,
32 completer,
32 completer,
33 celltoolbar,
33 celltoolbar,
34 CodeMirror,
34 CodeMirror,
35 cmpython,
35 cmpython,
36 cmip
36 cmip
37 ) {
37 ) {
38 "use strict";
38 "use strict";
39
39
40 var Cell = cell.Cell;
40 var Cell = cell.Cell;
41
41
42 /* local util for codemirror */
42 /* local util for codemirror */
43 var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
43 var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
44
44
45 /**
45 /**
46 *
46 *
47 * function to delete until previous non blanking space character
47 * function to delete until previous non blanking space character
48 * or first multiple of 4 tabstop.
48 * or first multiple of 4 tabstop.
49 * @private
49 * @private
50 */
50 */
51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
51 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
52 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
53 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
54 var cur = cm.getCursor(), line = cm.getLine(cur.line);
55 var tabsize = cm.getOption('tabSize');
55 var tabsize = cm.getOption('tabSize');
56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
56 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
57 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
58 var select = cm.getRange(from,cur);
58 var select = cm.getRange(from,cur);
59 if( select.match(/^\ +$/) !== null){
59 if( select.match(/^\ +$/) !== null){
60 cm.replaceRange("",from,cur);
60 cm.replaceRange("",from,cur);
61 } else {
61 } else {
62 cm.deleteH(-1,"char");
62 cm.deleteH(-1,"char");
63 }
63 }
64 };
64 };
65
65
66 var keycodes = keyboard.keycodes;
66 var keycodes = keyboard.keycodes;
67
67
68 var CodeCell = function (kernel, options) {
68 var CodeCell = function (kernel, options) {
69 /**
69 /**
70 * Constructor
70 * Constructor
71 *
71 *
72 * A Cell conceived to write code.
72 * A Cell conceived to write code.
73 *
73 *
74 * Parameters:
74 * Parameters:
75 * kernel: Kernel instance
75 * kernel: Kernel instance
76 * The kernel doesn't have to be set at creation time, in that case
76 * The kernel doesn't have to be set at creation time, in that case
77 * it will be null and set_kernel has to be called later.
77 * it will be null and set_kernel has to be called later.
78 * options: dictionary
78 * options: dictionary
79 * Dictionary of keyword arguments.
79 * Dictionary of keyword arguments.
80 * events: $(Events) instance
80 * events: $(Events) instance
81 * config: dictionary
81 * config: dictionary
82 * keyboard_manager: KeyboardManager instance
82 * keyboard_manager: KeyboardManager instance
83 * notebook: Notebook instance
83 * notebook: Notebook instance
84 * tooltip: Tooltip instance
84 * tooltip: Tooltip instance
85 */
85 */
86 this.kernel = kernel || null;
86 this.kernel = kernel || null;
87 this.notebook = options.notebook;
87 this.notebook = options.notebook;
88 this.collapsed = false;
88 this.collapsed = false;
89 this.events = options.events;
89 this.events = options.events;
90 this.tooltip = options.tooltip;
90 this.tooltip = options.tooltip;
91 this.config = options.config;
91 this.config = options.config;
92 this.class_config = new configmod.ConfigWithDefaults(this.config,
92 this.class_config = new configmod.ConfigWithDefaults(this.config,
93 CodeCell.config_defaults, 'CodeCell');
93 CodeCell.config_defaults, 'CodeCell');
94
94
95 // create all attributed in constructor function
95 // create all attributed in constructor function
96 // even if null for V8 VM optimisation
96 // even if null for V8 VM optimisation
97 this.input_prompt_number = null;
97 this.input_prompt_number = null;
98 this.celltoolbar = null;
98 this.celltoolbar = null;
99 this.output_area = null;
99 this.output_area = null;
100 // Keep a stack of the 'active' output areas (where active means the
100 // Keep a stack of the 'active' output areas (where active means the
101 // output area that recieves output). When a user activates an output
101 // output area that recieves output). When a user activates an output
102 // area, it gets pushed to the stack. Then, when the output area is
102 // area, it gets pushed to the stack. Then, when the output area is
103 // deactivated, it's popped from the stack. When the stack is empty,
103 // deactivated, it's popped from the stack. When the stack is empty,
104 // the cell's output area is used.
104 // the cell's output area is used.
105 this.active_output_areas = [];
105 this.active_output_areas = [];
106 var that = this;
106 var that = this;
107 Object.defineProperty(this, 'active_output_area', {
107 Object.defineProperty(this, 'active_output_area', {
108 get: function() {
108 get: function() {
109 if (that.active_output_areas && that.active_output_areas.length > 0) {
109 if (that.active_output_areas && that.active_output_areas.length > 0) {
110 return that.active_output_areas[that.active_output_areas.length-1];
110 return that.active_output_areas[that.active_output_areas.length-1];
111 } else {
111 } else {
112 return that.output_area;
112 return that.output_area;
113 }
113 }
114 },
114 },
115 });
115 });
116
116
117 this.last_msg_id = null;
117 this.last_msg_id = null;
118 this.completer = null;
118 this.completer = null;
119 this.widget_views = [];
119 this.widget_views = [];
120 this._widgets_live = true;
120 this._widgets_live = true;
121
121
122 Cell.apply(this,[{
122 Cell.apply(this,[{
123 config: $.extend({}, CodeCell.options_default),
123 config: $.extend({}, CodeCell.options_default),
124 keyboard_manager: options.keyboard_manager,
124 keyboard_manager: options.keyboard_manager,
125 events: this.events}]);
125 events: this.events}]);
126
126
127 // Attributes we want to override in this subclass.
127 // Attributes we want to override in this subclass.
128 this.cell_type = "code";
128 this.cell_type = "code";
129 this.element.focusout(
129 this.element.focusout(
130 function() { that.auto_highlight(); }
130 function() { that.auto_highlight(); }
131 );
131 );
132 };
132 };
133
133
134 CodeCell.options_default = {
134 CodeCell.options_default = {
135 cm_config : {
135 cm_config : {
136 extraKeys: {
136 extraKeys: {
137 "Tab" : "indentMore",
137 "Tab" : "indentMore",
138 "Shift-Tab" : "indentLess",
138 "Shift-Tab" : "indentLess",
139 "Backspace" : "delSpaceToPrevTabStop",
139 "Backspace" : "delSpaceToPrevTabStop",
140 "Cmd-/" : "toggleComment",
140 "Cmd-/" : "toggleComment",
141 "Ctrl-/" : "toggleComment"
141 "Ctrl-/" : "toggleComment"
142 },
142 },
143 mode: 'ipython',
143 mode: 'ipython',
144 theme: 'ipython',
144 theme: 'ipython',
145 matchBrackets: true
145 matchBrackets: true
146 }
146 }
147 };
147 };
148
148
149 CodeCell.config_defaults = {
149 CodeCell.config_defaults = {
150 highlight_modes : {
150 highlight_modes : {
151 'magic_javascript' :{'reg':[/^%%javascript/]},
151 'magic_javascript' :{'reg':[/^%%javascript/]},
152 'magic_perl' :{'reg':[/^%%perl/]},
152 'magic_perl' :{'reg':[/^%%perl/]},
153 'magic_ruby' :{'reg':[/^%%ruby/]},
153 'magic_ruby' :{'reg':[/^%%ruby/]},
154 'magic_python' :{'reg':[/^%%python3?/]},
154 'magic_python' :{'reg':[/^%%python3?/]},
155 'magic_shell' :{'reg':[/^%%bash/]},
155 'magic_shell' :{'reg':[/^%%bash/]},
156 'magic_r' :{'reg':[/^%%R/]},
156 'magic_r' :{'reg':[/^%%R/]},
157 'magic_text/x-cython' :{'reg':[/^%%cython/]},
157 'magic_text/x-cython' :{'reg':[/^%%cython/]},
158 },
158 },
159 };
159 };
160
160
161 CodeCell.msg_cells = {};
161 CodeCell.msg_cells = {};
162
162
163 CodeCell.prototype = Object.create(Cell.prototype);
163 CodeCell.prototype = Object.create(Cell.prototype);
164
164
165 /**
165 /**
166 * @method push_output_area
166 * @method push_output_area
167 */
167 */
168 CodeCell.prototype.push_output_area = function (output_area) {
168 CodeCell.prototype.push_output_area = function (output_area) {
169 this.active_output_areas.push(output_area);
169 this.active_output_areas.push(output_area);
170 };
170 };
171
171
172 /**
172 /**
173 * @method pop_output_area
173 * @method pop_output_area
174 */
174 */
175 CodeCell.prototype.pop_output_area = function (output_area) {
175 CodeCell.prototype.pop_output_area = function (output_area) {
176 var index = this.active_output_areas.lastIndexOf(output_area);
176 var index = this.active_output_areas.lastIndexOf(output_area);
177 if (index > -1) {
177 if (index > -1) {
178 this.active_output_areas.splice(index, 1);
178 this.active_output_areas.splice(index, 1);
179 }
179 }
180 };
180 };
181
181
182 /** @method create_element */
182 /** @method create_element */
183 CodeCell.prototype.create_element = function () {
183 CodeCell.prototype.create_element = function () {
184 Cell.prototype.create_element.apply(this, arguments);
184 Cell.prototype.create_element.apply(this, arguments);
185
185
186 var cell = $('<div></div>').addClass('cell code_cell');
186 var cell = $('<div></div>').addClass('cell code_cell');
187 cell.attr('tabindex','2');
187 cell.attr('tabindex','2');
188
188
189 var input = $('<div></div>').addClass('input');
189 var input = $('<div></div>').addClass('input');
190 var prompt = $('<div/>').addClass('prompt input_prompt');
190 var prompt = $('<div/>').addClass('prompt input_prompt');
191 var inner_cell = $('<div/>').addClass('inner_cell');
191 var inner_cell = $('<div/>').addClass('inner_cell');
192 this.celltoolbar = new celltoolbar.CellToolbar({
192 this.celltoolbar = new celltoolbar.CellToolbar({
193 cell: this,
193 cell: this,
194 notebook: this.notebook});
194 notebook: this.notebook});
195 inner_cell.append(this.celltoolbar.element);
195 inner_cell.append(this.celltoolbar.element);
196 var input_area = $('<div/>').addClass('input_area');
196 var input_area = $('<div/>').addClass('input_area');
197 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
197 this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
198 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
198 this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
199 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
199 $(this.code_mirror.getInputField()).attr("spellcheck", "false");
200 inner_cell.append(input_area);
200 inner_cell.append(input_area);
201 input.append(prompt).append(inner_cell);
201 input.append(prompt).append(inner_cell);
202
202
203 var widget_area = $('<div/>')
203 var widget_area = $('<div/>')
204 .addClass('widget-area')
204 .addClass('widget-area')
205 .hide();
205 .hide();
206 this.widget_area = widget_area;
206 this.widget_area = widget_area;
207 var widget_prompt = $('<div/>')
207 var widget_prompt = $('<div/>')
208 .addClass('prompt')
208 .addClass('prompt')
209 .appendTo(widget_area);
209 .appendTo(widget_area);
210 var widget_subarea = $('<div/>')
210 var widget_subarea = $('<div/>')
211 .addClass('widget-subarea')
211 .addClass('widget-subarea')
212 .appendTo(widget_area);
212 .appendTo(widget_area);
213 this.widget_subarea = widget_subarea;
213 this.widget_subarea = widget_subarea;
214 var that = this;
214 var that = this;
215 var widget_clear_buton = $('<button />')
215 var widget_clear_buton = $('<button />')
216 .addClass('close')
216 .addClass('close')
217 .html('&times;')
217 .html('&times;')
218 .click(function() {
218 .click(function() {
219 widget_area.slideUp('', function(){
219 widget_area.slideUp('', function(){
220 for (var i = 0; i < that.widget_views.length; i++) {
220 for (var i = 0; i < that.widget_views.length; i++) {
221 var view = that.widget_views[i];
221 var view = that.widget_views[i];
222 view.remove();
222 view.remove();
223
223
224 // Remove widget live events.
224 // Remove widget live events.
225 view.off('comm:live', that._widget_live);
225 view.off('comm:live', that._widget_live);
226 view.off('comm:dead', that._widget_dead);
226 view.off('comm:dead', that._widget_dead);
227 }
227 }
228 that.widget_views = [];
228 that.widget_views = [];
229 widget_subarea.html('');
229 widget_subarea.html('');
230 });
230 });
231 })
231 })
232 .appendTo(widget_prompt);
232 .appendTo(widget_prompt);
233
233
234 var output = $('<div></div>');
234 var output = $('<div></div>');
235 cell.append(input).append(widget_area).append(output);
235 cell.append(input).append(widget_area).append(output);
236 this.element = cell;
236 this.element = cell;
237 this.output_area = new outputarea.OutputArea({
237 this.output_area = new outputarea.OutputArea({
238 selector: output,
238 selector: output,
239 prompt_area: true,
239 prompt_area: true,
240 events: this.events,
240 events: this.events,
241 keyboard_manager: this.keyboard_manager});
241 keyboard_manager: this.keyboard_manager});
242 this.completer = new completer.Completer(this, this.events);
242 this.completer = new completer.Completer(this, this.events);
243 };
243 };
244
244
245 /**
245 /**
246 * Display a widget view in the cell.
246 * Display a widget view in the cell.
247 */
247 */
248 CodeCell.prototype.display_widget_view = function(view_promise) {
248 CodeCell.prototype.display_widget_view = function(view_promise) {
249
249
250 // Display a dummy element
250 // Display a dummy element
251 var dummy = $('<div/>');
251 var dummy = $('<div/>');
252 this.widget_subarea.append(dummy);
252 this.widget_subarea.append(dummy);
253
253
254 // Display the view.
254 // Display the view.
255 var that = this;
255 var that = this;
256 return view_promise.then(function(view) {
256 return view_promise.then(function(view) {
257 that.widget_area.show();
257 that.widget_area.show();
258 dummy.replaceWith(view.$el);
258 dummy.replaceWith(view.$el);
259 that.widget_views.push(view);
259 that.widget_views.push(view);
260
260
261 // Check the live state of the view's model.
261 // Check the live state of the view's model.
262 if (view.model.comm_live) {
262 if (view.model.comm_live) {
263 that._widget_live(view);
263 that._widget_live(view);
264 } else {
264 } else {
265 that._widget_dead(view);
265 that._widget_dead(view);
266 }
266 }
267
267
268 // Listen to comm live events for the view.
268 // Listen to comm live events for the view.
269 view.on('comm:live', that._widget_live, that);
269 view.on('comm:live', that._widget_live, that);
270 view.on('comm:dead', that._widget_dead, that);
270 view.on('comm:dead', that._widget_dead, that);
271 return view;
271 return view;
272 });
272 });
273 };
273 };
274
274
275 /**
275 /**
276 * Handles when a widget loses it's comm connection.
276 * Handles when a widget loses it's comm connection.
277 * @param {WidgetView} view
277 * @param {WidgetView} view
278 */
278 */
279 CodeCell.prototype._widget_dead = function(view) {
279 CodeCell.prototype._widget_dead = function(view) {
280 if (this._widgets_live) {
280 if (this._widgets_live) {
281 this._widgets_live = false;
281 this._widgets_live = false;
282 this.widget_area.addClass('connection-problems');
282 this.widget_area.addClass('connection-problems');
283 }
283 }
284
284
285 };
285 };
286
286
287 /**
287 /**
288 * Handles when a widget is connected to a live comm.
288 * Handles when a widget is connected to a live comm.
289 * @param {WidgetView} view
289 * @param {WidgetView} view
290 */
290 */
291 CodeCell.prototype._widget_live = function(view) {
291 CodeCell.prototype._widget_live = function(view) {
292 if (!this._widgets_live) {
292 if (!this._widgets_live) {
293 // Check that the other widgets are live too. O(N) operation.
293 // Check that the other widgets are live too. O(N) operation.
294 // Abort the function at the first dead widget found.
294 // Abort the function at the first dead widget found.
295 for (var i = 0; i < this.widget_views.length; i++) {
295 for (var i = 0; i < this.widget_views.length; i++) {
296 if (!this.widget_views[i].model.comm_live) return;
296 if (!this.widget_views[i].model.comm_live) return;
297 }
297 }
298 this._widgets_live = true;
298 this._widgets_live = true;
299 this.widget_area.removeClass('connection-problems');
299 this.widget_area.removeClass('connection-problems');
300 }
300 }
301 };
301 };
302
302
303 /** @method bind_events */
303 /** @method bind_events */
304 CodeCell.prototype.bind_events = function () {
304 CodeCell.prototype.bind_events = function () {
305 Cell.prototype.bind_events.apply(this);
305 Cell.prototype.bind_events.apply(this);
306 var that = this;
306 var that = this;
307
307
308 this.element.focusout(
308 this.element.focusout(
309 function() { that.auto_highlight(); }
309 function() { that.auto_highlight(); }
310 );
310 );
311 };
311 };
312
312
313
313
314 /**
314 /**
315 * This method gets called in CodeMirror's onKeyDown/onKeyPress
315 * This method gets called in CodeMirror's onKeyDown/onKeyPress
316 * handlers and is used to provide custom key handling. Its return
316 * handlers and is used to provide custom key handling. Its return
317 * value is used to determine if CodeMirror should ignore the event:
317 * value is used to determine if CodeMirror should ignore the event:
318 * true = ignore, false = don't ignore.
318 * true = ignore, false = don't ignore.
319 * @method handle_codemirror_keyevent
319 * @method handle_codemirror_keyevent
320 */
320 */
321
321
322 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
322 CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
323
323
324 var that = this;
324 var that = this;
325 // whatever key is pressed, first, cancel the tooltip request before
325 // whatever key is pressed, first, cancel the tooltip request before
326 // they are sent, and remove tooltip if any, except for tab again
326 // they are sent, and remove tooltip if any, except for tab again
327 var tooltip_closed = null;
327 var tooltip_closed = null;
328 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
328 if (event.type === 'keydown' && event.which !== keycodes.tab ) {
329 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
329 tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
330 }
330 }
331
331
332 var cur = editor.getCursor();
332 var cur = editor.getCursor();
333 if (event.keyCode === keycodes.enter){
333 if (event.keyCode === keycodes.enter){
334 this.auto_highlight();
334 this.auto_highlight();
335 }
335 }
336
336
337 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
337 if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
338 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
338 // triger on keypress (!) otherwise inconsistent event.which depending on plateform
339 // browser and keyboard layout !
339 // browser and keyboard layout !
340 // Pressing '(' , request tooltip, don't forget to reappend it
340 // Pressing '(' , request tooltip, don't forget to reappend it
341 // The second argument says to hide the tooltip if the docstring
341 // The second argument says to hide the tooltip if the docstring
342 // is actually empty
342 // is actually empty
343 this.tooltip.pending(that, true);
343 this.tooltip.pending(that, true);
344 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
344 } else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
345 // If tooltip is active, cancel it. The call to
345 // If tooltip is active, cancel it. The call to
346 // remove_and_cancel_tooltip above doesn't pass, force=true.
346 // remove_and_cancel_tooltip above doesn't pass, force=true.
347 // Because of this it won't actually close the tooltip
347 // Because of this it won't actually close the tooltip
348 // if it is in sticky mode. Thus, we have to check again if it is open
348 // if it is in sticky mode. Thus, we have to check again if it is open
349 // and close it with force=true.
349 // and close it with force=true.
350 if (!this.tooltip._hidden) {
350 if (!this.tooltip._hidden) {
351 this.tooltip.remove_and_cancel_tooltip(true);
351 this.tooltip.remove_and_cancel_tooltip(true);
352 }
352 }
353 // If we closed the tooltip, don't let CM or the global handlers
353 // If we closed the tooltip, don't let CM or the global handlers
354 // handle this event.
354 // handle this event.
355 event.codemirrorIgnore = true;
355 event.codemirrorIgnore = true;
356 event.preventDefault();
356 event.preventDefault();
357 return true;
357 return true;
358 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
358 } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
359 if (editor.somethingSelected() || editor.getSelections().length !== 1){
359 if (editor.somethingSelected() || editor.getSelections().length !== 1){
360 var anchor = editor.getCursor("anchor");
360 var anchor = editor.getCursor("anchor");
361 var head = editor.getCursor("head");
361 var head = editor.getCursor("head");
362 if( anchor.line !== head.line){
362 if( anchor.line !== head.line){
363 return false;
363 return false;
364 }
364 }
365 }
365 }
366 this.tooltip.request(that);
366 this.tooltip.request(that);
367 event.codemirrorIgnore = true;
367 event.codemirrorIgnore = true;
368 event.preventDefault();
368 event.preventDefault();
369 return true;
369 return true;
370 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
370 } else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
371 // Tab completion.
371 // Tab completion.
372 this.tooltip.remove_and_cancel_tooltip();
372 this.tooltip.remove_and_cancel_tooltip();
373
373
374 // completion does not work on multicursor, it might be possible though in some cases
374 // completion does not work on multicursor, it might be possible though in some cases
375 if (editor.somethingSelected() || editor.getSelections().length > 1) {
375 if (editor.somethingSelected() || editor.getSelections().length > 1) {
376 return false;
376 return false;
377 }
377 }
378 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
378 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
379 if (pre_cursor.trim() === "") {
379 if (pre_cursor.trim() === "") {
380 // Don't autocomplete if the part of the line before the cursor
380 // Don't autocomplete if the part of the line before the cursor
381 // is empty. In this case, let CodeMirror handle indentation.
381 // is empty. In this case, let CodeMirror handle indentation.
382 return false;
382 return false;
383 } else {
383 } else {
384 event.codemirrorIgnore = true;
384 event.codemirrorIgnore = true;
385 event.preventDefault();
385 event.preventDefault();
386 this.completer.startCompletion();
386 this.completer.startCompletion();
387 return true;
387 return true;
388 }
388 }
389 }
389 }
390
390
391 // keyboard event wasn't one of those unique to code cells, let's see
391 // keyboard event wasn't one of those unique to code cells, let's see
392 // if it's one of the generic ones (i.e. check edit mode shortcuts)
392 // if it's one of the generic ones (i.e. check edit mode shortcuts)
393 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
393 return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
394 };
394 };
395
395
396 // Kernel related calls.
396 // Kernel related calls.
397
397
398 CodeCell.prototype.set_kernel = function (kernel) {
398 CodeCell.prototype.set_kernel = function (kernel) {
399 this.kernel = kernel;
399 this.kernel = kernel;
400 };
400 };
401
401
402 /**
402 /**
403 * Execute current code cell to the kernel
403 * Execute current code cell to the kernel
404 * @method execute
404 * @method execute
405 */
405 */
406 CodeCell.prototype.execute = function () {
406 CodeCell.prototype.execute = function (skip_exceptions) {
407 if (!this.kernel || !this.kernel.is_connected()) {
407 if (!this.kernel || !this.kernel.is_connected()) {
408 console.log("Can't execute, kernel is not connected.");
408 console.log("Can't execute, kernel is not connected.");
409 return;
409 return;
410 }
410 }
411
411
412 this.active_output_area.clear_output(false, true);
412 this.active_output_area.clear_output(false, true);
413
413
414 if (skip_exceptions === undefined) {
415 skip_exceptions = false;
416 }
417
414 // Clear widget area
418 // Clear widget area
415 for (var i = 0; i < this.widget_views.length; i++) {
419 for (var i = 0; i < this.widget_views.length; i++) {
416 var view = this.widget_views[i];
420 var view = this.widget_views[i];
417 view.remove();
421 view.remove();
418
422
419 // Remove widget live events.
423 // Remove widget live events.
420 view.off('comm:live', this._widget_live);
424 view.off('comm:live', this._widget_live);
421 view.off('comm:dead', this._widget_dead);
425 view.off('comm:dead', this._widget_dead);
422 }
426 }
423 this.widget_views = [];
427 this.widget_views = [];
424 this.widget_subarea.html('');
428 this.widget_subarea.html('');
425 this.widget_subarea.height('');
429 this.widget_subarea.height('');
426 this.widget_area.height('');
430 this.widget_area.height('');
427 this.widget_area.hide();
431 this.widget_area.hide();
428
432
429 this.set_input_prompt('*');
433 this.set_input_prompt('*');
430 this.element.addClass("running");
434 this.element.addClass("running");
431 if (this.last_msg_id) {
435 if (this.last_msg_id) {
432 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
436 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
433 }
437 }
434 var callbacks = this.get_callbacks();
438 var callbacks = this.get_callbacks();
435
439
436 var old_msg_id = this.last_msg_id;
440 var old_msg_id = this.last_msg_id;
437 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
441 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
442 skip_exceptions : skip_exceptions});
438 if (old_msg_id) {
443 if (old_msg_id) {
439 delete CodeCell.msg_cells[old_msg_id];
444 delete CodeCell.msg_cells[old_msg_id];
440 }
445 }
441 CodeCell.msg_cells[this.last_msg_id] = this;
446 CodeCell.msg_cells[this.last_msg_id] = this;
442 this.render();
447 this.render();
443 this.events.trigger('execute.CodeCell', {cell: this});
448 this.events.trigger('execute.CodeCell', {cell: this});
444 };
449 };
445
450
446 /**
451 /**
447 * Construct the default callbacks for
452 * Construct the default callbacks for
448 * @method get_callbacks
453 * @method get_callbacks
449 */
454 */
450 CodeCell.prototype.get_callbacks = function () {
455 CodeCell.prototype.get_callbacks = function () {
451 var that = this;
456 var that = this;
452 return {
457 return {
453 shell : {
458 shell : {
454 reply : $.proxy(this._handle_execute_reply, this),
459 reply : $.proxy(this._handle_execute_reply, this),
455 payload : {
460 payload : {
456 set_next_input : $.proxy(this._handle_set_next_input, this),
461 set_next_input : $.proxy(this._handle_set_next_input, this),
457 page : $.proxy(this._open_with_pager, this)
462 page : $.proxy(this._open_with_pager, this)
458 }
463 }
459 },
464 },
460 iopub : {
465 iopub : {
461 output : function() {
466 output : function() {
462 that.active_output_area.handle_output.apply(that.active_output_area, arguments);
467 that.active_output_area.handle_output.apply(that.active_output_area, arguments);
463 },
468 },
464 clear_output : function() {
469 clear_output : function() {
465 that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments);
470 that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments);
466 },
471 },
467 },
472 },
468 input : $.proxy(this._handle_input_request, this)
473 input : $.proxy(this._handle_input_request, this)
469 };
474 };
470 };
475 };
471
476
472 CodeCell.prototype._open_with_pager = function (payload) {
477 CodeCell.prototype._open_with_pager = function (payload) {
473 this.events.trigger('open_with_text.Pager', payload);
478 this.events.trigger('open_with_text.Pager', payload);
474 };
479 };
475
480
476 /**
481 /**
477 * @method _handle_execute_reply
482 * @method _handle_execute_reply
478 * @private
483 * @private
479 */
484 */
480 CodeCell.prototype._handle_execute_reply = function (msg) {
485 CodeCell.prototype._handle_execute_reply = function (msg) {
481 this.set_input_prompt(msg.content.execution_count);
486 this.set_input_prompt(msg.content.execution_count);
482 this.element.removeClass("running");
487 this.element.removeClass("running");
483 this.events.trigger('set_dirty.Notebook', {value: true});
488 this.events.trigger('set_dirty.Notebook', {value: true});
484 };
489 };
485
490
486 /**
491 /**
487 * @method _handle_set_next_input
492 * @method _handle_set_next_input
488 * @private
493 * @private
489 */
494 */
490 CodeCell.prototype._handle_set_next_input = function (payload) {
495 CodeCell.prototype._handle_set_next_input = function (payload) {
491 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
496 var data = {'cell': this, 'text': payload.text, replace: payload.replace};
492 this.events.trigger('set_next_input.Notebook', data);
497 this.events.trigger('set_next_input.Notebook', data);
493 };
498 };
494
499
495 /**
500 /**
496 * @method _handle_input_request
501 * @method _handle_input_request
497 * @private
502 * @private
498 */
503 */
499 CodeCell.prototype._handle_input_request = function (msg) {
504 CodeCell.prototype._handle_input_request = function (msg) {
500 this.active_output_area.append_raw_input(msg);
505 this.active_output_area.append_raw_input(msg);
501 };
506 };
502
507
503
508
504 // Basic cell manipulation.
509 // Basic cell manipulation.
505
510
506 CodeCell.prototype.select = function () {
511 CodeCell.prototype.select = function () {
507 var cont = Cell.prototype.select.apply(this);
512 var cont = Cell.prototype.select.apply(this);
508 if (cont) {
513 if (cont) {
509 this.code_mirror.refresh();
514 this.code_mirror.refresh();
510 this.auto_highlight();
515 this.auto_highlight();
511 }
516 }
512 return cont;
517 return cont;
513 };
518 };
514
519
515 CodeCell.prototype.render = function () {
520 CodeCell.prototype.render = function () {
516 var cont = Cell.prototype.render.apply(this);
521 var cont = Cell.prototype.render.apply(this);
517 // Always execute, even if we are already in the rendered state
522 // Always execute, even if we are already in the rendered state
518 return cont;
523 return cont;
519 };
524 };
520
525
521 CodeCell.prototype.select_all = function () {
526 CodeCell.prototype.select_all = function () {
522 var start = {line: 0, ch: 0};
527 var start = {line: 0, ch: 0};
523 var nlines = this.code_mirror.lineCount();
528 var nlines = this.code_mirror.lineCount();
524 var last_line = this.code_mirror.getLine(nlines-1);
529 var last_line = this.code_mirror.getLine(nlines-1);
525 var end = {line: nlines-1, ch: last_line.length};
530 var end = {line: nlines-1, ch: last_line.length};
526 this.code_mirror.setSelection(start, end);
531 this.code_mirror.setSelection(start, end);
527 };
532 };
528
533
529
534
530 CodeCell.prototype.collapse_output = function () {
535 CodeCell.prototype.collapse_output = function () {
531 this.output_area.collapse();
536 this.output_area.collapse();
532 };
537 };
533
538
534
539
535 CodeCell.prototype.expand_output = function () {
540 CodeCell.prototype.expand_output = function () {
536 this.output_area.expand();
541 this.output_area.expand();
537 this.output_area.unscroll_area();
542 this.output_area.unscroll_area();
538 };
543 };
539
544
540 CodeCell.prototype.scroll_output = function () {
545 CodeCell.prototype.scroll_output = function () {
541 this.output_area.expand();
546 this.output_area.expand();
542 this.output_area.scroll_if_long();
547 this.output_area.scroll_if_long();
543 };
548 };
544
549
545 CodeCell.prototype.toggle_output = function () {
550 CodeCell.prototype.toggle_output = function () {
546 this.output_area.toggle_output();
551 this.output_area.toggle_output();
547 };
552 };
548
553
549 CodeCell.prototype.toggle_output_scroll = function () {
554 CodeCell.prototype.toggle_output_scroll = function () {
550 this.output_area.toggle_scroll();
555 this.output_area.toggle_scroll();
551 };
556 };
552
557
553
558
554 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
559 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
555 var ns;
560 var ns;
556 if (prompt_value === undefined || prompt_value === null) {
561 if (prompt_value === undefined || prompt_value === null) {
557 ns = "&nbsp;";
562 ns = "&nbsp;";
558 } else {
563 } else {
559 ns = encodeURIComponent(prompt_value);
564 ns = encodeURIComponent(prompt_value);
560 }
565 }
561 return 'In&nbsp;[' + ns + ']:';
566 return 'In&nbsp;[' + ns + ']:';
562 };
567 };
563
568
564 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
569 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
565 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
570 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
566 for(var i=1; i < lines_number; i++) {
571 for(var i=1; i < lines_number; i++) {
567 html.push(['...:']);
572 html.push(['...:']);
568 }
573 }
569 return html.join('<br/>');
574 return html.join('<br/>');
570 };
575 };
571
576
572 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
577 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
573
578
574
579
575 CodeCell.prototype.set_input_prompt = function (number) {
580 CodeCell.prototype.set_input_prompt = function (number) {
576 var nline = 1;
581 var nline = 1;
577 if (this.code_mirror !== undefined) {
582 if (this.code_mirror !== undefined) {
578 nline = this.code_mirror.lineCount();
583 nline = this.code_mirror.lineCount();
579 }
584 }
580 this.input_prompt_number = number;
585 this.input_prompt_number = number;
581 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
586 var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
582 // This HTML call is okay because the user contents are escaped.
587 // This HTML call is okay because the user contents are escaped.
583 this.element.find('div.input_prompt').html(prompt_html);
588 this.element.find('div.input_prompt').html(prompt_html);
584 };
589 };
585
590
586
591
587 CodeCell.prototype.clear_input = function () {
592 CodeCell.prototype.clear_input = function () {
588 this.code_mirror.setValue('');
593 this.code_mirror.setValue('');
589 };
594 };
590
595
591
596
592 CodeCell.prototype.get_text = function () {
597 CodeCell.prototype.get_text = function () {
593 return this.code_mirror.getValue();
598 return this.code_mirror.getValue();
594 };
599 };
595
600
596
601
597 CodeCell.prototype.set_text = function (code) {
602 CodeCell.prototype.set_text = function (code) {
598 return this.code_mirror.setValue(code);
603 return this.code_mirror.setValue(code);
599 };
604 };
600
605
601
606
602 CodeCell.prototype.clear_output = function (wait) {
607 CodeCell.prototype.clear_output = function (wait) {
603 this.active_output_area.clear_output(wait);
608 this.active_output_area.clear_output(wait);
604 this.set_input_prompt();
609 this.set_input_prompt();
605 };
610 };
606
611
607
612
608 // JSON serialization
613 // JSON serialization
609
614
610 CodeCell.prototype.fromJSON = function (data) {
615 CodeCell.prototype.fromJSON = function (data) {
611 Cell.prototype.fromJSON.apply(this, arguments);
616 Cell.prototype.fromJSON.apply(this, arguments);
612 if (data.cell_type === 'code') {
617 if (data.cell_type === 'code') {
613 if (data.source !== undefined) {
618 if (data.source !== undefined) {
614 this.set_text(data.source);
619 this.set_text(data.source);
615 // make this value the starting point, so that we can only undo
620 // make this value the starting point, so that we can only undo
616 // to this state, instead of a blank cell
621 // to this state, instead of a blank cell
617 this.code_mirror.clearHistory();
622 this.code_mirror.clearHistory();
618 this.auto_highlight();
623 this.auto_highlight();
619 }
624 }
620 this.set_input_prompt(data.execution_count);
625 this.set_input_prompt(data.execution_count);
621 this.output_area.trusted = data.metadata.trusted || false;
626 this.output_area.trusted = data.metadata.trusted || false;
622 this.output_area.fromJSON(data.outputs);
627 this.output_area.fromJSON(data.outputs);
623 if (data.metadata.collapsed !== undefined) {
628 if (data.metadata.collapsed !== undefined) {
624 if (data.metadata.collapsed) {
629 if (data.metadata.collapsed) {
625 this.collapse_output();
630 this.collapse_output();
626 } else {
631 } else {
627 this.expand_output();
632 this.expand_output();
628 }
633 }
629 }
634 }
630 }
635 }
631 };
636 };
632
637
633
638
634 CodeCell.prototype.toJSON = function () {
639 CodeCell.prototype.toJSON = function () {
635 var data = Cell.prototype.toJSON.apply(this);
640 var data = Cell.prototype.toJSON.apply(this);
636 data.source = this.get_text();
641 data.source = this.get_text();
637 // is finite protect against undefined and '*' value
642 // is finite protect against undefined and '*' value
638 if (isFinite(this.input_prompt_number)) {
643 if (isFinite(this.input_prompt_number)) {
639 data.execution_count = this.input_prompt_number;
644 data.execution_count = this.input_prompt_number;
640 } else {
645 } else {
641 data.execution_count = null;
646 data.execution_count = null;
642 }
647 }
643 var outputs = this.output_area.toJSON();
648 var outputs = this.output_area.toJSON();
644 data.outputs = outputs;
649 data.outputs = outputs;
645 data.metadata.trusted = this.output_area.trusted;
650 data.metadata.trusted = this.output_area.trusted;
646 data.metadata.collapsed = this.output_area.collapsed;
651 data.metadata.collapsed = this.output_area.collapsed;
647 return data;
652 return data;
648 };
653 };
649
654
650 /**
655 /**
651 * handle cell level logic when a cell is unselected
656 * handle cell level logic when a cell is unselected
652 * @method unselect
657 * @method unselect
653 * @return is the action being taken
658 * @return is the action being taken
654 */
659 */
655 CodeCell.prototype.unselect = function () {
660 CodeCell.prototype.unselect = function () {
656 var cont = Cell.prototype.unselect.apply(this);
661 var cont = Cell.prototype.unselect.apply(this);
657 if (cont) {
662 if (cont) {
658 // When a code cell is usnelected, make sure that the corresponding
663 // When a code cell is usnelected, make sure that the corresponding
659 // tooltip and completer to that cell is closed.
664 // tooltip and completer to that cell is closed.
660 this.tooltip.remove_and_cancel_tooltip(true);
665 this.tooltip.remove_and_cancel_tooltip(true);
661 if (this.completer !== null) {
666 if (this.completer !== null) {
662 this.completer.close();
667 this.completer.close();
663 }
668 }
664 }
669 }
665 return cont;
670 return cont;
666 };
671 };
667
672
668 // Backwards compatability.
673 // Backwards compatability.
669 IPython.CodeCell = CodeCell;
674 IPython.CodeCell = CodeCell;
670
675
671 return {'CodeCell': CodeCell};
676 return {'CodeCell': CodeCell};
672 });
677 });
@@ -1,78 +1,113 b''
1 //
1 //
2 // Test code cell execution.
2 // Test code cell execution.
3 //
3 //
4 casper.notebook_test(function () {
4 casper.notebook_test(function () {
5 this.evaluate(function () {
5 this.evaluate(function () {
6 var cell = IPython.notebook.get_cell(0);
6 var cell = IPython.notebook.get_cell(0);
7 cell.set_text('a=10; print(a)');
7 cell.set_text('a=10; print(a)');
8 cell.execute();
8 cell.execute();
9 });
9 });
10
10
11 this.wait_for_output(0);
11 this.wait_for_output(0);
12
12
13 // refactor this into just a get_output(0)
13 // refactor this into just a get_output(0)
14 this.then(function () {
14 this.then(function () {
15 var result = this.get_output_cell(0);
15 var result = this.get_output_cell(0);
16 this.test.assertEquals(result.text, '10\n', 'cell execute (using js)');
16 this.test.assertEquals(result.text, '10\n', 'cell execute (using js)');
17 });
17 });
18
18
19
19
20 // do it again with the keyboard shortcut
20 // do it again with the keyboard shortcut
21 this.thenEvaluate(function () {
21 this.thenEvaluate(function () {
22 var cell = IPython.notebook.get_cell(0);
22 var cell = IPython.notebook.get_cell(0);
23 cell.set_text('a=11; print(a)');
23 cell.set_text('a=11; print(a)');
24 cell.clear_output();
24 cell.clear_output();
25 });
25 });
26
26
27 this.then(function(){
27 this.then(function(){
28
28
29 this.trigger_keydown('shift-enter');
29 this.trigger_keydown('shift-enter');
30 });
30 });
31
31
32 this.wait_for_output(0);
32 this.wait_for_output(0);
33
33
34 this.then(function () {
34 this.then(function () {
35 var result = this.get_output_cell(0);
35 var result = this.get_output_cell(0);
36 var num_cells = this.get_cells_length();
36 var num_cells = this.get_cells_length();
37 this.test.assertEquals(result.text, '11\n', 'cell execute (using ctrl-enter)');
37 this.test.assertEquals(result.text, '11\n', 'cell execute (using ctrl-enter)');
38 this.test.assertEquals(num_cells, 2, 'shift-enter adds a new cell at the bottom')
38 this.test.assertEquals(num_cells, 2, 'shift-enter adds a new cell at the bottom')
39 });
39 });
40
40
41 // do it again with the keyboard shortcut
41 // do it again with the keyboard shortcut
42 this.thenEvaluate(function () {
42 this.thenEvaluate(function () {
43 IPython.notebook.select(1);
43 IPython.notebook.select(1);
44 IPython.notebook.delete_cell();
44 IPython.notebook.delete_cell();
45 var cell = IPython.notebook.get_cell(0);
45 var cell = IPython.notebook.get_cell(0);
46 cell.set_text('a=12; print(a)');
46 cell.set_text('a=12; print(a)');
47 cell.clear_output();
47 cell.clear_output();
48 });
48 });
49
49
50 this.then(function(){
50 this.then(function(){
51 this.trigger_keydown('ctrl-enter');
51 this.trigger_keydown('ctrl-enter');
52 });
52 });
53
53
54 this.wait_for_output(0);
54 this.wait_for_output(0);
55
55
56 this.then(function () {
56 this.then(function () {
57 var result = this.get_output_cell(0);
57 var result = this.get_output_cell(0);
58 var num_cells = this.get_cells_length();
58 var num_cells = this.get_cells_length();
59 this.test.assertEquals(result.text, '12\n', 'cell execute (using shift-enter)');
59 this.test.assertEquals(result.text, '12\n', 'cell execute (using shift-enter)');
60 this.test.assertEquals(num_cells, 1, 'ctrl-enter adds no new cell at the bottom')
60 this.test.assertEquals(num_cells, 1, 'ctrl-enter adds no new cell at the bottom')
61 });
61 });
62
62
63 // press the "play" triangle button in the toolbar
63 // press the "play" triangle button in the toolbar
64 this.thenEvaluate(function () {
64 this.thenEvaluate(function () {
65 var cell = IPython.notebook.get_cell(0);
65 var cell = IPython.notebook.get_cell(0);
66 IPython.notebook.select(0);
66 IPython.notebook.select(0);
67 cell.clear_output();
67 cell.clear_output();
68 cell.set_text('a=13; print(a)');
68 cell.set_text('a=13; print(a)');
69 $("button[data-jupyter-action='ipython.run-select-next']")[0].click()
69 $("button[data-jupyter-action='ipython.run-select-next']")[0].click()
70 });
70 });
71
71
72 this.wait_for_output(0);
72 this.wait_for_output(0);
73
73
74 this.then(function () {
74 this.then(function () {
75 var result = this.get_output_cell(0);
75 var result = this.get_output_cell(0);
76 this.test.assertEquals(result.text, '13\n', 'cell execute (using "play" toolbar button)')
76 this.test.assertEquals(result.text, '13\n', 'cell execute (using "play" toolbar button)')
77 });
77 });
78
79 // run code with skip_exception
80 this.thenEvaluate(function () {
81 var cell0 = IPython.notebook.get_cell(0);
82 cell0.set_text('raise IOError');
83 IPython.notebook.insert_cell_below('code',0);
84 var cell1 = IPython.notebook.get_cell(1);
85 cell1.set_text('a=14; print(a)');
86 cell0.execute(skip_exception=true);
87 cell1.execute();
88 });
89
90 this.wait_for_output(1);
91
92 this.then(function () {
93 var result = this.get_output_cell(1);
94 this.test.assertEquals(result.text, '14\n', 'cell execute, skip exceptions');
95 });
96
97 this.thenEvaluate(function () {
98 var cell0 = IPython.notebook.get_cell(0);
99 cell0.set_text('raise IOError');
100 IPython.notebook.insert_cell_below('code',0);
101 var cell1 = IPython.notebook.get_cell(1);
102 cell1.set_text('a=14; print(a)');
103 cell0.execute();
104 cell1.execute();
105 });
106
107 this.wait_for_output(1);
108
109 this.then(function () {
110 var result = this.get_output_cell(1);
111 this.test.assertNotEquals(result.text, '14\n', 'cell execute, skip exceptions');
112 });
78 });
113 });
@@ -1,387 +1,390 b''
1 """Base class to manage the interaction with a running kernel"""
1 """Base class to manage the interaction with a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7 from IPython.kernel.channels import major_protocol_version
7 from IPython.kernel.channels import major_protocol_version
8 from IPython.utils.py3compat import string_types, iteritems
8 from IPython.utils.py3compat import string_types, iteritems
9
9
10 import zmq
10 import zmq
11
11
12 from IPython.utils.traitlets import (
12 from IPython.utils.traitlets import (
13 Any, Instance, Type,
13 Any, Instance, Type,
14 )
14 )
15
15
16 from .channelsabc import (ChannelABC, HBChannelABC)
16 from .channelsabc import (ChannelABC, HBChannelABC)
17 from .clientabc import KernelClientABC
17 from .clientabc import KernelClientABC
18 from .connect import ConnectionFileMixin
18 from .connect import ConnectionFileMixin
19
19
20
20
21 # some utilities to validate message structure, these might get moved elsewhere
21 # some utilities to validate message structure, these might get moved elsewhere
22 # if they prove to have more generic utility
22 # if they prove to have more generic utility
23
23
24 def validate_string_dict(dct):
24 def validate_string_dict(dct):
25 """Validate that the input is a dict with string keys and values.
25 """Validate that the input is a dict with string keys and values.
26
26
27 Raises ValueError if not."""
27 Raises ValueError if not."""
28 for k,v in iteritems(dct):
28 for k,v in iteritems(dct):
29 if not isinstance(k, string_types):
29 if not isinstance(k, string_types):
30 raise ValueError('key %r in dict must be a string' % k)
30 raise ValueError('key %r in dict must be a string' % k)
31 if not isinstance(v, string_types):
31 if not isinstance(v, string_types):
32 raise ValueError('value %r in dict must be a string' % v)
32 raise ValueError('value %r in dict must be a string' % v)
33
33
34
34
35 class KernelClient(ConnectionFileMixin):
35 class KernelClient(ConnectionFileMixin):
36 """Communicates with a single kernel on any host via zmq channels.
36 """Communicates with a single kernel on any host via zmq channels.
37
37
38 There are four channels associated with each kernel:
38 There are four channels associated with each kernel:
39
39
40 * shell: for request/reply calls to the kernel.
40 * shell: for request/reply calls to the kernel.
41 * iopub: for the kernel to publish results to frontends.
41 * iopub: for the kernel to publish results to frontends.
42 * hb: for monitoring the kernel's heartbeat.
42 * hb: for monitoring the kernel's heartbeat.
43 * stdin: for frontends to reply to raw_input calls in the kernel.
43 * stdin: for frontends to reply to raw_input calls in the kernel.
44
44
45 The methods of the channels are exposed as methods of the client itself
45 The methods of the channels are exposed as methods of the client itself
46 (KernelClient.execute, complete, history, etc.).
46 (KernelClient.execute, complete, history, etc.).
47 See the channels themselves for documentation of these methods.
47 See the channels themselves for documentation of these methods.
48
48
49 """
49 """
50
50
51 # The PyZMQ Context to use for communication with the kernel.
51 # The PyZMQ Context to use for communication with the kernel.
52 context = Instance(zmq.Context)
52 context = Instance(zmq.Context)
53 def _context_default(self):
53 def _context_default(self):
54 return zmq.Context.instance()
54 return zmq.Context.instance()
55
55
56 # The classes to use for the various channels
56 # The classes to use for the various channels
57 shell_channel_class = Type(ChannelABC)
57 shell_channel_class = Type(ChannelABC)
58 iopub_channel_class = Type(ChannelABC)
58 iopub_channel_class = Type(ChannelABC)
59 stdin_channel_class = Type(ChannelABC)
59 stdin_channel_class = Type(ChannelABC)
60 hb_channel_class = Type(HBChannelABC)
60 hb_channel_class = Type(HBChannelABC)
61
61
62 # Protected traits
62 # Protected traits
63 _shell_channel = Any
63 _shell_channel = Any
64 _iopub_channel = Any
64 _iopub_channel = Any
65 _stdin_channel = Any
65 _stdin_channel = Any
66 _hb_channel = Any
66 _hb_channel = Any
67
67
68 # flag for whether execute requests should be allowed to call raw_input:
68 # flag for whether execute requests should be allowed to call raw_input:
69 allow_stdin = True
69 allow_stdin = True
70
70
71 #--------------------------------------------------------------------------
71 #--------------------------------------------------------------------------
72 # Channel proxy methods
72 # Channel proxy methods
73 #--------------------------------------------------------------------------
73 #--------------------------------------------------------------------------
74
74
75 def _get_msg(channel, *args, **kwargs):
75 def _get_msg(channel, *args, **kwargs):
76 return channel.get_msg(*args, **kwargs)
76 return channel.get_msg(*args, **kwargs)
77
77
78 def get_shell_msg(self, *args, **kwargs):
78 def get_shell_msg(self, *args, **kwargs):
79 """Get a message from the shell channel"""
79 """Get a message from the shell channel"""
80 return self.shell_channel.get_msg(*args, **kwargs)
80 return self.shell_channel.get_msg(*args, **kwargs)
81
81
82 def get_iopub_msg(self, *args, **kwargs):
82 def get_iopub_msg(self, *args, **kwargs):
83 """Get a message from the iopub channel"""
83 """Get a message from the iopub channel"""
84 return self.iopub_channel.get_msg(*args, **kwargs)
84 return self.iopub_channel.get_msg(*args, **kwargs)
85
85
86 def get_stdin_msg(self, *args, **kwargs):
86 def get_stdin_msg(self, *args, **kwargs):
87 """Get a message from the stdin channel"""
87 """Get a message from the stdin channel"""
88 return self.stdin_channel.get_msg(*args, **kwargs)
88 return self.stdin_channel.get_msg(*args, **kwargs)
89
89
90 #--------------------------------------------------------------------------
90 #--------------------------------------------------------------------------
91 # Channel management methods
91 # Channel management methods
92 #--------------------------------------------------------------------------
92 #--------------------------------------------------------------------------
93
93
94 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
94 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
95 """Starts the channels for this kernel.
95 """Starts the channels for this kernel.
96
96
97 This will create the channels if they do not exist and then start
97 This will create the channels if they do not exist and then start
98 them (their activity runs in a thread). If port numbers of 0 are
98 them (their activity runs in a thread). If port numbers of 0 are
99 being used (random ports) then you must first call
99 being used (random ports) then you must first call
100 :meth:`start_kernel`. If the channels have been stopped and you
100 :meth:`start_kernel`. If the channels have been stopped and you
101 call this, :class:`RuntimeError` will be raised.
101 call this, :class:`RuntimeError` will be raised.
102 """
102 """
103 if shell:
103 if shell:
104 self.shell_channel.start()
104 self.shell_channel.start()
105 self.kernel_info()
105 self.kernel_info()
106 if iopub:
106 if iopub:
107 self.iopub_channel.start()
107 self.iopub_channel.start()
108 if stdin:
108 if stdin:
109 self.stdin_channel.start()
109 self.stdin_channel.start()
110 self.allow_stdin = True
110 self.allow_stdin = True
111 else:
111 else:
112 self.allow_stdin = False
112 self.allow_stdin = False
113 if hb:
113 if hb:
114 self.hb_channel.start()
114 self.hb_channel.start()
115
115
116 def stop_channels(self):
116 def stop_channels(self):
117 """Stops all the running channels for this kernel.
117 """Stops all the running channels for this kernel.
118
118
119 This stops their event loops and joins their threads.
119 This stops their event loops and joins their threads.
120 """
120 """
121 if self.shell_channel.is_alive():
121 if self.shell_channel.is_alive():
122 self.shell_channel.stop()
122 self.shell_channel.stop()
123 if self.iopub_channel.is_alive():
123 if self.iopub_channel.is_alive():
124 self.iopub_channel.stop()
124 self.iopub_channel.stop()
125 if self.stdin_channel.is_alive():
125 if self.stdin_channel.is_alive():
126 self.stdin_channel.stop()
126 self.stdin_channel.stop()
127 if self.hb_channel.is_alive():
127 if self.hb_channel.is_alive():
128 self.hb_channel.stop()
128 self.hb_channel.stop()
129
129
130 @property
130 @property
131 def channels_running(self):
131 def channels_running(self):
132 """Are any of the channels created and running?"""
132 """Are any of the channels created and running?"""
133 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
133 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
134 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
134 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
135
135
136 ioloop = None # Overridden in subclasses that use pyzmq event loop
136 ioloop = None # Overridden in subclasses that use pyzmq event loop
137
137
138 @property
138 @property
139 def shell_channel(self):
139 def shell_channel(self):
140 """Get the shell channel object for this kernel."""
140 """Get the shell channel object for this kernel."""
141 if self._shell_channel is None:
141 if self._shell_channel is None:
142 url = self._make_url('shell')
142 url = self._make_url('shell')
143 self.log.debug("connecting shell channel to %s", url)
143 self.log.debug("connecting shell channel to %s", url)
144 socket = self.connect_shell(identity=self.session.bsession)
144 socket = self.connect_shell(identity=self.session.bsession)
145 self._shell_channel = self.shell_channel_class(
145 self._shell_channel = self.shell_channel_class(
146 socket, self.session, self.ioloop
146 socket, self.session, self.ioloop
147 )
147 )
148 return self._shell_channel
148 return self._shell_channel
149
149
150 @property
150 @property
151 def iopub_channel(self):
151 def iopub_channel(self):
152 """Get the iopub channel object for this kernel."""
152 """Get the iopub channel object for this kernel."""
153 if self._iopub_channel is None:
153 if self._iopub_channel is None:
154 url = self._make_url('iopub')
154 url = self._make_url('iopub')
155 self.log.debug("connecting iopub channel to %s", url)
155 self.log.debug("connecting iopub channel to %s", url)
156 socket = self.connect_iopub()
156 socket = self.connect_iopub()
157 self._iopub_channel = self.iopub_channel_class(
157 self._iopub_channel = self.iopub_channel_class(
158 socket, self.session, self.ioloop
158 socket, self.session, self.ioloop
159 )
159 )
160 return self._iopub_channel
160 return self._iopub_channel
161
161
162 @property
162 @property
163 def stdin_channel(self):
163 def stdin_channel(self):
164 """Get the stdin channel object for this kernel."""
164 """Get the stdin channel object for this kernel."""
165 if self._stdin_channel is None:
165 if self._stdin_channel is None:
166 url = self._make_url('stdin')
166 url = self._make_url('stdin')
167 self.log.debug("connecting stdin channel to %s", url)
167 self.log.debug("connecting stdin channel to %s", url)
168 socket = self.connect_stdin(identity=self.session.bsession)
168 socket = self.connect_stdin(identity=self.session.bsession)
169 self._stdin_channel = self.stdin_channel_class(
169 self._stdin_channel = self.stdin_channel_class(
170 socket, self.session, self.ioloop
170 socket, self.session, self.ioloop
171 )
171 )
172 return self._stdin_channel
172 return self._stdin_channel
173
173
174 @property
174 @property
175 def hb_channel(self):
175 def hb_channel(self):
176 """Get the hb channel object for this kernel."""
176 """Get the hb channel object for this kernel."""
177 if self._hb_channel is None:
177 if self._hb_channel is None:
178 url = self._make_url('hb')
178 url = self._make_url('hb')
179 self.log.debug("connecting heartbeat channel to %s", url)
179 self.log.debug("connecting heartbeat channel to %s", url)
180 self._hb_channel = self.hb_channel_class(
180 self._hb_channel = self.hb_channel_class(
181 self.context, self.session, url
181 self.context, self.session, url
182 )
182 )
183 return self._hb_channel
183 return self._hb_channel
184
184
185 def is_alive(self):
185 def is_alive(self):
186 """Is the kernel process still running?"""
186 """Is the kernel process still running?"""
187 if self._hb_channel is not None:
187 if self._hb_channel is not None:
188 # We didn't start the kernel with this KernelManager so we
188 # We didn't start the kernel with this KernelManager so we
189 # use the heartbeat.
189 # use the heartbeat.
190 return self._hb_channel.is_beating()
190 return self._hb_channel.is_beating()
191 else:
191 else:
192 # no heartbeat and not local, we can't tell if it's running,
192 # no heartbeat and not local, we can't tell if it's running,
193 # so naively return True
193 # so naively return True
194 return True
194 return True
195
195
196
196
197 # Methods to send specific messages on channels
197 # Methods to send specific messages on channels
198 def execute(self, code, silent=False, store_history=True,
198 def execute(self, code, silent=False, store_history=True,
199 user_expressions=None, allow_stdin=None):
199 user_expressions=None, allow_stdin=None, skip_exceptions=False):
200 """Execute code in the kernel.
200 """Execute code in the kernel.
201
201
202 Parameters
202 Parameters
203 ----------
203 ----------
204 code : str
204 code : str
205 A string of Python code.
205 A string of Python code.
206
206
207 silent : bool, optional (default False)
207 silent : bool, optional (default False)
208 If set, the kernel will execute the code as quietly possible, and
208 If set, the kernel will execute the code as quietly possible, and
209 will force store_history to be False.
209 will force store_history to be False.
210
210
211 store_history : bool, optional (default True)
211 store_history : bool, optional (default True)
212 If set, the kernel will store command history. This is forced
212 If set, the kernel will store command history. This is forced
213 to be False if silent is True.
213 to be False if silent is True.
214
214
215 user_expressions : dict, optional
215 user_expressions : dict, optional
216 A dict mapping names to expressions to be evaluated in the user's
216 A dict mapping names to expressions to be evaluated in the user's
217 dict. The expression values are returned as strings formatted using
217 dict. The expression values are returned as strings formatted using
218 :func:`repr`.
218 :func:`repr`.
219
219
220 allow_stdin : bool, optional (default self.allow_stdin)
220 allow_stdin : bool, optional (default self.allow_stdin)
221 Flag for whether the kernel can send stdin requests to frontends.
221 Flag for whether the kernel can send stdin requests to frontends.
222
222
223 Some frontends (e.g. the Notebook) do not support stdin requests.
223 Some frontends (e.g. the Notebook) do not support stdin requests.
224 If raw_input is called from code executed from such a frontend, a
224 If raw_input is called from code executed from such a frontend, a
225 StdinNotImplementedError will be raised.
225 StdinNotImplementedError will be raised.
226
226
227 skip_exceptions: bool, optional (default False)
228 Flag whether to abort the execution queue, if an exception is encountered.
229
227 Returns
230 Returns
228 -------
231 -------
229 The msg_id of the message sent.
232 The msg_id of the message sent.
230 """
233 """
231 if user_expressions is None:
234 if user_expressions is None:
232 user_expressions = {}
235 user_expressions = {}
233 if allow_stdin is None:
236 if allow_stdin is None:
234 allow_stdin = self.allow_stdin
237 allow_stdin = self.allow_stdin
235
238
236
239
237 # Don't waste network traffic if inputs are invalid
240 # Don't waste network traffic if inputs are invalid
238 if not isinstance(code, string_types):
241 if not isinstance(code, string_types):
239 raise ValueError('code %r must be a string' % code)
242 raise ValueError('code %r must be a string' % code)
240 validate_string_dict(user_expressions)
243 validate_string_dict(user_expressions)
241
244
242 # Create class for content/msg creation. Related to, but possibly
245 # Create class for content/msg creation. Related to, but possibly
243 # not in Session.
246 # not in Session.
244 content = dict(code=code, silent=silent, store_history=store_history,
247 content = dict(code=code, silent=silent, store_history=store_history,
245 user_expressions=user_expressions,
248 user_expressions=user_expressions,
246 allow_stdin=allow_stdin,
249 allow_stdin=allow_stdin, skip_exceptions=skip_exceptions
247 )
250 )
248 msg = self.session.msg('execute_request', content)
251 msg = self.session.msg('execute_request', content)
249 self.shell_channel.send(msg)
252 self.shell_channel.send(msg)
250 return msg['header']['msg_id']
253 return msg['header']['msg_id']
251
254
252 def complete(self, code, cursor_pos=None):
255 def complete(self, code, cursor_pos=None):
253 """Tab complete text in the kernel's namespace.
256 """Tab complete text in the kernel's namespace.
254
257
255 Parameters
258 Parameters
256 ----------
259 ----------
257 code : str
260 code : str
258 The context in which completion is requested.
261 The context in which completion is requested.
259 Can be anything between a variable name and an entire cell.
262 Can be anything between a variable name and an entire cell.
260 cursor_pos : int, optional
263 cursor_pos : int, optional
261 The position of the cursor in the block of code where the completion was requested.
264 The position of the cursor in the block of code where the completion was requested.
262 Default: ``len(code)``
265 Default: ``len(code)``
263
266
264 Returns
267 Returns
265 -------
268 -------
266 The msg_id of the message sent.
269 The msg_id of the message sent.
267 """
270 """
268 if cursor_pos is None:
271 if cursor_pos is None:
269 cursor_pos = len(code)
272 cursor_pos = len(code)
270 content = dict(code=code, cursor_pos=cursor_pos)
273 content = dict(code=code, cursor_pos=cursor_pos)
271 msg = self.session.msg('complete_request', content)
274 msg = self.session.msg('complete_request', content)
272 self.shell_channel.send(msg)
275 self.shell_channel.send(msg)
273 return msg['header']['msg_id']
276 return msg['header']['msg_id']
274
277
275 def inspect(self, code, cursor_pos=None, detail_level=0):
278 def inspect(self, code, cursor_pos=None, detail_level=0):
276 """Get metadata information about an object in the kernel's namespace.
279 """Get metadata information about an object in the kernel's namespace.
277
280
278 It is up to the kernel to determine the appropriate object to inspect.
281 It is up to the kernel to determine the appropriate object to inspect.
279
282
280 Parameters
283 Parameters
281 ----------
284 ----------
282 code : str
285 code : str
283 The context in which info is requested.
286 The context in which info is requested.
284 Can be anything between a variable name and an entire cell.
287 Can be anything between a variable name and an entire cell.
285 cursor_pos : int, optional
288 cursor_pos : int, optional
286 The position of the cursor in the block of code where the info was requested.
289 The position of the cursor in the block of code where the info was requested.
287 Default: ``len(code)``
290 Default: ``len(code)``
288 detail_level : int, optional
291 detail_level : int, optional
289 The level of detail for the introspection (0-2)
292 The level of detail for the introspection (0-2)
290
293
291 Returns
294 Returns
292 -------
295 -------
293 The msg_id of the message sent.
296 The msg_id of the message sent.
294 """
297 """
295 if cursor_pos is None:
298 if cursor_pos is None:
296 cursor_pos = len(code)
299 cursor_pos = len(code)
297 content = dict(code=code, cursor_pos=cursor_pos,
300 content = dict(code=code, cursor_pos=cursor_pos,
298 detail_level=detail_level,
301 detail_level=detail_level,
299 )
302 )
300 msg = self.session.msg('inspect_request', content)
303 msg = self.session.msg('inspect_request', content)
301 self.shell_channel.send(msg)
304 self.shell_channel.send(msg)
302 return msg['header']['msg_id']
305 return msg['header']['msg_id']
303
306
304 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
307 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
305 """Get entries from the kernel's history list.
308 """Get entries from the kernel's history list.
306
309
307 Parameters
310 Parameters
308 ----------
311 ----------
309 raw : bool
312 raw : bool
310 If True, return the raw input.
313 If True, return the raw input.
311 output : bool
314 output : bool
312 If True, then return the output as well.
315 If True, then return the output as well.
313 hist_access_type : str
316 hist_access_type : str
314 'range' (fill in session, start and stop params), 'tail' (fill in n)
317 'range' (fill in session, start and stop params), 'tail' (fill in n)
315 or 'search' (fill in pattern param).
318 or 'search' (fill in pattern param).
316
319
317 session : int
320 session : int
318 For a range request, the session from which to get lines. Session
321 For a range request, the session from which to get lines. Session
319 numbers are positive integers; negative ones count back from the
322 numbers are positive integers; negative ones count back from the
320 current session.
323 current session.
321 start : int
324 start : int
322 The first line number of a history range.
325 The first line number of a history range.
323 stop : int
326 stop : int
324 The final (excluded) line number of a history range.
327 The final (excluded) line number of a history range.
325
328
326 n : int
329 n : int
327 The number of lines of history to get for a tail request.
330 The number of lines of history to get for a tail request.
328
331
329 pattern : str
332 pattern : str
330 The glob-syntax pattern for a search request.
333 The glob-syntax pattern for a search request.
331
334
332 Returns
335 Returns
333 -------
336 -------
334 The msg_id of the message sent.
337 The msg_id of the message sent.
335 """
338 """
336 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
339 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
337 **kwargs)
340 **kwargs)
338 msg = self.session.msg('history_request', content)
341 msg = self.session.msg('history_request', content)
339 self.shell_channel.send(msg)
342 self.shell_channel.send(msg)
340 return msg['header']['msg_id']
343 return msg['header']['msg_id']
341
344
342 def kernel_info(self):
345 def kernel_info(self):
343 """Request kernel info."""
346 """Request kernel info."""
344 msg = self.session.msg('kernel_info_request')
347 msg = self.session.msg('kernel_info_request')
345 self.shell_channel.send(msg)
348 self.shell_channel.send(msg)
346 return msg['header']['msg_id']
349 return msg['header']['msg_id']
347
350
348 def _handle_kernel_info_reply(self, msg):
351 def _handle_kernel_info_reply(self, msg):
349 """handle kernel info reply
352 """handle kernel info reply
350
353
351 sets protocol adaptation version. This might
354 sets protocol adaptation version. This might
352 be run from a separate thread.
355 be run from a separate thread.
353 """
356 """
354 adapt_version = int(msg['content']['protocol_version'].split('.')[0])
357 adapt_version = int(msg['content']['protocol_version'].split('.')[0])
355 if adapt_version != major_protocol_version:
358 if adapt_version != major_protocol_version:
356 self.session.adapt_version = adapt_version
359 self.session.adapt_version = adapt_version
357
360
358 def shutdown(self, restart=False):
361 def shutdown(self, restart=False):
359 """Request an immediate kernel shutdown.
362 """Request an immediate kernel shutdown.
360
363
361 Upon receipt of the (empty) reply, client code can safely assume that
364 Upon receipt of the (empty) reply, client code can safely assume that
362 the kernel has shut down and it's safe to forcefully terminate it if
365 the kernel has shut down and it's safe to forcefully terminate it if
363 it's still alive.
366 it's still alive.
364
367
365 The kernel will send the reply via a function registered with Python's
368 The kernel will send the reply via a function registered with Python's
366 atexit module, ensuring it's truly done as the kernel is done with all
369 atexit module, ensuring it's truly done as the kernel is done with all
367 normal operation.
370 normal operation.
368 """
371 """
369 # Send quit message to kernel. Once we implement kernel-side setattr,
372 # Send quit message to kernel. Once we implement kernel-side setattr,
370 # this should probably be done that way, but for now this will do.
373 # this should probably be done that way, but for now this will do.
371 msg = self.session.msg('shutdown_request', {'restart':restart})
374 msg = self.session.msg('shutdown_request', {'restart':restart})
372 self.shell_channel.send(msg)
375 self.shell_channel.send(msg)
373 return msg['header']['msg_id']
376 return msg['header']['msg_id']
374
377
375 def is_complete(self, code):
378 def is_complete(self, code):
376 msg = self.session.msg('is_complete_request', {'code': code})
379 msg = self.session.msg('is_complete_request', {'code': code})
377 self.shell_channel.send(msg)
380 self.shell_channel.send(msg)
378 return msg['header']['msg_id']
381 return msg['header']['msg_id']
379
382
380 def input(self, string):
383 def input(self, string):
381 """Send a string of raw input to the kernel."""
384 """Send a string of raw input to the kernel."""
382 content = dict(value=string)
385 content = dict(value=string)
383 msg = self.session.msg('input_reply', content)
386 msg = self.session.msg('input_reply', content)
384 self.stdin_channel.send(msg)
387 self.stdin_channel.send(msg)
385
388
386
389
387 KernelClientABC.register(KernelClient)
390 KernelClientABC.register(KernelClient)
@@ -1,472 +1,490 b''
1 """Test suite for our zeromq-based message specification."""
1 """Test suite for our zeromq-based message specification."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import re
6 import re
7 import sys
7 import sys
8 from distutils.version import LooseVersion as V
8 from distutils.version import LooseVersion as V
9 try:
9 try:
10 from queue import Empty # Py 3
10 from queue import Empty # Py 3
11 except ImportError:
11 except ImportError:
12 from Queue import Empty # Py 2
12 from Queue import Empty # Py 2
13
13
14 import nose.tools as nt
14 import nose.tools as nt
15
15
16 from IPython.utils.traitlets import (
16 from IPython.utils.traitlets import (
17 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum,
17 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum,
18 )
18 )
19 from IPython.utils.py3compat import string_types, iteritems
19 from IPython.utils.py3compat import string_types, iteritems
20
20
21 from .utils import TIMEOUT, start_global_kernel, flush_channels, execute
21 from .utils import TIMEOUT, start_global_kernel, flush_channels, execute
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Globals
24 # Globals
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 KC = None
26 KC = None
27
27
28 def setup():
28 def setup():
29 global KC
29 global KC
30 KC = start_global_kernel()
30 KC = start_global_kernel()
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Message Spec References
33 # Message Spec References
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 class Reference(HasTraits):
36 class Reference(HasTraits):
37
37
38 """
38 """
39 Base class for message spec specification testing.
39 Base class for message spec specification testing.
40
40
41 This class is the core of the message specification test. The
41 This class is the core of the message specification test. The
42 idea is that child classes implement trait attributes for each
42 idea is that child classes implement trait attributes for each
43 message keys, so that message keys can be tested against these
43 message keys, so that message keys can be tested against these
44 traits using :meth:`check` method.
44 traits using :meth:`check` method.
45
45
46 """
46 """
47
47
48 def check(self, d):
48 def check(self, d):
49 """validate a dict against our traits"""
49 """validate a dict against our traits"""
50 for key in self.trait_names():
50 for key in self.trait_names():
51 nt.assert_in(key, d)
51 nt.assert_in(key, d)
52 # FIXME: always allow None, probably not a good idea
52 # FIXME: always allow None, probably not a good idea
53 if d[key] is None:
53 if d[key] is None:
54 continue
54 continue
55 try:
55 try:
56 setattr(self, key, d[key])
56 setattr(self, key, d[key])
57 except TraitError as e:
57 except TraitError as e:
58 assert False, str(e)
58 assert False, str(e)
59
59
60
60
61 class Version(Unicode):
61 class Version(Unicode):
62 def __init__(self, *args, **kwargs):
62 def __init__(self, *args, **kwargs):
63 self.min = kwargs.pop('min', None)
63 self.min = kwargs.pop('min', None)
64 self.max = kwargs.pop('max', None)
64 self.max = kwargs.pop('max', None)
65 kwargs['default_value'] = self.min
65 kwargs['default_value'] = self.min
66 super(Version, self).__init__(*args, **kwargs)
66 super(Version, self).__init__(*args, **kwargs)
67
67
68 def validate(self, obj, value):
68 def validate(self, obj, value):
69 if self.min and V(value) < V(self.min):
69 if self.min and V(value) < V(self.min):
70 raise TraitError("bad version: %s < %s" % (value, self.min))
70 raise TraitError("bad version: %s < %s" % (value, self.min))
71 if self.max and (V(value) > V(self.max)):
71 if self.max and (V(value) > V(self.max)):
72 raise TraitError("bad version: %s > %s" % (value, self.max))
72 raise TraitError("bad version: %s > %s" % (value, self.max))
73
73
74
74
75 class RMessage(Reference):
75 class RMessage(Reference):
76 msg_id = Unicode()
76 msg_id = Unicode()
77 msg_type = Unicode()
77 msg_type = Unicode()
78 header = Dict()
78 header = Dict()
79 parent_header = Dict()
79 parent_header = Dict()
80 content = Dict()
80 content = Dict()
81
81
82 def check(self, d):
82 def check(self, d):
83 super(RMessage, self).check(d)
83 super(RMessage, self).check(d)
84 RHeader().check(self.header)
84 RHeader().check(self.header)
85 if self.parent_header:
85 if self.parent_header:
86 RHeader().check(self.parent_header)
86 RHeader().check(self.parent_header)
87
87
88 class RHeader(Reference):
88 class RHeader(Reference):
89 msg_id = Unicode()
89 msg_id = Unicode()
90 msg_type = Unicode()
90 msg_type = Unicode()
91 session = Unicode()
91 session = Unicode()
92 username = Unicode()
92 username = Unicode()
93 version = Version(min='5.0')
93 version = Version(min='5.0')
94
94
95 mime_pat = re.compile(r'^[\w\-\+\.]+/[\w\-\+\.]+$')
95 mime_pat = re.compile(r'^[\w\-\+\.]+/[\w\-\+\.]+$')
96
96
97 class MimeBundle(Reference):
97 class MimeBundle(Reference):
98 metadata = Dict()
98 metadata = Dict()
99 data = Dict()
99 data = Dict()
100 def _data_changed(self, name, old, new):
100 def _data_changed(self, name, old, new):
101 for k,v in iteritems(new):
101 for k,v in iteritems(new):
102 assert mime_pat.match(k)
102 assert mime_pat.match(k)
103 nt.assert_is_instance(v, string_types)
103 nt.assert_is_instance(v, string_types)
104
104
105 # shell replies
105 # shell replies
106
106
107 class ExecuteReply(Reference):
107 class ExecuteReply(Reference):
108 execution_count = Integer()
108 execution_count = Integer()
109 status = Enum((u'ok', u'error'))
109 status = Enum((u'ok', u'error'))
110
110
111 def check(self, d):
111 def check(self, d):
112 Reference.check(self, d)
112 Reference.check(self, d)
113 if d['status'] == 'ok':
113 if d['status'] == 'ok':
114 ExecuteReplyOkay().check(d)
114 ExecuteReplyOkay().check(d)
115 elif d['status'] == 'error':
115 elif d['status'] == 'error':
116 ExecuteReplyError().check(d)
116 ExecuteReplyError().check(d)
117
117
118
118
119 class ExecuteReplyOkay(Reference):
119 class ExecuteReplyOkay(Reference):
120 payload = List(Dict)
120 payload = List(Dict)
121 user_expressions = Dict()
121 user_expressions = Dict()
122
122
123
123
124 class ExecuteReplyError(Reference):
124 class ExecuteReplyError(Reference):
125 ename = Unicode()
125 ename = Unicode()
126 evalue = Unicode()
126 evalue = Unicode()
127 traceback = List(Unicode)
127 traceback = List(Unicode)
128
128
129
129
130 class InspectReply(MimeBundle):
130 class InspectReply(MimeBundle):
131 found = Bool()
131 found = Bool()
132
132
133
133
134 class ArgSpec(Reference):
134 class ArgSpec(Reference):
135 args = List(Unicode)
135 args = List(Unicode)
136 varargs = Unicode()
136 varargs = Unicode()
137 varkw = Unicode()
137 varkw = Unicode()
138 defaults = List()
138 defaults = List()
139
139
140
140
141 class Status(Reference):
141 class Status(Reference):
142 execution_state = Enum((u'busy', u'idle', u'starting'))
142 execution_state = Enum((u'busy', u'idle', u'starting'))
143
143
144
144
145 class CompleteReply(Reference):
145 class CompleteReply(Reference):
146 matches = List(Unicode)
146 matches = List(Unicode)
147 cursor_start = Integer()
147 cursor_start = Integer()
148 cursor_end = Integer()
148 cursor_end = Integer()
149 status = Unicode()
149 status = Unicode()
150
150
151 class LanguageInfo(Reference):
151 class LanguageInfo(Reference):
152 name = Unicode('python')
152 name = Unicode('python')
153 version = Unicode(sys.version.split()[0])
153 version = Unicode(sys.version.split()[0])
154
154
155 class KernelInfoReply(Reference):
155 class KernelInfoReply(Reference):
156 protocol_version = Version(min='5.0')
156 protocol_version = Version(min='5.0')
157 implementation = Unicode('ipython')
157 implementation = Unicode('ipython')
158 implementation_version = Version(min='2.1')
158 implementation_version = Version(min='2.1')
159 language_info = Dict()
159 language_info = Dict()
160 banner = Unicode()
160 banner = Unicode()
161
161
162 def check(self, d):
162 def check(self, d):
163 Reference.check(self, d)
163 Reference.check(self, d)
164 LanguageInfo().check(d['language_info'])
164 LanguageInfo().check(d['language_info'])
165
165
166
166
167 class IsCompleteReply(Reference):
167 class IsCompleteReply(Reference):
168 status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'))
168 status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'))
169
169
170 def check(self, d):
170 def check(self, d):
171 Reference.check(self, d)
171 Reference.check(self, d)
172 if d['status'] == 'incomplete':
172 if d['status'] == 'incomplete':
173 IsCompleteReplyIncomplete().check(d)
173 IsCompleteReplyIncomplete().check(d)
174
174
175 class IsCompleteReplyIncomplete(Reference):
175 class IsCompleteReplyIncomplete(Reference):
176 indent = Unicode()
176 indent = Unicode()
177
177
178
178
179 # IOPub messages
179 # IOPub messages
180
180
181 class ExecuteInput(Reference):
181 class ExecuteInput(Reference):
182 code = Unicode()
182 code = Unicode()
183 execution_count = Integer()
183 execution_count = Integer()
184
184
185
185
186 Error = ExecuteReplyError
186 Error = ExecuteReplyError
187
187
188
188
189 class Stream(Reference):
189 class Stream(Reference):
190 name = Enum((u'stdout', u'stderr'))
190 name = Enum((u'stdout', u'stderr'))
191 text = Unicode()
191 text = Unicode()
192
192
193
193
194 class DisplayData(MimeBundle):
194 class DisplayData(MimeBundle):
195 pass
195 pass
196
196
197
197
198 class ExecuteResult(MimeBundle):
198 class ExecuteResult(MimeBundle):
199 execution_count = Integer()
199 execution_count = Integer()
200
200
201 class HistoryReply(Reference):
201 class HistoryReply(Reference):
202 history = List(List())
202 history = List(List())
203
203
204
204
205 references = {
205 references = {
206 'execute_reply' : ExecuteReply(),
206 'execute_reply' : ExecuteReply(),
207 'inspect_reply' : InspectReply(),
207 'inspect_reply' : InspectReply(),
208 'status' : Status(),
208 'status' : Status(),
209 'complete_reply' : CompleteReply(),
209 'complete_reply' : CompleteReply(),
210 'kernel_info_reply': KernelInfoReply(),
210 'kernel_info_reply': KernelInfoReply(),
211 'is_complete_reply': IsCompleteReply(),
211 'is_complete_reply': IsCompleteReply(),
212 'execute_input' : ExecuteInput(),
212 'execute_input' : ExecuteInput(),
213 'execute_result' : ExecuteResult(),
213 'execute_result' : ExecuteResult(),
214 'history_reply' : HistoryReply(),
214 'history_reply' : HistoryReply(),
215 'error' : Error(),
215 'error' : Error(),
216 'stream' : Stream(),
216 'stream' : Stream(),
217 'display_data' : DisplayData(),
217 'display_data' : DisplayData(),
218 'header' : RHeader(),
218 'header' : RHeader(),
219 }
219 }
220 """
220 """
221 Specifications of `content` part of the reply messages.
221 Specifications of `content` part of the reply messages.
222 """
222 """
223
223
224
224
225 def validate_message(msg, msg_type=None, parent=None):
225 def validate_message(msg, msg_type=None, parent=None):
226 """validate a message
226 """validate a message
227
227
228 This is a generator, and must be iterated through to actually
228 This is a generator, and must be iterated through to actually
229 trigger each test.
229 trigger each test.
230
230
231 If msg_type and/or parent are given, the msg_type and/or parent msg_id
231 If msg_type and/or parent are given, the msg_type and/or parent msg_id
232 are compared with the given values.
232 are compared with the given values.
233 """
233 """
234 RMessage().check(msg)
234 RMessage().check(msg)
235 if msg_type:
235 if msg_type:
236 nt.assert_equal(msg['msg_type'], msg_type)
236 nt.assert_equal(msg['msg_type'], msg_type)
237 if parent:
237 if parent:
238 nt.assert_equal(msg['parent_header']['msg_id'], parent)
238 nt.assert_equal(msg['parent_header']['msg_id'], parent)
239 content = msg['content']
239 content = msg['content']
240 ref = references[msg['msg_type']]
240 ref = references[msg['msg_type']]
241 ref.check(content)
241 ref.check(content)
242
242
243
243
244 #-----------------------------------------------------------------------------
244 #-----------------------------------------------------------------------------
245 # Tests
245 # Tests
246 #-----------------------------------------------------------------------------
246 #-----------------------------------------------------------------------------
247
247
248 # Shell channel
248 # Shell channel
249
249
250 def test_execute():
250 def test_execute():
251 flush_channels()
251 flush_channels()
252
252
253 msg_id = KC.execute(code='x=1')
253 msg_id = KC.execute(code='x=1')
254 reply = KC.get_shell_msg(timeout=TIMEOUT)
254 reply = KC.get_shell_msg(timeout=TIMEOUT)
255 validate_message(reply, 'execute_reply', msg_id)
255 validate_message(reply, 'execute_reply', msg_id)
256
256
257
257
258 def test_execute_silent():
258 def test_execute_silent():
259 flush_channels()
259 flush_channels()
260 msg_id, reply = execute(code='x=1', silent=True)
260 msg_id, reply = execute(code='x=1', silent=True)
261
261
262 # flush status=idle
262 # flush status=idle
263 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
263 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
264 validate_message(status, 'status', msg_id)
264 validate_message(status, 'status', msg_id)
265 nt.assert_equal(status['content']['execution_state'], 'idle')
265 nt.assert_equal(status['content']['execution_state'], 'idle')
266
266
267 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
267 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
268 count = reply['execution_count']
268 count = reply['execution_count']
269
269
270 msg_id, reply = execute(code='x=2', silent=True)
270 msg_id, reply = execute(code='x=2', silent=True)
271
271
272 # flush status=idle
272 # flush status=idle
273 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
273 status = KC.iopub_channel.get_msg(timeout=TIMEOUT)
274 validate_message(status, 'status', msg_id)
274 validate_message(status, 'status', msg_id)
275 nt.assert_equal(status['content']['execution_state'], 'idle')
275 nt.assert_equal(status['content']['execution_state'], 'idle')
276
276
277 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
277 nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1)
278 count_2 = reply['execution_count']
278 count_2 = reply['execution_count']
279 nt.assert_equal(count_2, count)
279 nt.assert_equal(count_2, count)
280
280
281
281
282 def test_execute_error():
282 def test_execute_error():
283 flush_channels()
283 flush_channels()
284
284
285 msg_id, reply = execute(code='1/0')
285 msg_id, reply = execute(code='1/0')
286 nt.assert_equal(reply['status'], 'error')
286 nt.assert_equal(reply['status'], 'error')
287 nt.assert_equal(reply['ename'], 'ZeroDivisionError')
287 nt.assert_equal(reply['ename'], 'ZeroDivisionError')
288
288
289 error = KC.iopub_channel.get_msg(timeout=TIMEOUT)
289 error = KC.iopub_channel.get_msg(timeout=TIMEOUT)
290 validate_message(error, 'error', msg_id)
290 validate_message(error, 'error', msg_id)
291
291
292
292
293 def test_execute_inc():
293 def test_execute_inc():
294 """execute request should increment execution_count"""
294 """execute request should increment execution_count"""
295 flush_channels()
295 flush_channels()
296
296
297 msg_id, reply = execute(code='x=1')
297 msg_id, reply = execute(code='x=1')
298 count = reply['execution_count']
298 count = reply['execution_count']
299
299
300 flush_channels()
300 flush_channels()
301
301
302 msg_id, reply = execute(code='x=2')
302 msg_id, reply = execute(code='x=2')
303 count_2 = reply['execution_count']
303 count_2 = reply['execution_count']
304 nt.assert_equal(count_2, count+1)
304 nt.assert_equal(count_2, count+1)
305
305
306 def test_execute_skip_exceptions():
307 """execute request should not abort execution queue with skip_exceptions"""
308 flush_channels()
309
310 KC.execute(code='raise IOError')
311 msg_id = KC.execute(code='print("Hallo")')
312 KC.get_shell_msg(timeout=TIMEOUT)
313 reply = KC.get_shell_msg(timeout=TIMEOUT)
314 nt.assert_equal(reply['content']['status'], 'aborted')
315
316 flush_channels()
317
318 KC.execute(code='raise IOError', skip_exceptions=True)
319 msg_id = KC.execute(code='print("Hallo")')
320 KC.get_shell_msg(timeout=TIMEOUT)
321 reply = KC.get_shell_msg(timeout=TIMEOUT)
322 nt.assert_equal(reply['content']['status'], 'ok')
323
306
324
307 def test_user_expressions():
325 def test_user_expressions():
308 flush_channels()
326 flush_channels()
309
327
310 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
328 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
311 user_expressions = reply['user_expressions']
329 user_expressions = reply['user_expressions']
312 nt.assert_equal(user_expressions, {u'foo': {
330 nt.assert_equal(user_expressions, {u'foo': {
313 u'status': u'ok',
331 u'status': u'ok',
314 u'data': {u'text/plain': u'2'},
332 u'data': {u'text/plain': u'2'},
315 u'metadata': {},
333 u'metadata': {},
316 }})
334 }})
317
335
318
336
319 def test_user_expressions_fail():
337 def test_user_expressions_fail():
320 flush_channels()
338 flush_channels()
321
339
322 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
340 msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname'))
323 user_expressions = reply['user_expressions']
341 user_expressions = reply['user_expressions']
324 foo = user_expressions['foo']
342 foo = user_expressions['foo']
325 nt.assert_equal(foo['status'], 'error')
343 nt.assert_equal(foo['status'], 'error')
326 nt.assert_equal(foo['ename'], 'NameError')
344 nt.assert_equal(foo['ename'], 'NameError')
327
345
328
346
329 def test_oinfo():
347 def test_oinfo():
330 flush_channels()
348 flush_channels()
331
349
332 msg_id = KC.inspect('a')
350 msg_id = KC.inspect('a')
333 reply = KC.get_shell_msg(timeout=TIMEOUT)
351 reply = KC.get_shell_msg(timeout=TIMEOUT)
334 validate_message(reply, 'inspect_reply', msg_id)
352 validate_message(reply, 'inspect_reply', msg_id)
335
353
336
354
337 def test_oinfo_found():
355 def test_oinfo_found():
338 flush_channels()
356 flush_channels()
339
357
340 msg_id, reply = execute(code='a=5')
358 msg_id, reply = execute(code='a=5')
341
359
342 msg_id = KC.inspect('a')
360 msg_id = KC.inspect('a')
343 reply = KC.get_shell_msg(timeout=TIMEOUT)
361 reply = KC.get_shell_msg(timeout=TIMEOUT)
344 validate_message(reply, 'inspect_reply', msg_id)
362 validate_message(reply, 'inspect_reply', msg_id)
345 content = reply['content']
363 content = reply['content']
346 assert content['found']
364 assert content['found']
347 text = content['data']['text/plain']
365 text = content['data']['text/plain']
348 nt.assert_in('Type:', text)
366 nt.assert_in('Type:', text)
349 nt.assert_in('Docstring:', text)
367 nt.assert_in('Docstring:', text)
350
368
351
369
352 def test_oinfo_detail():
370 def test_oinfo_detail():
353 flush_channels()
371 flush_channels()
354
372
355 msg_id, reply = execute(code='ip=get_ipython()')
373 msg_id, reply = execute(code='ip=get_ipython()')
356
374
357 msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1)
375 msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1)
358 reply = KC.get_shell_msg(timeout=TIMEOUT)
376 reply = KC.get_shell_msg(timeout=TIMEOUT)
359 validate_message(reply, 'inspect_reply', msg_id)
377 validate_message(reply, 'inspect_reply', msg_id)
360 content = reply['content']
378 content = reply['content']
361 assert content['found']
379 assert content['found']
362 text = content['data']['text/plain']
380 text = content['data']['text/plain']
363 nt.assert_in('Definition:', text)
381 nt.assert_in('Definition:', text)
364 nt.assert_in('Source:', text)
382 nt.assert_in('Source:', text)
365
383
366
384
367 def test_oinfo_not_found():
385 def test_oinfo_not_found():
368 flush_channels()
386 flush_channels()
369
387
370 msg_id = KC.inspect('dne')
388 msg_id = KC.inspect('dne')
371 reply = KC.get_shell_msg(timeout=TIMEOUT)
389 reply = KC.get_shell_msg(timeout=TIMEOUT)
372 validate_message(reply, 'inspect_reply', msg_id)
390 validate_message(reply, 'inspect_reply', msg_id)
373 content = reply['content']
391 content = reply['content']
374 nt.assert_false(content['found'])
392 nt.assert_false(content['found'])
375
393
376
394
377 def test_complete():
395 def test_complete():
378 flush_channels()
396 flush_channels()
379
397
380 msg_id, reply = execute(code="alpha = albert = 5")
398 msg_id, reply = execute(code="alpha = albert = 5")
381
399
382 msg_id = KC.complete('al', 2)
400 msg_id = KC.complete('al', 2)
383 reply = KC.get_shell_msg(timeout=TIMEOUT)
401 reply = KC.get_shell_msg(timeout=TIMEOUT)
384 validate_message(reply, 'complete_reply', msg_id)
402 validate_message(reply, 'complete_reply', msg_id)
385 matches = reply['content']['matches']
403 matches = reply['content']['matches']
386 for name in ('alpha', 'albert'):
404 for name in ('alpha', 'albert'):
387 nt.assert_in(name, matches)
405 nt.assert_in(name, matches)
388
406
389
407
390 def test_kernel_info_request():
408 def test_kernel_info_request():
391 flush_channels()
409 flush_channels()
392
410
393 msg_id = KC.kernel_info()
411 msg_id = KC.kernel_info()
394 reply = KC.get_shell_msg(timeout=TIMEOUT)
412 reply = KC.get_shell_msg(timeout=TIMEOUT)
395 validate_message(reply, 'kernel_info_reply', msg_id)
413 validate_message(reply, 'kernel_info_reply', msg_id)
396
414
397
415
398 def test_single_payload():
416 def test_single_payload():
399 flush_channels()
417 flush_channels()
400 msg_id, reply = execute(code="for i in range(3):\n"+
418 msg_id, reply = execute(code="for i in range(3):\n"+
401 " x=range?\n")
419 " x=range?\n")
402 payload = reply['payload']
420 payload = reply['payload']
403 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
421 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
404 nt.assert_equal(len(next_input_pls), 1)
422 nt.assert_equal(len(next_input_pls), 1)
405
423
406 def test_is_complete():
424 def test_is_complete():
407 flush_channels()
425 flush_channels()
408
426
409 msg_id = KC.is_complete("a = 1")
427 msg_id = KC.is_complete("a = 1")
410 reply = KC.get_shell_msg(timeout=TIMEOUT)
428 reply = KC.get_shell_msg(timeout=TIMEOUT)
411 validate_message(reply, 'is_complete_reply', msg_id)
429 validate_message(reply, 'is_complete_reply', msg_id)
412
430
413 def test_history_range():
431 def test_history_range():
414 flush_channels()
432 flush_channels()
415
433
416 msg_id_exec = KC.execute(code='x=1', store_history = True)
434 msg_id_exec = KC.execute(code='x=1', store_history = True)
417 reply_exec = KC.get_shell_msg(timeout=TIMEOUT)
435 reply_exec = KC.get_shell_msg(timeout=TIMEOUT)
418
436
419 msg_id = KC.history(hist_access_type = 'range', raw = True, output = True, start = 1, stop = 2, session = 0)
437 msg_id = KC.history(hist_access_type = 'range', raw = True, output = True, start = 1, stop = 2, session = 0)
420 reply = KC.get_shell_msg(timeout=TIMEOUT)
438 reply = KC.get_shell_msg(timeout=TIMEOUT)
421 validate_message(reply, 'history_reply', msg_id)
439 validate_message(reply, 'history_reply', msg_id)
422 content = reply['content']
440 content = reply['content']
423 nt.assert_equal(len(content['history']), 1)
441 nt.assert_equal(len(content['history']), 1)
424
442
425 def test_history_tail():
443 def test_history_tail():
426 flush_channels()
444 flush_channels()
427
445
428 msg_id_exec = KC.execute(code='x=1', store_history = True)
446 msg_id_exec = KC.execute(code='x=1', store_history = True)
429 reply_exec = KC.get_shell_msg(timeout=TIMEOUT)
447 reply_exec = KC.get_shell_msg(timeout=TIMEOUT)
430
448
431 msg_id = KC.history(hist_access_type = 'tail', raw = True, output = True, n = 1, session = 0)
449 msg_id = KC.history(hist_access_type = 'tail', raw = True, output = True, n = 1, session = 0)
432 reply = KC.get_shell_msg(timeout=TIMEOUT)
450 reply = KC.get_shell_msg(timeout=TIMEOUT)
433 validate_message(reply, 'history_reply', msg_id)
451 validate_message(reply, 'history_reply', msg_id)
434 content = reply['content']
452 content = reply['content']
435 nt.assert_equal(len(content['history']), 1)
453 nt.assert_equal(len(content['history']), 1)
436
454
437 def test_history_search():
455 def test_history_search():
438 flush_channels()
456 flush_channels()
439
457
440 msg_id_exec = KC.execute(code='x=1', store_history = True)
458 msg_id_exec = KC.execute(code='x=1', store_history = True)
441 reply_exec = KC.get_shell_msg(timeout=TIMEOUT)
459 reply_exec = KC.get_shell_msg(timeout=TIMEOUT)
442
460
443 msg_id = KC.history(hist_access_type = 'search', raw = True, output = True, n = 1, pattern = '*', session = 0)
461 msg_id = KC.history(hist_access_type = 'search', raw = True, output = True, n = 1, pattern = '*', session = 0)
444 reply = KC.get_shell_msg(timeout=TIMEOUT)
462 reply = KC.get_shell_msg(timeout=TIMEOUT)
445 validate_message(reply, 'history_reply', msg_id)
463 validate_message(reply, 'history_reply', msg_id)
446 content = reply['content']
464 content = reply['content']
447 nt.assert_equal(len(content['history']), 1)
465 nt.assert_equal(len(content['history']), 1)
448
466
449 # IOPub channel
467 # IOPub channel
450
468
451
469
452 def test_stream():
470 def test_stream():
453 flush_channels()
471 flush_channels()
454
472
455 msg_id, reply = execute("print('hi')")
473 msg_id, reply = execute("print('hi')")
456
474
457 stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT)
475 stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT)
458 validate_message(stdout, 'stream', msg_id)
476 validate_message(stdout, 'stream', msg_id)
459 content = stdout['content']
477 content = stdout['content']
460 nt.assert_equal(content['text'], u'hi\n')
478 nt.assert_equal(content['text'], u'hi\n')
461
479
462
480
463 def test_display_data():
481 def test_display_data():
464 flush_channels()
482 flush_channels()
465
483
466 msg_id, reply = execute("from IPython.core.display import display; display(1)")
484 msg_id, reply = execute("from IPython.core.display import display; display(1)")
467
485
468 display = KC.iopub_channel.get_msg(timeout=TIMEOUT)
486 display = KC.iopub_channel.get_msg(timeout=TIMEOUT)
469 validate_message(display, 'display_data', parent=msg_id)
487 validate_message(display, 'display_data', parent=msg_id)
470 data = display['content']['data']
488 data = display['content']['data']
471 nt.assert_equal(data['text/plain'], u'1')
489 nt.assert_equal(data['text/plain'], u'1')
472
490
@@ -1,699 +1,704 b''
1 """Base class for a kernel that talks to frontends over 0MQ."""
1 """Base class for a kernel that talks to frontends over 0MQ."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 import sys
8 import sys
9 import time
9 import time
10 import logging
10 import logging
11 import uuid
11 import uuid
12
12
13 from datetime import datetime
13 from datetime import datetime
14 from signal import (
14 from signal import (
15 signal, default_int_handler, SIGINT
15 signal, default_int_handler, SIGINT
16 )
16 )
17
17
18 import zmq
18 import zmq
19 from zmq.eventloop import ioloop
19 from zmq.eventloop import ioloop
20 from zmq.eventloop.zmqstream import ZMQStream
20 from zmq.eventloop.zmqstream import ZMQStream
21
21
22 from IPython.config.configurable import SingletonConfigurable
22 from IPython.config.configurable import SingletonConfigurable
23 from IPython.core.error import StdinNotImplementedError
23 from IPython.core.error import StdinNotImplementedError
24 from IPython.core import release
24 from IPython.core import release
25 from IPython.utils import py3compat
25 from IPython.utils import py3compat
26 from IPython.utils.py3compat import unicode_type, string_types
26 from IPython.utils.py3compat import unicode_type, string_types
27 from IPython.utils.jsonutil import json_clean
27 from IPython.utils.jsonutil import json_clean
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool,
29 Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool,
30 )
30 )
31
31
32 from .session import Session
32 from .session import Session
33
33
34
34
35 class Kernel(SingletonConfigurable):
35 class Kernel(SingletonConfigurable):
36
36
37 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
38 # Kernel interface
38 # Kernel interface
39 #---------------------------------------------------------------------------
39 #---------------------------------------------------------------------------
40
40
41 # attribute to override with a GUI
41 # attribute to override with a GUI
42 eventloop = Any(None)
42 eventloop = Any(None)
43 def _eventloop_changed(self, name, old, new):
43 def _eventloop_changed(self, name, old, new):
44 """schedule call to eventloop from IOLoop"""
44 """schedule call to eventloop from IOLoop"""
45 loop = ioloop.IOLoop.instance()
45 loop = ioloop.IOLoop.instance()
46 loop.add_callback(self.enter_eventloop)
46 loop.add_callback(self.enter_eventloop)
47
47
48 session = Instance(Session)
48 session = Instance(Session)
49 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
49 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
50 shell_streams = List()
50 shell_streams = List()
51 control_stream = Instance(ZMQStream)
51 control_stream = Instance(ZMQStream)
52 iopub_socket = Instance(zmq.Socket)
52 iopub_socket = Instance(zmq.Socket)
53 stdin_socket = Instance(zmq.Socket)
53 stdin_socket = Instance(zmq.Socket)
54 log = Instance(logging.Logger)
54 log = Instance(logging.Logger)
55
55
56 # identities:
56 # identities:
57 int_id = Integer(-1)
57 int_id = Integer(-1)
58 ident = Unicode()
58 ident = Unicode()
59
59
60 def _ident_default(self):
60 def _ident_default(self):
61 return unicode_type(uuid.uuid4())
61 return unicode_type(uuid.uuid4())
62
62
63 # This should be overridden by wrapper kernels that implement any real
63 # This should be overridden by wrapper kernels that implement any real
64 # language.
64 # language.
65 language_info = {}
65 language_info = {}
66
66
67 # any links that should go in the help menu
67 # any links that should go in the help menu
68 help_links = List()
68 help_links = List()
69
69
70 # Private interface
70 # Private interface
71
71
72 _darwin_app_nap = Bool(True, config=True,
72 _darwin_app_nap = Bool(True, config=True,
73 help="""Whether to use appnope for compatiblity with OS X App Nap.
73 help="""Whether to use appnope for compatiblity with OS X App Nap.
74
74
75 Only affects OS X >= 10.9.
75 Only affects OS X >= 10.9.
76 """
76 """
77 )
77 )
78
78
79 # track associations with current request
79 # track associations with current request
80 _allow_stdin = Bool(False)
80 _allow_stdin = Bool(False)
81 _parent_header = Dict()
81 _parent_header = Dict()
82 _parent_ident = Any(b'')
82 _parent_ident = Any(b'')
83 # Time to sleep after flushing the stdout/err buffers in each execute
83 # Time to sleep after flushing the stdout/err buffers in each execute
84 # cycle. While this introduces a hard limit on the minimal latency of the
84 # cycle. While this introduces a hard limit on the minimal latency of the
85 # execute cycle, it helps prevent output synchronization problems for
85 # execute cycle, it helps prevent output synchronization problems for
86 # clients.
86 # clients.
87 # Units are in seconds. The minimum zmq latency on local host is probably
87 # Units are in seconds. The minimum zmq latency on local host is probably
88 # ~150 microseconds, set this to 500us for now. We may need to increase it
88 # ~150 microseconds, set this to 500us for now. We may need to increase it
89 # a little if it's not enough after more interactive testing.
89 # a little if it's not enough after more interactive testing.
90 _execute_sleep = Float(0.0005, config=True)
90 _execute_sleep = Float(0.0005, config=True)
91
91
92 # Frequency of the kernel's event loop.
92 # Frequency of the kernel's event loop.
93 # Units are in seconds, kernel subclasses for GUI toolkits may need to
93 # Units are in seconds, kernel subclasses for GUI toolkits may need to
94 # adapt to milliseconds.
94 # adapt to milliseconds.
95 _poll_interval = Float(0.05, config=True)
95 _poll_interval = Float(0.05, config=True)
96
96
97 # If the shutdown was requested over the network, we leave here the
97 # If the shutdown was requested over the network, we leave here the
98 # necessary reply message so it can be sent by our registered atexit
98 # necessary reply message so it can be sent by our registered atexit
99 # handler. This ensures that the reply is only sent to clients truly at
99 # handler. This ensures that the reply is only sent to clients truly at
100 # the end of our shutdown process (which happens after the underlying
100 # the end of our shutdown process (which happens after the underlying
101 # IPython shell's own shutdown).
101 # IPython shell's own shutdown).
102 _shutdown_message = None
102 _shutdown_message = None
103
103
104 # This is a dict of port number that the kernel is listening on. It is set
104 # This is a dict of port number that the kernel is listening on. It is set
105 # by record_ports and used by connect_request.
105 # by record_ports and used by connect_request.
106 _recorded_ports = Dict()
106 _recorded_ports = Dict()
107
107
108 # set of aborted msg_ids
108 # set of aborted msg_ids
109 aborted = Set()
109 aborted = Set()
110
110
111 # Track execution count here. For IPython, we override this to use the
111 # Track execution count here. For IPython, we override this to use the
112 # execution count we store in the shell.
112 # execution count we store in the shell.
113 execution_count = 0
113 execution_count = 0
114
114
115
115
116 def __init__(self, **kwargs):
116 def __init__(self, **kwargs):
117 super(Kernel, self).__init__(**kwargs)
117 super(Kernel, self).__init__(**kwargs)
118
118
119 # Build dict of handlers for message types
119 # Build dict of handlers for message types
120 msg_types = [ 'execute_request', 'complete_request',
120 msg_types = [ 'execute_request', 'complete_request',
121 'inspect_request', 'history_request',
121 'inspect_request', 'history_request',
122 'kernel_info_request',
122 'kernel_info_request',
123 'connect_request', 'shutdown_request',
123 'connect_request', 'shutdown_request',
124 'apply_request', 'is_complete_request',
124 'apply_request', 'is_complete_request',
125 ]
125 ]
126 self.shell_handlers = {}
126 self.shell_handlers = {}
127 for msg_type in msg_types:
127 for msg_type in msg_types:
128 self.shell_handlers[msg_type] = getattr(self, msg_type)
128 self.shell_handlers[msg_type] = getattr(self, msg_type)
129
129
130 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
130 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
131 self.control_handlers = {}
131 self.control_handlers = {}
132 for msg_type in control_msg_types:
132 for msg_type in control_msg_types:
133 self.control_handlers[msg_type] = getattr(self, msg_type)
133 self.control_handlers[msg_type] = getattr(self, msg_type)
134
134
135
135
136 def dispatch_control(self, msg):
136 def dispatch_control(self, msg):
137 """dispatch control requests"""
137 """dispatch control requests"""
138 idents,msg = self.session.feed_identities(msg, copy=False)
138 idents,msg = self.session.feed_identities(msg, copy=False)
139 try:
139 try:
140 msg = self.session.deserialize(msg, content=True, copy=False)
140 msg = self.session.deserialize(msg, content=True, copy=False)
141 except:
141 except:
142 self.log.error("Invalid Control Message", exc_info=True)
142 self.log.error("Invalid Control Message", exc_info=True)
143 return
143 return
144
144
145 self.log.debug("Control received: %s", msg)
145 self.log.debug("Control received: %s", msg)
146
146
147 # Set the parent message for side effects.
147 # Set the parent message for side effects.
148 self.set_parent(idents, msg)
148 self.set_parent(idents, msg)
149 self._publish_status(u'busy')
149 self._publish_status(u'busy')
150
150
151 header = msg['header']
151 header = msg['header']
152 msg_type = header['msg_type']
152 msg_type = header['msg_type']
153
153
154 handler = self.control_handlers.get(msg_type, None)
154 handler = self.control_handlers.get(msg_type, None)
155 if handler is None:
155 if handler is None:
156 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
156 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
157 else:
157 else:
158 try:
158 try:
159 handler(self.control_stream, idents, msg)
159 handler(self.control_stream, idents, msg)
160 except Exception:
160 except Exception:
161 self.log.error("Exception in control handler:", exc_info=True)
161 self.log.error("Exception in control handler:", exc_info=True)
162
162
163 sys.stdout.flush()
163 sys.stdout.flush()
164 sys.stderr.flush()
164 sys.stderr.flush()
165 self._publish_status(u'idle')
165 self._publish_status(u'idle')
166
166
167 def dispatch_shell(self, stream, msg):
167 def dispatch_shell(self, stream, msg):
168 """dispatch shell requests"""
168 """dispatch shell requests"""
169 # flush control requests first
169 # flush control requests first
170 if self.control_stream:
170 if self.control_stream:
171 self.control_stream.flush()
171 self.control_stream.flush()
172
172
173 idents,msg = self.session.feed_identities(msg, copy=False)
173 idents,msg = self.session.feed_identities(msg, copy=False)
174 try:
174 try:
175 msg = self.session.deserialize(msg, content=True, copy=False)
175 msg = self.session.deserialize(msg, content=True, copy=False)
176 except:
176 except:
177 self.log.error("Invalid Message", exc_info=True)
177 self.log.error("Invalid Message", exc_info=True)
178 return
178 return
179
179
180 # Set the parent message for side effects.
180 # Set the parent message for side effects.
181 self.set_parent(idents, msg)
181 self.set_parent(idents, msg)
182 self._publish_status(u'busy')
182 self._publish_status(u'busy')
183
183
184 header = msg['header']
184 header = msg['header']
185 msg_id = header['msg_id']
185 msg_id = header['msg_id']
186 msg_type = msg['header']['msg_type']
186 msg_type = msg['header']['msg_type']
187
187
188 # Print some info about this message and leave a '--->' marker, so it's
188 # Print some info about this message and leave a '--->' marker, so it's
189 # easier to trace visually the message chain when debugging. Each
189 # easier to trace visually the message chain when debugging. Each
190 # handler prints its message at the end.
190 # handler prints its message at the end.
191 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
191 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
192 self.log.debug(' Content: %s\n --->\n ', msg['content'])
192 self.log.debug(' Content: %s\n --->\n ', msg['content'])
193
193
194 if msg_id in self.aborted:
194 if msg_id in self.aborted:
195 self.aborted.remove(msg_id)
195 self.aborted.remove(msg_id)
196 # is it safe to assume a msg_id will not be resubmitted?
196 # is it safe to assume a msg_id will not be resubmitted?
197 reply_type = msg_type.split('_')[0] + '_reply'
197 reply_type = msg_type.split('_')[0] + '_reply'
198 status = {'status' : 'aborted'}
198 status = {'status' : 'aborted'}
199 md = {'engine' : self.ident}
199 md = {'engine' : self.ident}
200 md.update(status)
200 md.update(status)
201 self.session.send(stream, reply_type, metadata=md,
201 self.session.send(stream, reply_type, metadata=md,
202 content=status, parent=msg, ident=idents)
202 content=status, parent=msg, ident=idents)
203 return
203 return
204
204
205 handler = self.shell_handlers.get(msg_type, None)
205 handler = self.shell_handlers.get(msg_type, None)
206 if handler is None:
206 if handler is None:
207 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
207 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
208 else:
208 else:
209 # ensure default_int_handler during handler call
209 # ensure default_int_handler during handler call
210 sig = signal(SIGINT, default_int_handler)
210 sig = signal(SIGINT, default_int_handler)
211 self.log.debug("%s: %s", msg_type, msg)
211 self.log.debug("%s: %s", msg_type, msg)
212 try:
212 try:
213 handler(stream, idents, msg)
213 handler(stream, idents, msg)
214 except Exception:
214 except Exception:
215 self.log.error("Exception in message handler:", exc_info=True)
215 self.log.error("Exception in message handler:", exc_info=True)
216 finally:
216 finally:
217 signal(SIGINT, sig)
217 signal(SIGINT, sig)
218
218
219 sys.stdout.flush()
219 sys.stdout.flush()
220 sys.stderr.flush()
220 sys.stderr.flush()
221 self._publish_status(u'idle')
221 self._publish_status(u'idle')
222
222
223 def enter_eventloop(self):
223 def enter_eventloop(self):
224 """enter eventloop"""
224 """enter eventloop"""
225 self.log.info("entering eventloop %s", self.eventloop)
225 self.log.info("entering eventloop %s", self.eventloop)
226 for stream in self.shell_streams:
226 for stream in self.shell_streams:
227 # flush any pending replies,
227 # flush any pending replies,
228 # which may be skipped by entering the eventloop
228 # which may be skipped by entering the eventloop
229 stream.flush(zmq.POLLOUT)
229 stream.flush(zmq.POLLOUT)
230 # restore default_int_handler
230 # restore default_int_handler
231 signal(SIGINT, default_int_handler)
231 signal(SIGINT, default_int_handler)
232 while self.eventloop is not None:
232 while self.eventloop is not None:
233 try:
233 try:
234 self.eventloop(self)
234 self.eventloop(self)
235 except KeyboardInterrupt:
235 except KeyboardInterrupt:
236 # Ctrl-C shouldn't crash the kernel
236 # Ctrl-C shouldn't crash the kernel
237 self.log.error("KeyboardInterrupt caught in kernel")
237 self.log.error("KeyboardInterrupt caught in kernel")
238 continue
238 continue
239 else:
239 else:
240 # eventloop exited cleanly, this means we should stop (right?)
240 # eventloop exited cleanly, this means we should stop (right?)
241 self.eventloop = None
241 self.eventloop = None
242 break
242 break
243 self.log.info("exiting eventloop")
243 self.log.info("exiting eventloop")
244
244
245 def start(self):
245 def start(self):
246 """register dispatchers for streams"""
246 """register dispatchers for streams"""
247 if self.control_stream:
247 if self.control_stream:
248 self.control_stream.on_recv(self.dispatch_control, copy=False)
248 self.control_stream.on_recv(self.dispatch_control, copy=False)
249
249
250 def make_dispatcher(stream):
250 def make_dispatcher(stream):
251 def dispatcher(msg):
251 def dispatcher(msg):
252 return self.dispatch_shell(stream, msg)
252 return self.dispatch_shell(stream, msg)
253 return dispatcher
253 return dispatcher
254
254
255 for s in self.shell_streams:
255 for s in self.shell_streams:
256 s.on_recv(make_dispatcher(s), copy=False)
256 s.on_recv(make_dispatcher(s), copy=False)
257
257
258 # publish idle status
258 # publish idle status
259 self._publish_status('starting')
259 self._publish_status('starting')
260
260
261 def do_one_iteration(self):
261 def do_one_iteration(self):
262 """step eventloop just once"""
262 """step eventloop just once"""
263 if self.control_stream:
263 if self.control_stream:
264 self.control_stream.flush()
264 self.control_stream.flush()
265 for stream in self.shell_streams:
265 for stream in self.shell_streams:
266 # handle at most one request per iteration
266 # handle at most one request per iteration
267 stream.flush(zmq.POLLIN, 1)
267 stream.flush(zmq.POLLIN, 1)
268 stream.flush(zmq.POLLOUT)
268 stream.flush(zmq.POLLOUT)
269
269
270
270
271 def record_ports(self, ports):
271 def record_ports(self, ports):
272 """Record the ports that this kernel is using.
272 """Record the ports that this kernel is using.
273
273
274 The creator of the Kernel instance must call this methods if they
274 The creator of the Kernel instance must call this methods if they
275 want the :meth:`connect_request` method to return the port numbers.
275 want the :meth:`connect_request` method to return the port numbers.
276 """
276 """
277 self._recorded_ports = ports
277 self._recorded_ports = ports
278
278
279 #---------------------------------------------------------------------------
279 #---------------------------------------------------------------------------
280 # Kernel request handlers
280 # Kernel request handlers
281 #---------------------------------------------------------------------------
281 #---------------------------------------------------------------------------
282
282
283 def _make_metadata(self, other=None):
283 def _make_metadata(self, other=None):
284 """init metadata dict, for execute/apply_reply"""
284 """init metadata dict, for execute/apply_reply"""
285 new_md = {
285 new_md = {
286 'dependencies_met' : True,
286 'dependencies_met' : True,
287 'engine' : self.ident,
287 'engine' : self.ident,
288 'started': datetime.now(),
288 'started': datetime.now(),
289 }
289 }
290 if other:
290 if other:
291 new_md.update(other)
291 new_md.update(other)
292 return new_md
292 return new_md
293
293
294 def _publish_execute_input(self, code, parent, execution_count):
294 def _publish_execute_input(self, code, parent, execution_count):
295 """Publish the code request on the iopub stream."""
295 """Publish the code request on the iopub stream."""
296
296
297 self.session.send(self.iopub_socket, u'execute_input',
297 self.session.send(self.iopub_socket, u'execute_input',
298 {u'code':code, u'execution_count': execution_count},
298 {u'code':code, u'execution_count': execution_count},
299 parent=parent, ident=self._topic('execute_input')
299 parent=parent, ident=self._topic('execute_input')
300 )
300 )
301
301
302 def _publish_status(self, status, parent=None):
302 def _publish_status(self, status, parent=None):
303 """send status (busy/idle) on IOPub"""
303 """send status (busy/idle) on IOPub"""
304 self.session.send(self.iopub_socket,
304 self.session.send(self.iopub_socket,
305 u'status',
305 u'status',
306 {u'execution_state': status},
306 {u'execution_state': status},
307 parent=parent or self._parent_header,
307 parent=parent or self._parent_header,
308 ident=self._topic('status'),
308 ident=self._topic('status'),
309 )
309 )
310
310
311 def set_parent(self, ident, parent):
311 def set_parent(self, ident, parent):
312 """Set the current parent_header
312 """Set the current parent_header
313
313
314 Side effects (IOPub messages) and replies are associated with
314 Side effects (IOPub messages) and replies are associated with
315 the request that caused them via the parent_header.
315 the request that caused them via the parent_header.
316
316
317 The parent identity is used to route input_request messages
317 The parent identity is used to route input_request messages
318 on the stdin channel.
318 on the stdin channel.
319 """
319 """
320 self._parent_ident = ident
320 self._parent_ident = ident
321 self._parent_header = parent
321 self._parent_header = parent
322
322
323 def send_response(self, stream, msg_or_type, content=None, ident=None,
323 def send_response(self, stream, msg_or_type, content=None, ident=None,
324 buffers=None, track=False, header=None, metadata=None):
324 buffers=None, track=False, header=None, metadata=None):
325 """Send a response to the message we're currently processing.
325 """Send a response to the message we're currently processing.
326
326
327 This accepts all the parameters of :meth:`IPython.kernel.zmq.session.Session.send`
327 This accepts all the parameters of :meth:`IPython.kernel.zmq.session.Session.send`
328 except ``parent``.
328 except ``parent``.
329
329
330 This relies on :meth:`set_parent` having been called for the current
330 This relies on :meth:`set_parent` having been called for the current
331 message.
331 message.
332 """
332 """
333 return self.session.send(stream, msg_or_type, content, self._parent_header,
333 return self.session.send(stream, msg_or_type, content, self._parent_header,
334 ident, buffers, track, header, metadata)
334 ident, buffers, track, header, metadata)
335
335
336 def execute_request(self, stream, ident, parent):
336 def execute_request(self, stream, ident, parent):
337 """handle an execute_request"""
337 """handle an execute_request"""
338
338
339 try:
339 try:
340 content = parent[u'content']
340 content = parent[u'content']
341 code = py3compat.cast_unicode_py2(content[u'code'])
341 code = py3compat.cast_unicode_py2(content[u'code'])
342 silent = content[u'silent']
342 silent = content[u'silent']
343 store_history = content.get(u'store_history', not silent)
343 store_history = content.get(u'store_history', not silent)
344 user_expressions = content.get('user_expressions', {})
344 user_expressions = content.get('user_expressions', {})
345 allow_stdin = content.get('allow_stdin', False)
345 allow_stdin = content.get('allow_stdin', False)
346 except:
346 except:
347 self.log.error("Got bad msg: ")
347 self.log.error("Got bad msg: ")
348 self.log.error("%s", parent)
348 self.log.error("%s", parent)
349 return
349 return
350
350
351 if u'skip_exceptions' in content and content[u'skip_exceptions'] is True:
352 skip_exceptions = True
353 else:
354 skip_exceptions = False
355
351 md = self._make_metadata(parent['metadata'])
356 md = self._make_metadata(parent['metadata'])
352
357
353 # Re-broadcast our input for the benefit of listening clients, and
358 # Re-broadcast our input for the benefit of listening clients, and
354 # start computing output
359 # start computing output
355 if not silent:
360 if not silent:
356 self.execution_count += 1
361 self.execution_count += 1
357 self._publish_execute_input(code, parent, self.execution_count)
362 self._publish_execute_input(code, parent, self.execution_count)
358
363
359 reply_content = self.do_execute(code, silent, store_history,
364 reply_content = self.do_execute(code, silent, store_history,
360 user_expressions, allow_stdin)
365 user_expressions, allow_stdin)
361
366
362 # Flush output before sending the reply.
367 # Flush output before sending the reply.
363 sys.stdout.flush()
368 sys.stdout.flush()
364 sys.stderr.flush()
369 sys.stderr.flush()
365 # FIXME: on rare occasions, the flush doesn't seem to make it to the
370 # FIXME: on rare occasions, the flush doesn't seem to make it to the
366 # clients... This seems to mitigate the problem, but we definitely need
371 # clients... This seems to mitigate the problem, but we definitely need
367 # to better understand what's going on.
372 # to better understand what's going on.
368 if self._execute_sleep:
373 if self._execute_sleep:
369 time.sleep(self._execute_sleep)
374 time.sleep(self._execute_sleep)
370
375
371 # Send the reply.
376 # Send the reply.
372 reply_content = json_clean(reply_content)
377 reply_content = json_clean(reply_content)
373
378
374 md['status'] = reply_content['status']
379 md['status'] = reply_content['status']
375 if reply_content['status'] == 'error' and \
380 if reply_content['status'] == 'error' and \
376 reply_content['ename'] == 'UnmetDependency':
381 reply_content['ename'] == 'UnmetDependency':
377 md['dependencies_met'] = False
382 md['dependencies_met'] = False
378
383
379 reply_msg = self.session.send(stream, u'execute_reply',
384 reply_msg = self.session.send(stream, u'execute_reply',
380 reply_content, parent, metadata=md,
385 reply_content, parent, metadata=md,
381 ident=ident)
386 ident=ident)
382
387
383 self.log.debug("%s", reply_msg)
388 self.log.debug("%s", reply_msg)
384
389
385 if not silent and reply_msg['content']['status'] == u'error':
390 if not silent and reply_msg['content']['status'] == u'error' and not skip_exceptions:
386 self._abort_queues()
391 self._abort_queues()
387
392
388 def do_execute(self, code, silent, store_history=True,
393 def do_execute(self, code, silent, store_history=True,
389 user_experssions=None, allow_stdin=False):
394 user_expressions=None, allow_stdin=False):
390 """Execute user code. Must be overridden by subclasses.
395 """Execute user code. Must be overridden by subclasses.
391 """
396 """
392 raise NotImplementedError
397 raise NotImplementedError
393
398
394 def complete_request(self, stream, ident, parent):
399 def complete_request(self, stream, ident, parent):
395 content = parent['content']
400 content = parent['content']
396 code = content['code']
401 code = content['code']
397 cursor_pos = content['cursor_pos']
402 cursor_pos = content['cursor_pos']
398
403
399 matches = self.do_complete(code, cursor_pos)
404 matches = self.do_complete(code, cursor_pos)
400 matches = json_clean(matches)
405 matches = json_clean(matches)
401 completion_msg = self.session.send(stream, 'complete_reply',
406 completion_msg = self.session.send(stream, 'complete_reply',
402 matches, parent, ident)
407 matches, parent, ident)
403 self.log.debug("%s", completion_msg)
408 self.log.debug("%s", completion_msg)
404
409
405 def do_complete(self, code, cursor_pos):
410 def do_complete(self, code, cursor_pos):
406 """Override in subclasses to find completions.
411 """Override in subclasses to find completions.
407 """
412 """
408 return {'matches' : [],
413 return {'matches' : [],
409 'cursor_end' : cursor_pos,
414 'cursor_end' : cursor_pos,
410 'cursor_start' : cursor_pos,
415 'cursor_start' : cursor_pos,
411 'metadata' : {},
416 'metadata' : {},
412 'status' : 'ok'}
417 'status' : 'ok'}
413
418
414 def inspect_request(self, stream, ident, parent):
419 def inspect_request(self, stream, ident, parent):
415 content = parent['content']
420 content = parent['content']
416
421
417 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
422 reply_content = self.do_inspect(content['code'], content['cursor_pos'],
418 content.get('detail_level', 0))
423 content.get('detail_level', 0))
419 # Before we send this object over, we scrub it for JSON usage
424 # Before we send this object over, we scrub it for JSON usage
420 reply_content = json_clean(reply_content)
425 reply_content = json_clean(reply_content)
421 msg = self.session.send(stream, 'inspect_reply',
426 msg = self.session.send(stream, 'inspect_reply',
422 reply_content, parent, ident)
427 reply_content, parent, ident)
423 self.log.debug("%s", msg)
428 self.log.debug("%s", msg)
424
429
425 def do_inspect(self, code, cursor_pos, detail_level=0):
430 def do_inspect(self, code, cursor_pos, detail_level=0):
426 """Override in subclasses to allow introspection.
431 """Override in subclasses to allow introspection.
427 """
432 """
428 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
433 return {'status': 'ok', 'data':{}, 'metadata':{}, 'found':False}
429
434
430 def history_request(self, stream, ident, parent):
435 def history_request(self, stream, ident, parent):
431 content = parent['content']
436 content = parent['content']
432
437
433 reply_content = self.do_history(**content)
438 reply_content = self.do_history(**content)
434
439
435 reply_content = json_clean(reply_content)
440 reply_content = json_clean(reply_content)
436 msg = self.session.send(stream, 'history_reply',
441 msg = self.session.send(stream, 'history_reply',
437 reply_content, parent, ident)
442 reply_content, parent, ident)
438 self.log.debug("%s", msg)
443 self.log.debug("%s", msg)
439
444
440 def do_history(self, hist_access_type, output, raw, session=None, start=None,
445 def do_history(self, hist_access_type, output, raw, session=None, start=None,
441 stop=None, n=None, pattern=None, unique=False):
446 stop=None, n=None, pattern=None, unique=False):
442 """Override in subclasses to access history.
447 """Override in subclasses to access history.
443 """
448 """
444 return {'history': []}
449 return {'history': []}
445
450
446 def connect_request(self, stream, ident, parent):
451 def connect_request(self, stream, ident, parent):
447 if self._recorded_ports is not None:
452 if self._recorded_ports is not None:
448 content = self._recorded_ports.copy()
453 content = self._recorded_ports.copy()
449 else:
454 else:
450 content = {}
455 content = {}
451 msg = self.session.send(stream, 'connect_reply',
456 msg = self.session.send(stream, 'connect_reply',
452 content, parent, ident)
457 content, parent, ident)
453 self.log.debug("%s", msg)
458 self.log.debug("%s", msg)
454
459
455 @property
460 @property
456 def kernel_info(self):
461 def kernel_info(self):
457 return {
462 return {
458 'protocol_version': release.kernel_protocol_version,
463 'protocol_version': release.kernel_protocol_version,
459 'implementation': self.implementation,
464 'implementation': self.implementation,
460 'implementation_version': self.implementation_version,
465 'implementation_version': self.implementation_version,
461 'language_info': self.language_info,
466 'language_info': self.language_info,
462 'banner': self.banner,
467 'banner': self.banner,
463 'help_links': self.help_links,
468 'help_links': self.help_links,
464 }
469 }
465
470
466 def kernel_info_request(self, stream, ident, parent):
471 def kernel_info_request(self, stream, ident, parent):
467 msg = self.session.send(stream, 'kernel_info_reply',
472 msg = self.session.send(stream, 'kernel_info_reply',
468 self.kernel_info, parent, ident)
473 self.kernel_info, parent, ident)
469 self.log.debug("%s", msg)
474 self.log.debug("%s", msg)
470
475
471 def shutdown_request(self, stream, ident, parent):
476 def shutdown_request(self, stream, ident, parent):
472 content = self.do_shutdown(parent['content']['restart'])
477 content = self.do_shutdown(parent['content']['restart'])
473 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
478 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
474 # same content, but different msg_id for broadcasting on IOPub
479 # same content, but different msg_id for broadcasting on IOPub
475 self._shutdown_message = self.session.msg(u'shutdown_reply',
480 self._shutdown_message = self.session.msg(u'shutdown_reply',
476 content, parent
481 content, parent
477 )
482 )
478
483
479 self._at_shutdown()
484 self._at_shutdown()
480 # call sys.exit after a short delay
485 # call sys.exit after a short delay
481 loop = ioloop.IOLoop.instance()
486 loop = ioloop.IOLoop.instance()
482 loop.add_timeout(time.time()+0.1, loop.stop)
487 loop.add_timeout(time.time()+0.1, loop.stop)
483
488
484 def do_shutdown(self, restart):
489 def do_shutdown(self, restart):
485 """Override in subclasses to do things when the frontend shuts down the
490 """Override in subclasses to do things when the frontend shuts down the
486 kernel.
491 kernel.
487 """
492 """
488 return {'status': 'ok', 'restart': restart}
493 return {'status': 'ok', 'restart': restart}
489
494
490 def is_complete_request(self, stream, ident, parent):
495 def is_complete_request(self, stream, ident, parent):
491 content = parent['content']
496 content = parent['content']
492 code = content['code']
497 code = content['code']
493
498
494 reply_content = self.do_is_complete(code)
499 reply_content = self.do_is_complete(code)
495 reply_content = json_clean(reply_content)
500 reply_content = json_clean(reply_content)
496 reply_msg = self.session.send(stream, 'is_complete_reply',
501 reply_msg = self.session.send(stream, 'is_complete_reply',
497 reply_content, parent, ident)
502 reply_content, parent, ident)
498 self.log.debug("%s", reply_msg)
503 self.log.debug("%s", reply_msg)
499
504
500 def do_is_complete(self, code):
505 def do_is_complete(self, code):
501 """Override in subclasses to find completions.
506 """Override in subclasses to find completions.
502 """
507 """
503 return {'status' : 'unknown',
508 return {'status' : 'unknown',
504 }
509 }
505
510
506 #---------------------------------------------------------------------------
511 #---------------------------------------------------------------------------
507 # Engine methods
512 # Engine methods
508 #---------------------------------------------------------------------------
513 #---------------------------------------------------------------------------
509
514
510 def apply_request(self, stream, ident, parent):
515 def apply_request(self, stream, ident, parent):
511 try:
516 try:
512 content = parent[u'content']
517 content = parent[u'content']
513 bufs = parent[u'buffers']
518 bufs = parent[u'buffers']
514 msg_id = parent['header']['msg_id']
519 msg_id = parent['header']['msg_id']
515 except:
520 except:
516 self.log.error("Got bad msg: %s", parent, exc_info=True)
521 self.log.error("Got bad msg: %s", parent, exc_info=True)
517 return
522 return
518
523
519 md = self._make_metadata(parent['metadata'])
524 md = self._make_metadata(parent['metadata'])
520
525
521 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
526 reply_content, result_buf = self.do_apply(content, bufs, msg_id, md)
522
527
523 # put 'ok'/'error' status in header, for scheduler introspection:
528 # put 'ok'/'error' status in header, for scheduler introspection:
524 md['status'] = reply_content['status']
529 md['status'] = reply_content['status']
525
530
526 # flush i/o
531 # flush i/o
527 sys.stdout.flush()
532 sys.stdout.flush()
528 sys.stderr.flush()
533 sys.stderr.flush()
529
534
530 self.session.send(stream, u'apply_reply', reply_content,
535 self.session.send(stream, u'apply_reply', reply_content,
531 parent=parent, ident=ident,buffers=result_buf, metadata=md)
536 parent=parent, ident=ident,buffers=result_buf, metadata=md)
532
537
533 def do_apply(self, content, bufs, msg_id, reply_metadata):
538 def do_apply(self, content, bufs, msg_id, reply_metadata):
534 """Override in subclasses to support the IPython parallel framework.
539 """Override in subclasses to support the IPython parallel framework.
535 """
540 """
536 raise NotImplementedError
541 raise NotImplementedError
537
542
538 #---------------------------------------------------------------------------
543 #---------------------------------------------------------------------------
539 # Control messages
544 # Control messages
540 #---------------------------------------------------------------------------
545 #---------------------------------------------------------------------------
541
546
542 def abort_request(self, stream, ident, parent):
547 def abort_request(self, stream, ident, parent):
543 """abort a specific msg by id"""
548 """abort a specific msg by id"""
544 msg_ids = parent['content'].get('msg_ids', None)
549 msg_ids = parent['content'].get('msg_ids', None)
545 if isinstance(msg_ids, string_types):
550 if isinstance(msg_ids, string_types):
546 msg_ids = [msg_ids]
551 msg_ids = [msg_ids]
547 if not msg_ids:
552 if not msg_ids:
548 self._abort_queues()
553 self._abort_queues()
549 for mid in msg_ids:
554 for mid in msg_ids:
550 self.aborted.add(str(mid))
555 self.aborted.add(str(mid))
551
556
552 content = dict(status='ok')
557 content = dict(status='ok')
553 reply_msg = self.session.send(stream, 'abort_reply', content=content,
558 reply_msg = self.session.send(stream, 'abort_reply', content=content,
554 parent=parent, ident=ident)
559 parent=parent, ident=ident)
555 self.log.debug("%s", reply_msg)
560 self.log.debug("%s", reply_msg)
556
561
557 def clear_request(self, stream, idents, parent):
562 def clear_request(self, stream, idents, parent):
558 """Clear our namespace."""
563 """Clear our namespace."""
559 content = self.do_clear()
564 content = self.do_clear()
560 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
565 self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
561 content = content)
566 content = content)
562
567
563 def do_clear(self):
568 def do_clear(self):
564 """Override in subclasses to clear the namespace
569 """Override in subclasses to clear the namespace
565
570
566 This is only required for IPython.parallel.
571 This is only required for IPython.parallel.
567 """
572 """
568 raise NotImplementedError
573 raise NotImplementedError
569
574
570 #---------------------------------------------------------------------------
575 #---------------------------------------------------------------------------
571 # Protected interface
576 # Protected interface
572 #---------------------------------------------------------------------------
577 #---------------------------------------------------------------------------
573
578
574 def _topic(self, topic):
579 def _topic(self, topic):
575 """prefixed topic for IOPub messages"""
580 """prefixed topic for IOPub messages"""
576 if self.int_id >= 0:
581 if self.int_id >= 0:
577 base = "engine.%i" % self.int_id
582 base = "engine.%i" % self.int_id
578 else:
583 else:
579 base = "kernel.%s" % self.ident
584 base = "kernel.%s" % self.ident
580
585
581 return py3compat.cast_bytes("%s.%s" % (base, topic))
586 return py3compat.cast_bytes("%s.%s" % (base, topic))
582
587
583 def _abort_queues(self):
588 def _abort_queues(self):
584 for stream in self.shell_streams:
589 for stream in self.shell_streams:
585 if stream:
590 if stream:
586 self._abort_queue(stream)
591 self._abort_queue(stream)
587
592
588 def _abort_queue(self, stream):
593 def _abort_queue(self, stream):
589 poller = zmq.Poller()
594 poller = zmq.Poller()
590 poller.register(stream.socket, zmq.POLLIN)
595 poller.register(stream.socket, zmq.POLLIN)
591 while True:
596 while True:
592 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
597 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
593 if msg is None:
598 if msg is None:
594 return
599 return
595
600
596 self.log.info("Aborting:")
601 self.log.info("Aborting:")
597 self.log.info("%s", msg)
602 self.log.info("%s", msg)
598 msg_type = msg['header']['msg_type']
603 msg_type = msg['header']['msg_type']
599 reply_type = msg_type.split('_')[0] + '_reply'
604 reply_type = msg_type.split('_')[0] + '_reply'
600
605
601 status = {'status' : 'aborted'}
606 status = {'status' : 'aborted'}
602 md = {'engine' : self.ident}
607 md = {'engine' : self.ident}
603 md.update(status)
608 md.update(status)
604 reply_msg = self.session.send(stream, reply_type, metadata=md,
609 reply_msg = self.session.send(stream, reply_type, metadata=md,
605 content=status, parent=msg, ident=idents)
610 content=status, parent=msg, ident=idents)
606 self.log.debug("%s", reply_msg)
611 self.log.debug("%s", reply_msg)
607 # We need to wait a bit for requests to come in. This can probably
612 # We need to wait a bit for requests to come in. This can probably
608 # be set shorter for true asynchronous clients.
613 # be set shorter for true asynchronous clients.
609 poller.poll(50)
614 poller.poll(50)
610
615
611
616
612 def _no_raw_input(self):
617 def _no_raw_input(self):
613 """Raise StdinNotImplentedError if active frontend doesn't support
618 """Raise StdinNotImplentedError if active frontend doesn't support
614 stdin."""
619 stdin."""
615 raise StdinNotImplementedError("raw_input was called, but this "
620 raise StdinNotImplementedError("raw_input was called, but this "
616 "frontend does not support stdin.")
621 "frontend does not support stdin.")
617
622
618 def getpass(self, prompt=''):
623 def getpass(self, prompt=''):
619 """Forward getpass to frontends
624 """Forward getpass to frontends
620
625
621 Raises
626 Raises
622 ------
627 ------
623 StdinNotImplentedError if active frontend doesn't support stdin.
628 StdinNotImplentedError if active frontend doesn't support stdin.
624 """
629 """
625 if not self._allow_stdin:
630 if not self._allow_stdin:
626 raise StdinNotImplementedError(
631 raise StdinNotImplementedError(
627 "getpass was called, but this frontend does not support input requests."
632 "getpass was called, but this frontend does not support input requests."
628 )
633 )
629 return self._input_request(prompt,
634 return self._input_request(prompt,
630 self._parent_ident,
635 self._parent_ident,
631 self._parent_header,
636 self._parent_header,
632 password=True,
637 password=True,
633 )
638 )
634
639
635 def raw_input(self, prompt=''):
640 def raw_input(self, prompt=''):
636 """Forward raw_input to frontends
641 """Forward raw_input to frontends
637
642
638 Raises
643 Raises
639 ------
644 ------
640 StdinNotImplentedError if active frontend doesn't support stdin.
645 StdinNotImplentedError if active frontend doesn't support stdin.
641 """
646 """
642 if not self._allow_stdin:
647 if not self._allow_stdin:
643 raise StdinNotImplementedError(
648 raise StdinNotImplementedError(
644 "raw_input was called, but this frontend does not support input requests."
649 "raw_input was called, but this frontend does not support input requests."
645 )
650 )
646 return self._input_request(prompt,
651 return self._input_request(prompt,
647 self._parent_ident,
652 self._parent_ident,
648 self._parent_header,
653 self._parent_header,
649 password=False,
654 password=False,
650 )
655 )
651
656
652 def _input_request(self, prompt, ident, parent, password=False):
657 def _input_request(self, prompt, ident, parent, password=False):
653 # Flush output before making the request.
658 # Flush output before making the request.
654 sys.stderr.flush()
659 sys.stderr.flush()
655 sys.stdout.flush()
660 sys.stdout.flush()
656 # flush the stdin socket, to purge stale replies
661 # flush the stdin socket, to purge stale replies
657 while True:
662 while True:
658 try:
663 try:
659 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
664 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
660 except zmq.ZMQError as e:
665 except zmq.ZMQError as e:
661 if e.errno == zmq.EAGAIN:
666 if e.errno == zmq.EAGAIN:
662 break
667 break
663 else:
668 else:
664 raise
669 raise
665
670
666 # Send the input request.
671 # Send the input request.
667 content = json_clean(dict(prompt=prompt, password=password))
672 content = json_clean(dict(prompt=prompt, password=password))
668 self.session.send(self.stdin_socket, u'input_request', content, parent,
673 self.session.send(self.stdin_socket, u'input_request', content, parent,
669 ident=ident)
674 ident=ident)
670
675
671 # Await a response.
676 # Await a response.
672 while True:
677 while True:
673 try:
678 try:
674 ident, reply = self.session.recv(self.stdin_socket, 0)
679 ident, reply = self.session.recv(self.stdin_socket, 0)
675 except Exception:
680 except Exception:
676 self.log.warn("Invalid Message:", exc_info=True)
681 self.log.warn("Invalid Message:", exc_info=True)
677 except KeyboardInterrupt:
682 except KeyboardInterrupt:
678 # re-raise KeyboardInterrupt, to truncate traceback
683 # re-raise KeyboardInterrupt, to truncate traceback
679 raise KeyboardInterrupt
684 raise KeyboardInterrupt
680 else:
685 else:
681 break
686 break
682 try:
687 try:
683 value = py3compat.unicode_to_str(reply['content']['value'])
688 value = py3compat.unicode_to_str(reply['content']['value'])
684 except:
689 except:
685 self.log.error("Bad input_reply: %s", parent)
690 self.log.error("Bad input_reply: %s", parent)
686 value = ''
691 value = ''
687 if value == '\x04':
692 if value == '\x04':
688 # EOF
693 # EOF
689 raise EOFError
694 raise EOFError
690 return value
695 return value
691
696
692 def _at_shutdown(self):
697 def _at_shutdown(self):
693 """Actions taken at shutdown by the kernel, called by python's atexit.
698 """Actions taken at shutdown by the kernel, called by python's atexit.
694 """
699 """
695 # io.rprint("Kernel at_shutdown") # dbg
700 # io.rprint("Kernel at_shutdown") # dbg
696 if self._shutdown_message is not None:
701 if self._shutdown_message is not None:
697 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
702 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
698 self.log.debug("%s", self._shutdown_message)
703 self.log.debug("%s", self._shutdown_message)
699 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
704 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
@@ -1,1204 +1,1208 b''
1 .. _messaging:
1 .. _messaging:
2
2
3 ======================
3 ======================
4 Messaging in IPython
4 Messaging in IPython
5 ======================
5 ======================
6
6
7
7
8 Versioning
8 Versioning
9 ==========
9 ==========
10
10
11 The IPython message specification is versioned independently of IPython.
11 The IPython message specification is versioned independently of IPython.
12 The current version of the specification is 5.0.
12 The current version of the specification is 5.0.
13
13
14
14
15 Introduction
15 Introduction
16 ============
16 ============
17
17
18 This document explains the basic communications design and messaging
18 This document explains the basic communications design and messaging
19 specification for how the various IPython objects interact over a network
19 specification for how the various IPython objects interact over a network
20 transport. The current implementation uses the ZeroMQ_ library for messaging
20 transport. The current implementation uses the ZeroMQ_ library for messaging
21 within and between hosts.
21 within and between hosts.
22
22
23 .. Note::
23 .. Note::
24
24
25 This document should be considered the authoritative description of the
25 This document should be considered the authoritative description of the
26 IPython messaging protocol, and all developers are strongly encouraged to
26 IPython messaging protocol, and all developers are strongly encouraged to
27 keep it updated as the implementation evolves, so that we have a single
27 keep it updated as the implementation evolves, so that we have a single
28 common reference for all protocol details.
28 common reference for all protocol details.
29
29
30 The basic design is explained in the following diagram:
30 The basic design is explained in the following diagram:
31
31
32 .. image:: figs/frontend-kernel.png
32 .. image:: figs/frontend-kernel.png
33 :width: 450px
33 :width: 450px
34 :alt: IPython kernel/frontend messaging architecture.
34 :alt: IPython kernel/frontend messaging architecture.
35 :align: center
35 :align: center
36 :target: ../_images/frontend-kernel.png
36 :target: ../_images/frontend-kernel.png
37
37
38 A single kernel can be simultaneously connected to one or more frontends. The
38 A single kernel can be simultaneously connected to one or more frontends. The
39 kernel has three sockets that serve the following functions:
39 kernel has three sockets that serve the following functions:
40
40
41 1. Shell: this single ROUTER socket allows multiple incoming connections from
41 1. Shell: this single ROUTER socket allows multiple incoming connections from
42 frontends, and this is the socket where requests for code execution, object
42 frontends, and this is the socket where requests for code execution, object
43 information, prompts, etc. are made to the kernel by any frontend. The
43 information, prompts, etc. are made to the kernel by any frontend. The
44 communication on this socket is a sequence of request/reply actions from
44 communication on this socket is a sequence of request/reply actions from
45 each frontend and the kernel.
45 each frontend and the kernel.
46
46
47 2. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
47 2. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
48 side effects (stdout, stderr, etc.) as well as the requests coming from any
48 side effects (stdout, stderr, etc.) as well as the requests coming from any
49 client over the shell socket and its own requests on the stdin socket. There
49 client over the shell socket and its own requests on the stdin socket. There
50 are a number of actions in Python which generate side effects: :func:`print`
50 are a number of actions in Python which generate side effects: :func:`print`
51 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
51 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
52 a multi-client scenario, we want all frontends to be able to know what each
52 a multi-client scenario, we want all frontends to be able to know what each
53 other has sent to the kernel (this can be useful in collaborative scenarios,
53 other has sent to the kernel (this can be useful in collaborative scenarios,
54 for example). This socket allows both side effects and the information
54 for example). This socket allows both side effects and the information
55 about communications taking place with one client over the shell channel
55 about communications taking place with one client over the shell channel
56 to be made available to all clients in a uniform manner.
56 to be made available to all clients in a uniform manner.
57
57
58 3. stdin: this ROUTER socket is connected to all frontends, and it allows
58 3. stdin: this ROUTER socket is connected to all frontends, and it allows
59 the kernel to request input from the active frontend when :func:`raw_input` is called.
59 the kernel to request input from the active frontend when :func:`raw_input` is called.
60 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
60 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
61 for the kernel while this communication is happening (illustrated in the
61 for the kernel while this communication is happening (illustrated in the
62 figure by the black outline around the central keyboard). In practice,
62 figure by the black outline around the central keyboard). In practice,
63 frontends may display such kernel requests using a special input widget or
63 frontends may display such kernel requests using a special input widget or
64 otherwise indicating that the user is to type input for the kernel instead
64 otherwise indicating that the user is to type input for the kernel instead
65 of normal commands in the frontend.
65 of normal commands in the frontend.
66
66
67 All messages are tagged with enough information (details below) for clients
67 All messages are tagged with enough information (details below) for clients
68 to know which messages come from their own interaction with the kernel and
68 to know which messages come from their own interaction with the kernel and
69 which ones are from other clients, so they can display each type
69 which ones are from other clients, so they can display each type
70 appropriately.
70 appropriately.
71
71
72 4. Control: This channel is identical to Shell, but operates on a separate socket,
72 4. Control: This channel is identical to Shell, but operates on a separate socket,
73 to allow important messages to avoid queueing behind execution requests (e.g. shutdown or abort).
73 to allow important messages to avoid queueing behind execution requests (e.g. shutdown or abort).
74
74
75 The actual format of the messages allowed on each of these channels is
75 The actual format of the messages allowed on each of these channels is
76 specified below. Messages are dicts of dicts with string keys and values that
76 specified below. Messages are dicts of dicts with string keys and values that
77 are reasonably representable in JSON. Our current implementation uses JSON
77 are reasonably representable in JSON. Our current implementation uses JSON
78 explicitly as its message format, but this shouldn't be considered a permanent
78 explicitly as its message format, but this shouldn't be considered a permanent
79 feature. As we've discovered that JSON has non-trivial performance issues due
79 feature. As we've discovered that JSON has non-trivial performance issues due
80 to excessive copying, we may in the future move to a pure pickle-based raw
80 to excessive copying, we may in the future move to a pure pickle-based raw
81 message format. However, it should be possible to easily convert from the raw
81 message format. However, it should be possible to easily convert from the raw
82 objects to JSON, since we may have non-python clients (e.g. a web frontend).
82 objects to JSON, since we may have non-python clients (e.g. a web frontend).
83 As long as it's easy to make a JSON version of the objects that is a faithful
83 As long as it's easy to make a JSON version of the objects that is a faithful
84 representation of all the data, we can communicate with such clients.
84 representation of all the data, we can communicate with such clients.
85
85
86 .. Note::
86 .. Note::
87
87
88 Not all of these have yet been fully fleshed out, but the key ones are, see
88 Not all of these have yet been fully fleshed out, but the key ones are, see
89 kernel and frontend files for actual implementation details.
89 kernel and frontend files for actual implementation details.
90
90
91 General Message Format
91 General Message Format
92 ======================
92 ======================
93
93
94 A message is defined by the following four-dictionary structure::
94 A message is defined by the following four-dictionary structure::
95
95
96 {
96 {
97 # The message header contains a pair of unique identifiers for the
97 # The message header contains a pair of unique identifiers for the
98 # originating session and the actual message id, in addition to the
98 # originating session and the actual message id, in addition to the
99 # username for the process that generated the message. This is useful in
99 # username for the process that generated the message. This is useful in
100 # collaborative settings where multiple users may be interacting with the
100 # collaborative settings where multiple users may be interacting with the
101 # same kernel simultaneously, so that frontends can label the various
101 # same kernel simultaneously, so that frontends can label the various
102 # messages in a meaningful way.
102 # messages in a meaningful way.
103 'header' : {
103 'header' : {
104 'msg_id' : uuid,
104 'msg_id' : uuid,
105 'username' : str,
105 'username' : str,
106 'session' : uuid,
106 'session' : uuid,
107 # All recognized message type strings are listed below.
107 # All recognized message type strings are listed below.
108 'msg_type' : str,
108 'msg_type' : str,
109 # the message protocol version
109 # the message protocol version
110 'version' : '5.0',
110 'version' : '5.0',
111 },
111 },
112
112
113 # In a chain of messages, the header from the parent is copied so that
113 # In a chain of messages, the header from the parent is copied so that
114 # clients can track where messages come from.
114 # clients can track where messages come from.
115 'parent_header' : dict,
115 'parent_header' : dict,
116
116
117 # Any metadata associated with the message.
117 # Any metadata associated with the message.
118 'metadata' : dict,
118 'metadata' : dict,
119
119
120 # The actual content of the message must be a dict, whose structure
120 # The actual content of the message must be a dict, whose structure
121 # depends on the message type.
121 # depends on the message type.
122 'content' : dict,
122 'content' : dict,
123 }
123 }
124
124
125 .. versionchanged:: 5.0
125 .. versionchanged:: 5.0
126
126
127 ``version`` key added to the header.
127 ``version`` key added to the header.
128
128
129 .. _wire_protocol:
129 .. _wire_protocol:
130
130
131 The Wire Protocol
131 The Wire Protocol
132 =================
132 =================
133
133
134
134
135 This message format exists at a high level,
135 This message format exists at a high level,
136 but does not describe the actual *implementation* at the wire level in zeromq.
136 but does not describe the actual *implementation* at the wire level in zeromq.
137 The canonical implementation of the message spec is our :class:`~IPython.kernel.zmq.session.Session` class.
137 The canonical implementation of the message spec is our :class:`~IPython.kernel.zmq.session.Session` class.
138
138
139 .. note::
139 .. note::
140
140
141 This section should only be relevant to non-Python consumers of the protocol.
141 This section should only be relevant to non-Python consumers of the protocol.
142 Python consumers should simply import and use IPython's own implementation of the wire protocol
142 Python consumers should simply import and use IPython's own implementation of the wire protocol
143 in the :class:`IPython.kernel.zmq.session.Session` object.
143 in the :class:`IPython.kernel.zmq.session.Session` object.
144
144
145 Every message is serialized to a sequence of at least six blobs of bytes:
145 Every message is serialized to a sequence of at least six blobs of bytes:
146
146
147 .. sourcecode:: python
147 .. sourcecode:: python
148
148
149 [
149 [
150 b'u-u-i-d', # zmq identity(ies)
150 b'u-u-i-d', # zmq identity(ies)
151 b'<IDS|MSG>', # delimiter
151 b'<IDS|MSG>', # delimiter
152 b'baddad42', # HMAC signature
152 b'baddad42', # HMAC signature
153 b'{header}', # serialized header dict
153 b'{header}', # serialized header dict
154 b'{parent_header}', # serialized parent header dict
154 b'{parent_header}', # serialized parent header dict
155 b'{metadata}', # serialized metadata dict
155 b'{metadata}', # serialized metadata dict
156 b'{content}, # serialized content dict
156 b'{content}, # serialized content dict
157 b'blob', # extra raw data buffer(s)
157 b'blob', # extra raw data buffer(s)
158 ...
158 ...
159 ]
159 ]
160
160
161 The front of the message is the ZeroMQ routing prefix,
161 The front of the message is the ZeroMQ routing prefix,
162 which can be zero or more socket identities.
162 which can be zero or more socket identities.
163 This is every piece of the message prior to the delimiter key ``<IDS|MSG>``.
163 This is every piece of the message prior to the delimiter key ``<IDS|MSG>``.
164 In the case of IOPub, there should be just one prefix component,
164 In the case of IOPub, there should be just one prefix component,
165 which is the topic for IOPub subscribers, e.g. ``execute_result``, ``display_data``.
165 which is the topic for IOPub subscribers, e.g. ``execute_result``, ``display_data``.
166
166
167 .. note::
167 .. note::
168
168
169 In most cases, the IOPub topics are irrelevant and completely ignored,
169 In most cases, the IOPub topics are irrelevant and completely ignored,
170 because frontends just subscribe to all topics.
170 because frontends just subscribe to all topics.
171 The convention used in the IPython kernel is to use the msg_type as the topic,
171 The convention used in the IPython kernel is to use the msg_type as the topic,
172 and possibly extra information about the message, e.g. ``execute_result`` or ``stream.stdout``
172 and possibly extra information about the message, e.g. ``execute_result`` or ``stream.stdout``
173
173
174 After the delimiter is the `HMAC`_ signature of the message, used for authentication.
174 After the delimiter is the `HMAC`_ signature of the message, used for authentication.
175 If authentication is disabled, this should be an empty string.
175 If authentication is disabled, this should be an empty string.
176 By default, the hashing function used for computing these signatures is sha256.
176 By default, the hashing function used for computing these signatures is sha256.
177
177
178 .. _HMAC: http://en.wikipedia.org/wiki/HMAC
178 .. _HMAC: http://en.wikipedia.org/wiki/HMAC
179
179
180 .. note::
180 .. note::
181
181
182 To disable authentication and signature checking,
182 To disable authentication and signature checking,
183 set the `key` field of a connection file to an empty string.
183 set the `key` field of a connection file to an empty string.
184
184
185 The signature is the HMAC hex digest of the concatenation of:
185 The signature is the HMAC hex digest of the concatenation of:
186
186
187 - A shared key (typically the ``key`` field of a connection file)
187 - A shared key (typically the ``key`` field of a connection file)
188 - The serialized header dict
188 - The serialized header dict
189 - The serialized parent header dict
189 - The serialized parent header dict
190 - The serialized metadata dict
190 - The serialized metadata dict
191 - The serialized content dict
191 - The serialized content dict
192
192
193 In Python, this is implemented via:
193 In Python, this is implemented via:
194
194
195 .. sourcecode:: python
195 .. sourcecode:: python
196
196
197 # once:
197 # once:
198 digester = HMAC(key, digestmod=hashlib.sha256)
198 digester = HMAC(key, digestmod=hashlib.sha256)
199
199
200 # for each message
200 # for each message
201 d = digester.copy()
201 d = digester.copy()
202 for serialized_dict in (header, parent, metadata, content):
202 for serialized_dict in (header, parent, metadata, content):
203 d.update(serialized_dict)
203 d.update(serialized_dict)
204 signature = d.hexdigest()
204 signature = d.hexdigest()
205
205
206 After the signature is the actual message, always in four frames of bytes.
206 After the signature is the actual message, always in four frames of bytes.
207 The four dictionaries that compose a message are serialized separately,
207 The four dictionaries that compose a message are serialized separately,
208 in the order of header, parent header, metadata, and content.
208 in the order of header, parent header, metadata, and content.
209 These can be serialized by any function that turns a dict into bytes.
209 These can be serialized by any function that turns a dict into bytes.
210 The default and most common serialization is JSON, but msgpack and pickle
210 The default and most common serialization is JSON, but msgpack and pickle
211 are common alternatives.
211 are common alternatives.
212
212
213 After the serialized dicts are zero to many raw data buffers,
213 After the serialized dicts are zero to many raw data buffers,
214 which can be used by message types that support binary data (mainly apply and data_pub).
214 which can be used by message types that support binary data (mainly apply and data_pub).
215
215
216
216
217 Python functional API
217 Python functional API
218 =====================
218 =====================
219
219
220 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
220 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
221 should develop, at a few key points, functional forms of all the requests that
221 should develop, at a few key points, functional forms of all the requests that
222 take arguments in this manner and automatically construct the necessary dict
222 take arguments in this manner and automatically construct the necessary dict
223 for sending.
223 for sending.
224
224
225 In addition, the Python implementation of the message specification extends
225 In addition, the Python implementation of the message specification extends
226 messages upon deserialization to the following form for convenience::
226 messages upon deserialization to the following form for convenience::
227
227
228 {
228 {
229 'header' : dict,
229 'header' : dict,
230 # The msg's unique identifier and type are always stored in the header,
230 # The msg's unique identifier and type are always stored in the header,
231 # but the Python implementation copies them to the top level.
231 # but the Python implementation copies them to the top level.
232 'msg_id' : uuid,
232 'msg_id' : uuid,
233 'msg_type' : str,
233 'msg_type' : str,
234 'parent_header' : dict,
234 'parent_header' : dict,
235 'content' : dict,
235 'content' : dict,
236 'metadata' : dict,
236 'metadata' : dict,
237 }
237 }
238
238
239 All messages sent to or received by any IPython process should have this
239 All messages sent to or received by any IPython process should have this
240 extended structure.
240 extended structure.
241
241
242
242
243 Messages on the shell ROUTER/DEALER sockets
243 Messages on the shell ROUTER/DEALER sockets
244 ===========================================
244 ===========================================
245
245
246 .. _execute:
246 .. _execute:
247
247
248 Execute
248 Execute
249 -------
249 -------
250
250
251 This message type is used by frontends to ask the kernel to execute code on
251 This message type is used by frontends to ask the kernel to execute code on
252 behalf of the user, in a namespace reserved to the user's variables (and thus
252 behalf of the user, in a namespace reserved to the user's variables (and thus
253 separate from the kernel's own internal code and variables).
253 separate from the kernel's own internal code and variables).
254
254
255 Message type: ``execute_request``::
255 Message type: ``execute_request``::
256
256
257 content = {
257 content = {
258 # Source code to be executed by the kernel, one or more lines.
258 # Source code to be executed by the kernel, one or more lines.
259 'code' : str,
259 'code' : str,
260
260
261 # A boolean flag which, if True, signals the kernel to execute
261 # A boolean flag which, if True, signals the kernel to execute
262 # this code as quietly as possible.
262 # this code as quietly as possible.
263 # silent=True forces store_history to be False,
263 # silent=True forces store_history to be False,
264 # and will *not*:
264 # and will *not*:
265 # - broadcast output on the IOPUB channel
265 # - broadcast output on the IOPUB channel
266 # - have an execute_result
266 # - have an execute_result
267 # The default is False.
267 # The default is False.
268 'silent' : bool,
268 'silent' : bool,
269
269
270 # A boolean flag which, if True, signals the kernel to populate history
270 # A boolean flag which, if True, signals the kernel to populate history
271 # The default is True if silent is False. If silent is True, store_history
271 # The default is True if silent is False. If silent is True, store_history
272 # is forced to be False.
272 # is forced to be False.
273 'store_history' : bool,
273 'store_history' : bool,
274
274
275 # A dict mapping names to expressions to be evaluated in the
275 # A dict mapping names to expressions to be evaluated in the
276 # user's dict. The rich display-data representation of each will be evaluated after execution.
276 # user's dict. The rich display-data representation of each will be evaluated after execution.
277 # See the display_data content for the structure of the representation data.
277 # See the display_data content for the structure of the representation data.
278 'user_expressions' : dict,
278 'user_expressions' : dict,
279
279
280 # Some frontends do not support stdin requests.
280 # Some frontends do not support stdin requests.
281 # If raw_input is called from code executed from such a frontend,
281 # If raw_input is called from code executed from such a frontend,
282 # a StdinNotImplementedError will be raised.
282 # a StdinNotImplementedError will be raised.
283 'allow_stdin' : True,
283 'allow_stdin' : True,
284
285 # A boolean flag, which, if True, does not abort the execution queue, if an exception is encountered.
286 # This allows the queued execution of multiple execute_requests, even if they generate exceptions.
287 'skip_exceptions' : True,
284 }
288 }
285
289
286 .. versionchanged:: 5.0
290 .. versionchanged:: 5.0
287
291
288 ``user_variables`` removed, because it is redundant with user_expressions.
292 ``user_variables`` removed, because it is redundant with user_expressions.
289
293
290 The ``code`` field contains a single string (possibly multiline) to be executed.
294 The ``code`` field contains a single string (possibly multiline) to be executed.
291
295
292 The ``user_expressions`` field deserves a detailed explanation. In the past, IPython had
296 The ``user_expressions`` field deserves a detailed explanation. In the past, IPython had
293 the notion of a prompt string that allowed arbitrary code to be evaluated, and
297 the notion of a prompt string that allowed arbitrary code to be evaluated, and
294 this was put to good use by many in creating prompts that displayed system
298 this was put to good use by many in creating prompts that displayed system
295 status, path information, and even more esoteric uses like remote instrument
299 status, path information, and even more esoteric uses like remote instrument
296 status acquired over the network. But now that IPython has a clean separation
300 status acquired over the network. But now that IPython has a clean separation
297 between the kernel and the clients, the kernel has no prompt knowledge; prompts
301 between the kernel and the clients, the kernel has no prompt knowledge; prompts
298 are a frontend feature, and it should be even possible for different
302 are a frontend feature, and it should be even possible for different
299 frontends to display different prompts while interacting with the same kernel.
303 frontends to display different prompts while interacting with the same kernel.
300 ``user_expressions`` can be used to retrieve this information.
304 ``user_expressions`` can be used to retrieve this information.
301
305
302 Any error in evaluating any expression in ``user_expressions`` will result in
306 Any error in evaluating any expression in ``user_expressions`` will result in
303 only that key containing a standard error message, of the form::
307 only that key containing a standard error message, of the form::
304
308
305 {
309 {
306 'status' : 'error',
310 'status' : 'error',
307 'ename' : 'NameError',
311 'ename' : 'NameError',
308 'evalue' : 'foo',
312 'evalue' : 'foo',
309 'traceback' : ...
313 'traceback' : ...
310 }
314 }
311
315
312 .. Note::
316 .. Note::
313
317
314 In order to obtain the current execution counter for the purposes of
318 In order to obtain the current execution counter for the purposes of
315 displaying input prompts, frontends may make an execution request with an
319 displaying input prompts, frontends may make an execution request with an
316 empty code string and ``silent=True``.
320 empty code string and ``silent=True``.
317
321
318 Upon completion of the execution request, the kernel *always* sends a reply,
322 Upon completion of the execution request, the kernel *always* sends a reply,
319 with a status code indicating what happened and additional data depending on
323 with a status code indicating what happened and additional data depending on
320 the outcome. See :ref:`below <execution_results>` for the possible return
324 the outcome. See :ref:`below <execution_results>` for the possible return
321 codes and associated data.
325 codes and associated data.
322
326
323 .. seealso::
327 .. seealso::
324
328
325 :ref:`execution_semantics`
329 :ref:`execution_semantics`
326
330
327 .. _execution_counter:
331 .. _execution_counter:
328
332
329 Execution counter (prompt number)
333 Execution counter (prompt number)
330 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
334 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
331
335
332 The kernel should have a single, monotonically increasing counter of all execution
336 The kernel should have a single, monotonically increasing counter of all execution
333 requests that are made with ``store_history=True``. This counter is used to populate
337 requests that are made with ``store_history=True``. This counter is used to populate
334 the ``In[n]`` and ``Out[n]`` prompts. The value of this counter will be returned as the
338 the ``In[n]`` and ``Out[n]`` prompts. The value of this counter will be returned as the
335 ``execution_count`` field of all ``execute_reply`` and ``execute_input`` messages.
339 ``execution_count`` field of all ``execute_reply`` and ``execute_input`` messages.
336
340
337 .. _execution_results:
341 .. _execution_results:
338
342
339 Execution results
343 Execution results
340 ~~~~~~~~~~~~~~~~~
344 ~~~~~~~~~~~~~~~~~
341
345
342 Message type: ``execute_reply``::
346 Message type: ``execute_reply``::
343
347
344 content = {
348 content = {
345 # One of: 'ok' OR 'error' OR 'abort'
349 # One of: 'ok' OR 'error' OR 'abort'
346 'status' : str,
350 'status' : str,
347
351
348 # The global kernel counter that increases by one with each request that
352 # The global kernel counter that increases by one with each request that
349 # stores history. This will typically be used by clients to display
353 # stores history. This will typically be used by clients to display
350 # prompt numbers to the user. If the request did not store history, this will
354 # prompt numbers to the user. If the request did not store history, this will
351 # be the current value of the counter in the kernel.
355 # be the current value of the counter in the kernel.
352 'execution_count' : int,
356 'execution_count' : int,
353 }
357 }
354
358
355 When status is 'ok', the following extra fields are present::
359 When status is 'ok', the following extra fields are present::
356
360
357 {
361 {
358 # 'payload' will be a list of payload dicts, and is optional.
362 # 'payload' will be a list of payload dicts, and is optional.
359 # payloads are considered deprecated.
363 # payloads are considered deprecated.
360 # The only requirement of each payload dict is that it have a 'source' key,
364 # The only requirement of each payload dict is that it have a 'source' key,
361 # which is a string classifying the payload (e.g. 'page').
365 # which is a string classifying the payload (e.g. 'page').
362
366
363 'payload' : list(dict),
367 'payload' : list(dict),
364
368
365 # Results for the user_expressions.
369 # Results for the user_expressions.
366 'user_expressions' : dict,
370 'user_expressions' : dict,
367 }
371 }
368
372
369 .. versionchanged:: 5.0
373 .. versionchanged:: 5.0
370
374
371 ``user_variables`` is removed, use user_expressions instead.
375 ``user_variables`` is removed, use user_expressions instead.
372
376
373 When status is 'error', the following extra fields are present::
377 When status is 'error', the following extra fields are present::
374
378
375 {
379 {
376 'ename' : str, # Exception name, as a string
380 'ename' : str, # Exception name, as a string
377 'evalue' : str, # Exception value, as a string
381 'evalue' : str, # Exception value, as a string
378
382
379 # The traceback will contain a list of frames, represented each as a
383 # The traceback will contain a list of frames, represented each as a
380 # string. For now we'll stick to the existing design of ultraTB, which
384 # string. For now we'll stick to the existing design of ultraTB, which
381 # controls exception level of detail statefully. But eventually we'll
385 # controls exception level of detail statefully. But eventually we'll
382 # want to grow into a model where more information is collected and
386 # want to grow into a model where more information is collected and
383 # packed into the traceback object, with clients deciding how little or
387 # packed into the traceback object, with clients deciding how little or
384 # how much of it to unpack. But for now, let's start with a simple list
388 # how much of it to unpack. But for now, let's start with a simple list
385 # of strings, since that requires only minimal changes to ultratb as
389 # of strings, since that requires only minimal changes to ultratb as
386 # written.
390 # written.
387 'traceback' : list,
391 'traceback' : list,
388 }
392 }
389
393
390
394
391 When status is 'abort', there are for now no additional data fields. This
395 When status is 'abort', there are for now no additional data fields. This
392 happens when the kernel was interrupted by a signal.
396 happens when the kernel was interrupted by a signal.
393
397
394 Payloads
398 Payloads
395 ********
399 ********
396
400
397 .. admonition:: Execution payloads
401 .. admonition:: Execution payloads
398
402
399 Payloads are considered deprecated, though their replacement is not yet implemented.
403 Payloads are considered deprecated, though their replacement is not yet implemented.
400
404
401 Payloads are a way to trigger frontend actions from the kernel. Current payloads:
405 Payloads are a way to trigger frontend actions from the kernel. Current payloads:
402
406
403 **page**: display data in a pager.
407 **page**: display data in a pager.
404
408
405 Pager output is used for introspection, or other displayed information that's not considered output.
409 Pager output is used for introspection, or other displayed information that's not considered output.
406 Pager payloads are generally displayed in a separate pane, that can be viewed alongside code,
410 Pager payloads are generally displayed in a separate pane, that can be viewed alongside code,
407 and are not included in notebook documents.
411 and are not included in notebook documents.
408
412
409 .. sourcecode:: python
413 .. sourcecode:: python
410
414
411 {
415 {
412 "source": "page",
416 "source": "page",
413 # mime-bundle of data to display in the pager.
417 # mime-bundle of data to display in the pager.
414 # Must include text/plain.
418 # Must include text/plain.
415 "data": mimebundle,
419 "data": mimebundle,
416 # line offset to start from
420 # line offset to start from
417 "start": int,
421 "start": int,
418 }
422 }
419
423
420 **set_next_input**: create a new output
424 **set_next_input**: create a new output
421
425
422 used to create new cells in the notebook,
426 used to create new cells in the notebook,
423 or set the next input in a console interface.
427 or set the next input in a console interface.
424 The main example being ``%load``.
428 The main example being ``%load``.
425
429
426 .. sourcecode:: python
430 .. sourcecode:: python
427
431
428 {
432 {
429 "source": "set_next_input",
433 "source": "set_next_input",
430 # the text contents of the cell to create
434 # the text contents of the cell to create
431 "text": "some cell content",
435 "text": "some cell content",
432 # If true, replace the current cell in document UIs instead of inserting
436 # If true, replace the current cell in document UIs instead of inserting
433 # a cell. Ignored in console UIs.
437 # a cell. Ignored in console UIs.
434 "replace": bool,
438 "replace": bool,
435 }
439 }
436
440
437 **edit**: open a file for editing.
441 **edit**: open a file for editing.
438
442
439 Triggered by `%edit`. Only the QtConsole currently supports edit payloads.
443 Triggered by `%edit`. Only the QtConsole currently supports edit payloads.
440
444
441 .. sourcecode:: python
445 .. sourcecode:: python
442
446
443 {
447 {
444 "source": "edit",
448 "source": "edit",
445 "filename": "/path/to/file.py", # the file to edit
449 "filename": "/path/to/file.py", # the file to edit
446 "line_number": int, # the line number to start with
450 "line_number": int, # the line number to start with
447 }
451 }
448
452
449 **ask_exit**: instruct the frontend to prompt the user for exit
453 **ask_exit**: instruct the frontend to prompt the user for exit
450
454
451 Allows the kernel to request exit, e.g. via ``%exit`` in IPython.
455 Allows the kernel to request exit, e.g. via ``%exit`` in IPython.
452 Only for console frontends.
456 Only for console frontends.
453
457
454 .. sourcecode:: python
458 .. sourcecode:: python
455
459
456 {
460 {
457 "source": "ask_exit",
461 "source": "ask_exit",
458 # whether the kernel should be left running, only closing the client
462 # whether the kernel should be left running, only closing the client
459 "keepkernel": bool,
463 "keepkernel": bool,
460 }
464 }
461
465
462
466
463 .. _msging_inspection:
467 .. _msging_inspection:
464
468
465 Introspection
469 Introspection
466 -------------
470 -------------
467
471
468 Code can be inspected to show useful information to the user.
472 Code can be inspected to show useful information to the user.
469 It is up to the Kernel to decide what information should be displayed, and its formatting.
473 It is up to the Kernel to decide what information should be displayed, and its formatting.
470
474
471 Message type: ``inspect_request``::
475 Message type: ``inspect_request``::
472
476
473 content = {
477 content = {
474 # The code context in which introspection is requested
478 # The code context in which introspection is requested
475 # this may be up to an entire multiline cell.
479 # this may be up to an entire multiline cell.
476 'code' : str,
480 'code' : str,
477
481
478 # The cursor position within 'code' (in unicode characters) where inspection is requested
482 # The cursor position within 'code' (in unicode characters) where inspection is requested
479 'cursor_pos' : int,
483 'cursor_pos' : int,
480
484
481 # The level of detail desired. In IPython, the default (0) is equivalent to typing
485 # The level of detail desired. In IPython, the default (0) is equivalent to typing
482 # 'x?' at the prompt, 1 is equivalent to 'x??'.
486 # 'x?' at the prompt, 1 is equivalent to 'x??'.
483 # The difference is up to kernels, but in IPython level 1 includes the source code
487 # The difference is up to kernels, but in IPython level 1 includes the source code
484 # if available.
488 # if available.
485 'detail_level' : 0 or 1,
489 'detail_level' : 0 or 1,
486 }
490 }
487
491
488 .. versionchanged:: 5.0
492 .. versionchanged:: 5.0
489
493
490 ``object_info_request`` renamed to ``inspect_request``.
494 ``object_info_request`` renamed to ``inspect_request``.
491
495
492 .. versionchanged:: 5.0
496 .. versionchanged:: 5.0
493
497
494 ``name`` key replaced with ``code`` and ``cursor_pos``,
498 ``name`` key replaced with ``code`` and ``cursor_pos``,
495 moving the lexing responsibility to the kernel.
499 moving the lexing responsibility to the kernel.
496
500
497 The reply is a mime-bundle, like a `display_data`_ message,
501 The reply is a mime-bundle, like a `display_data`_ message,
498 which should be a formatted representation of information about the context.
502 which should be a formatted representation of information about the context.
499 In the notebook, this is used to show tooltips over function calls, etc.
503 In the notebook, this is used to show tooltips over function calls, etc.
500
504
501 Message type: ``inspect_reply``::
505 Message type: ``inspect_reply``::
502
506
503 content = {
507 content = {
504 # 'ok' if the request succeeded or 'error', with error information as in all other replies.
508 # 'ok' if the request succeeded or 'error', with error information as in all other replies.
505 'status' : 'ok',
509 'status' : 'ok',
506
510
507 # data can be empty if nothing is found
511 # data can be empty if nothing is found
508 'data' : dict,
512 'data' : dict,
509 'metadata' : dict,
513 'metadata' : dict,
510 }
514 }
511
515
512 .. versionchanged:: 5.0
516 .. versionchanged:: 5.0
513
517
514 ``object_info_reply`` renamed to ``inspect_reply``.
518 ``object_info_reply`` renamed to ``inspect_reply``.
515
519
516 .. versionchanged:: 5.0
520 .. versionchanged:: 5.0
517
521
518 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
522 Reply is changed from structured data to a mime bundle, allowing formatting decisions to be made by the kernel.
519
523
520 .. _msging_completion:
524 .. _msging_completion:
521
525
522 Completion
526 Completion
523 ----------
527 ----------
524
528
525 Message type: ``complete_request``::
529 Message type: ``complete_request``::
526
530
527 content = {
531 content = {
528 # The code context in which completion is requested
532 # The code context in which completion is requested
529 # this may be up to an entire multiline cell, such as
533 # this may be up to an entire multiline cell, such as
530 # 'foo = a.isal'
534 # 'foo = a.isal'
531 'code' : str,
535 'code' : str,
532
536
533 # The cursor position within 'code' (in unicode characters) where completion is requested
537 # The cursor position within 'code' (in unicode characters) where completion is requested
534 'cursor_pos' : int,
538 'cursor_pos' : int,
535 }
539 }
536
540
537 .. versionchanged:: 5.0
541 .. versionchanged:: 5.0
538
542
539 ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
543 ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
540 Lexing is up to the kernel.
544 Lexing is up to the kernel.
541
545
542
546
543 Message type: ``complete_reply``::
547 Message type: ``complete_reply``::
544
548
545 content = {
549 content = {
546 # The list of all matches to the completion request, such as
550 # The list of all matches to the completion request, such as
547 # ['a.isalnum', 'a.isalpha'] for the above example.
551 # ['a.isalnum', 'a.isalpha'] for the above example.
548 'matches' : list,
552 'matches' : list,
549
553
550 # The range of text that should be replaced by the above matches when a completion is accepted.
554 # The range of text that should be replaced by the above matches when a completion is accepted.
551 # typically cursor_end is the same as cursor_pos in the request.
555 # typically cursor_end is the same as cursor_pos in the request.
552 'cursor_start' : int,
556 'cursor_start' : int,
553 'cursor_end' : int,
557 'cursor_end' : int,
554
558
555 # Information that frontend plugins might use for extra display information about completions.
559 # Information that frontend plugins might use for extra display information about completions.
556 'metadata' : dict,
560 'metadata' : dict,
557
561
558 # status should be 'ok' unless an exception was raised during the request,
562 # status should be 'ok' unless an exception was raised during the request,
559 # in which case it should be 'error', along with the usual error message content
563 # in which case it should be 'error', along with the usual error message content
560 # in other messages.
564 # in other messages.
561 'status' : 'ok'
565 'status' : 'ok'
562 }
566 }
563
567
564 .. versionchanged:: 5.0
568 .. versionchanged:: 5.0
565
569
566 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
570 - ``matched_text`` is removed in favor of ``cursor_start`` and ``cursor_end``.
567 - ``metadata`` is added for extended information.
571 - ``metadata`` is added for extended information.
568
572
569 .. _msging_history:
573 .. _msging_history:
570
574
571 History
575 History
572 -------
576 -------
573
577
574 For clients to explicitly request history from a kernel. The kernel has all
578 For clients to explicitly request history from a kernel. The kernel has all
575 the actual execution history stored in a single location, so clients can
579 the actual execution history stored in a single location, so clients can
576 request it from the kernel when needed.
580 request it from the kernel when needed.
577
581
578 Message type: ``history_request``::
582 Message type: ``history_request``::
579
583
580 content = {
584 content = {
581
585
582 # If True, also return output history in the resulting dict.
586 # If True, also return output history in the resulting dict.
583 'output' : bool,
587 'output' : bool,
584
588
585 # If True, return the raw input history, else the transformed input.
589 # If True, return the raw input history, else the transformed input.
586 'raw' : bool,
590 'raw' : bool,
587
591
588 # So far, this can be 'range', 'tail' or 'search'.
592 # So far, this can be 'range', 'tail' or 'search'.
589 'hist_access_type' : str,
593 'hist_access_type' : str,
590
594
591 # If hist_access_type is 'range', get a range of input cells. session can
595 # If hist_access_type is 'range', get a range of input cells. session can
592 # be a positive session number, or a negative number to count back from
596 # be a positive session number, or a negative number to count back from
593 # the current session.
597 # the current session.
594 'session' : int,
598 'session' : int,
595 # start and stop are line numbers within that session.
599 # start and stop are line numbers within that session.
596 'start' : int,
600 'start' : int,
597 'stop' : int,
601 'stop' : int,
598
602
599 # If hist_access_type is 'tail' or 'search', get the last n cells.
603 # If hist_access_type is 'tail' or 'search', get the last n cells.
600 'n' : int,
604 'n' : int,
601
605
602 # If hist_access_type is 'search', get cells matching the specified glob
606 # If hist_access_type is 'search', get cells matching the specified glob
603 # pattern (with * and ? as wildcards).
607 # pattern (with * and ? as wildcards).
604 'pattern' : str,
608 'pattern' : str,
605
609
606 # If hist_access_type is 'search' and unique is true, do not
610 # If hist_access_type is 'search' and unique is true, do not
607 # include duplicated history. Default is false.
611 # include duplicated history. Default is false.
608 'unique' : bool,
612 'unique' : bool,
609
613
610 }
614 }
611
615
612 .. versionadded:: 4.0
616 .. versionadded:: 4.0
613 The key ``unique`` for ``history_request``.
617 The key ``unique`` for ``history_request``.
614
618
615 Message type: ``history_reply``::
619 Message type: ``history_reply``::
616
620
617 content = {
621 content = {
618 # A list of 3 tuples, either:
622 # A list of 3 tuples, either:
619 # (session, line_number, input) or
623 # (session, line_number, input) or
620 # (session, line_number, (input, output)),
624 # (session, line_number, (input, output)),
621 # depending on whether output was False or True, respectively.
625 # depending on whether output was False or True, respectively.
622 'history' : list,
626 'history' : list,
623 }
627 }
624
628
625 .. _msging_is_complete:
629 .. _msging_is_complete:
626
630
627 Code completeness
631 Code completeness
628 -----------------
632 -----------------
629
633
630 .. versionadded:: 5.0
634 .. versionadded:: 5.0
631
635
632 When the user enters a line in a console style interface, the console must
636 When the user enters a line in a console style interface, the console must
633 decide whether to immediately execute the current code, or whether to show a
637 decide whether to immediately execute the current code, or whether to show a
634 continuation prompt for further input. For instance, in Python ``a = 5`` would
638 continuation prompt for further input. For instance, in Python ``a = 5`` would
635 be executed immediately, while ``for i in range(5):`` would expect further input.
639 be executed immediately, while ``for i in range(5):`` would expect further input.
636
640
637 There are four possible replies:
641 There are four possible replies:
638
642
639 - *complete* code is ready to be executed
643 - *complete* code is ready to be executed
640 - *incomplete* code should prompt for another line
644 - *incomplete* code should prompt for another line
641 - *invalid* code will typically be sent for execution, so that the user sees the
645 - *invalid* code will typically be sent for execution, so that the user sees the
642 error soonest.
646 error soonest.
643 - *unknown* - if the kernel is not able to determine this. The frontend should
647 - *unknown* - if the kernel is not able to determine this. The frontend should
644 also handle the kernel not replying promptly. It may default to sending the
648 also handle the kernel not replying promptly. It may default to sending the
645 code for execution, or it may implement simple fallback heuristics for whether
649 code for execution, or it may implement simple fallback heuristics for whether
646 to execute the code (e.g. execute after a blank line).
650 to execute the code (e.g. execute after a blank line).
647
651
648 Frontends may have ways to override this, forcing the code to be sent for
652 Frontends may have ways to override this, forcing the code to be sent for
649 execution or forcing a continuation prompt.
653 execution or forcing a continuation prompt.
650
654
651 Message type: ``is_complete_request``::
655 Message type: ``is_complete_request``::
652
656
653 content = {
657 content = {
654 # The code entered so far as a multiline string
658 # The code entered so far as a multiline string
655 'code' : str,
659 'code' : str,
656 }
660 }
657
661
658 Message type: ``is_complete_reply``::
662 Message type: ``is_complete_reply``::
659
663
660 content = {
664 content = {
661 # One of 'complete', 'incomplete', 'invalid', 'unknown'
665 # One of 'complete', 'incomplete', 'invalid', 'unknown'
662 'status' : str,
666 'status' : str,
663
667
664 # If status is 'incomplete', indent should contain the characters to use
668 # If status is 'incomplete', indent should contain the characters to use
665 # to indent the next line. This is only a hint: frontends may ignore it
669 # to indent the next line. This is only a hint: frontends may ignore it
666 # and use their own autoindentation rules. For other statuses, this
670 # and use their own autoindentation rules. For other statuses, this
667 # field does not exist.
671 # field does not exist.
668 'indent': str,
672 'indent': str,
669 }
673 }
670
674
671 Connect
675 Connect
672 -------
676 -------
673
677
674 When a client connects to the request/reply socket of the kernel, it can issue
678 When a client connects to the request/reply socket of the kernel, it can issue
675 a connect request to get basic information about the kernel, such as the ports
679 a connect request to get basic information about the kernel, such as the ports
676 the other ZeroMQ sockets are listening on. This allows clients to only have
680 the other ZeroMQ sockets are listening on. This allows clients to only have
677 to know about a single port (the shell channel) to connect to a kernel.
681 to know about a single port (the shell channel) to connect to a kernel.
678
682
679 Message type: ``connect_request``::
683 Message type: ``connect_request``::
680
684
681 content = {
685 content = {
682 }
686 }
683
687
684 Message type: ``connect_reply``::
688 Message type: ``connect_reply``::
685
689
686 content = {
690 content = {
687 'shell_port' : int, # The port the shell ROUTER socket is listening on.
691 'shell_port' : int, # The port the shell ROUTER socket is listening on.
688 'iopub_port' : int, # The port the PUB socket is listening on.
692 'iopub_port' : int, # The port the PUB socket is listening on.
689 'stdin_port' : int, # The port the stdin ROUTER socket is listening on.
693 'stdin_port' : int, # The port the stdin ROUTER socket is listening on.
690 'hb_port' : int, # The port the heartbeat socket is listening on.
694 'hb_port' : int, # The port the heartbeat socket is listening on.
691 }
695 }
692
696
693 .. _msging_kernel_info:
697 .. _msging_kernel_info:
694
698
695 Kernel info
699 Kernel info
696 -----------
700 -----------
697
701
698 If a client needs to know information about the kernel, it can
702 If a client needs to know information about the kernel, it can
699 make a request of the kernel's information.
703 make a request of the kernel's information.
700 This message can be used to fetch core information of the
704 This message can be used to fetch core information of the
701 kernel, including language (e.g., Python), language version number and
705 kernel, including language (e.g., Python), language version number and
702 IPython version number, and the IPython message spec version number.
706 IPython version number, and the IPython message spec version number.
703
707
704 Message type: ``kernel_info_request``::
708 Message type: ``kernel_info_request``::
705
709
706 content = {
710 content = {
707 }
711 }
708
712
709 Message type: ``kernel_info_reply``::
713 Message type: ``kernel_info_reply``::
710
714
711 content = {
715 content = {
712 # Version of messaging protocol.
716 # Version of messaging protocol.
713 # The first integer indicates major version. It is incremented when
717 # The first integer indicates major version. It is incremented when
714 # there is any backward incompatible change.
718 # there is any backward incompatible change.
715 # The second integer indicates minor version. It is incremented when
719 # The second integer indicates minor version. It is incremented when
716 # there is any backward compatible change.
720 # there is any backward compatible change.
717 'protocol_version': 'X.Y.Z',
721 'protocol_version': 'X.Y.Z',
718
722
719 # The kernel implementation name
723 # The kernel implementation name
720 # (e.g. 'ipython' for the IPython kernel)
724 # (e.g. 'ipython' for the IPython kernel)
721 'implementation': str,
725 'implementation': str,
722
726
723 # Implementation version number.
727 # Implementation version number.
724 # The version number of the kernel's implementation
728 # The version number of the kernel's implementation
725 # (e.g. IPython.__version__ for the IPython kernel)
729 # (e.g. IPython.__version__ for the IPython kernel)
726 'implementation_version': 'X.Y.Z',
730 'implementation_version': 'X.Y.Z',
727
731
728 # Information about the language of code for the kernel
732 # Information about the language of code for the kernel
729 'language_info': {
733 'language_info': {
730 # Name of the programming language in which kernel is implemented.
734 # Name of the programming language in which kernel is implemented.
731 # Kernel included in IPython returns 'python'.
735 # Kernel included in IPython returns 'python'.
732 'name': str,
736 'name': str,
733
737
734 # Language version number.
738 # Language version number.
735 # It is Python version number (e.g., '2.7.3') for the kernel
739 # It is Python version number (e.g., '2.7.3') for the kernel
736 # included in IPython.
740 # included in IPython.
737 'version': 'X.Y.Z',
741 'version': 'X.Y.Z',
738
742
739 # mimetype for script files in this language
743 # mimetype for script files in this language
740 'mimetype': str,
744 'mimetype': str,
741
745
742 # Extension without the dot, e.g. 'py'
746 # Extension without the dot, e.g. 'py'
743 'file_extension': str,
747 'file_extension': str,
744
748
745 # Pygments lexer, for highlighting
749 # Pygments lexer, for highlighting
746 # Only needed if it differs from the top level 'language' field.
750 # Only needed if it differs from the top level 'language' field.
747 'pygments_lexer': str,
751 'pygments_lexer': str,
748
752
749 # Codemirror mode, for for highlighting in the notebook.
753 # Codemirror mode, for for highlighting in the notebook.
750 # Only needed if it differs from the top level 'language' field.
754 # Only needed if it differs from the top level 'language' field.
751 'codemirror_mode': str or dict,
755 'codemirror_mode': str or dict,
752
756
753 # Nbconvert exporter, if notebooks written with this kernel should
757 # Nbconvert exporter, if notebooks written with this kernel should
754 # be exported with something other than the general 'script'
758 # be exported with something other than the general 'script'
755 # exporter.
759 # exporter.
756 'nbconvert_exporter': str,
760 'nbconvert_exporter': str,
757 },
761 },
758
762
759 # A banner of information about the kernel,
763 # A banner of information about the kernel,
760 # which may be desplayed in console environments.
764 # which may be desplayed in console environments.
761 'banner' : str,
765 'banner' : str,
762
766
763 # Optional: A list of dictionaries, each with keys 'text' and 'url'.
767 # Optional: A list of dictionaries, each with keys 'text' and 'url'.
764 # These will be displayed in the help menu in the notebook UI.
768 # These will be displayed in the help menu in the notebook UI.
765 'help_links': [
769 'help_links': [
766 {'text': str, 'url': str}
770 {'text': str, 'url': str}
767 ],
771 ],
768 }
772 }
769
773
770 Refer to the lists of available `Pygments lexers <http://pygments.org/docs/lexers/>`_
774 Refer to the lists of available `Pygments lexers <http://pygments.org/docs/lexers/>`_
771 and `codemirror modes <http://codemirror.net/mode/index.html>`_ for those fields.
775 and `codemirror modes <http://codemirror.net/mode/index.html>`_ for those fields.
772
776
773 .. versionchanged:: 5.0
777 .. versionchanged:: 5.0
774
778
775 Versions changed from lists of integers to strings.
779 Versions changed from lists of integers to strings.
776
780
777 .. versionchanged:: 5.0
781 .. versionchanged:: 5.0
778
782
779 ``ipython_version`` is removed.
783 ``ipython_version`` is removed.
780
784
781 .. versionchanged:: 5.0
785 .. versionchanged:: 5.0
782
786
783 ``language_info``, ``implementation``, ``implementation_version``, ``banner``
787 ``language_info``, ``implementation``, ``implementation_version``, ``banner``
784 and ``help_links`` keys are added.
788 and ``help_links`` keys are added.
785
789
786 .. versionchanged:: 5.0
790 .. versionchanged:: 5.0
787
791
788 ``language_version`` moved to ``language_info.version``
792 ``language_version`` moved to ``language_info.version``
789
793
790 .. versionchanged:: 5.0
794 .. versionchanged:: 5.0
791
795
792 ``language`` moved to ``language_info.name``
796 ``language`` moved to ``language_info.name``
793
797
794 .. _msging_shutdown:
798 .. _msging_shutdown:
795
799
796 Kernel shutdown
800 Kernel shutdown
797 ---------------
801 ---------------
798
802
799 The clients can request the kernel to shut itself down; this is used in
803 The clients can request the kernel to shut itself down; this is used in
800 multiple cases:
804 multiple cases:
801
805
802 - when the user chooses to close the client application via a menu or window
806 - when the user chooses to close the client application via a menu or window
803 control.
807 control.
804 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
808 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
805 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
809 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
806 IPythonQt client) to force a kernel restart to get a clean kernel without
810 IPythonQt client) to force a kernel restart to get a clean kernel without
807 losing client-side state like history or inlined figures.
811 losing client-side state like history or inlined figures.
808
812
809 The client sends a shutdown request to the kernel, and once it receives the
813 The client sends a shutdown request to the kernel, and once it receives the
810 reply message (which is otherwise empty), it can assume that the kernel has
814 reply message (which is otherwise empty), it can assume that the kernel has
811 completed shutdown safely.
815 completed shutdown safely.
812
816
813 Upon their own shutdown, client applications will typically execute a last
817 Upon their own shutdown, client applications will typically execute a last
814 minute sanity check and forcefully terminate any kernel that is still alive, to
818 minute sanity check and forcefully terminate any kernel that is still alive, to
815 avoid leaving stray processes in the user's machine.
819 avoid leaving stray processes in the user's machine.
816
820
817 Message type: ``shutdown_request``::
821 Message type: ``shutdown_request``::
818
822
819 content = {
823 content = {
820 'restart' : bool # whether the shutdown is final, or precedes a restart
824 'restart' : bool # whether the shutdown is final, or precedes a restart
821 }
825 }
822
826
823 Message type: ``shutdown_reply``::
827 Message type: ``shutdown_reply``::
824
828
825 content = {
829 content = {
826 'restart' : bool # whether the shutdown is final, or precedes a restart
830 'restart' : bool # whether the shutdown is final, or precedes a restart
827 }
831 }
828
832
829 .. Note::
833 .. Note::
830
834
831 When the clients detect a dead kernel thanks to inactivity on the heartbeat
835 When the clients detect a dead kernel thanks to inactivity on the heartbeat
832 socket, they simply send a forceful process termination signal, since a dead
836 socket, they simply send a forceful process termination signal, since a dead
833 process is unlikely to respond in any useful way to messages.
837 process is unlikely to respond in any useful way to messages.
834
838
835
839
836 Messages on the PUB/SUB socket
840 Messages on the PUB/SUB socket
837 ==============================
841 ==============================
838
842
839 Streams (stdout, stderr, etc)
843 Streams (stdout, stderr, etc)
840 ------------------------------
844 ------------------------------
841
845
842 Message type: ``stream``::
846 Message type: ``stream``::
843
847
844 content = {
848 content = {
845 # The name of the stream is one of 'stdout', 'stderr'
849 # The name of the stream is one of 'stdout', 'stderr'
846 'name' : str,
850 'name' : str,
847
851
848 # The text is an arbitrary string to be written to that stream
852 # The text is an arbitrary string to be written to that stream
849 'text' : str,
853 'text' : str,
850 }
854 }
851
855
852 .. versionchanged:: 5.0
856 .. versionchanged:: 5.0
853
857
854 'data' key renamed to 'text' for conistency with the notebook format.
858 'data' key renamed to 'text' for conistency with the notebook format.
855
859
856 Display Data
860 Display Data
857 ------------
861 ------------
858
862
859 This type of message is used to bring back data that should be displayed (text,
863 This type of message is used to bring back data that should be displayed (text,
860 html, svg, etc.) in the frontends. This data is published to all frontends.
864 html, svg, etc.) in the frontends. This data is published to all frontends.
861 Each message can have multiple representations of the data; it is up to the
865 Each message can have multiple representations of the data; it is up to the
862 frontend to decide which to use and how. A single message should contain all
866 frontend to decide which to use and how. A single message should contain all
863 possible representations of the same information. Each representation should
867 possible representations of the same information. Each representation should
864 be a JSON'able data structure, and should be a valid MIME type.
868 be a JSON'able data structure, and should be a valid MIME type.
865
869
866 Some questions remain about this design:
870 Some questions remain about this design:
867
871
868 * Do we use this message type for execute_result/displayhook? Probably not, because
872 * Do we use this message type for execute_result/displayhook? Probably not, because
869 the displayhook also has to handle the Out prompt display. On the other hand
873 the displayhook also has to handle the Out prompt display. On the other hand
870 we could put that information into the metadata section.
874 we could put that information into the metadata section.
871
875
872 .. _display_data:
876 .. _display_data:
873
877
874 Message type: ``display_data``::
878 Message type: ``display_data``::
875
879
876 content = {
880 content = {
877
881
878 # Who create the data
882 # Who create the data
879 'source' : str,
883 'source' : str,
880
884
881 # The data dict contains key/value pairs, where the keys are MIME
885 # The data dict contains key/value pairs, where the keys are MIME
882 # types and the values are the raw data of the representation in that
886 # types and the values are the raw data of the representation in that
883 # format.
887 # format.
884 'data' : dict,
888 'data' : dict,
885
889
886 # Any metadata that describes the data
890 # Any metadata that describes the data
887 'metadata' : dict
891 'metadata' : dict
888 }
892 }
889
893
890
894
891 The ``metadata`` contains any metadata that describes the output.
895 The ``metadata`` contains any metadata that describes the output.
892 Global keys are assumed to apply to the output as a whole.
896 Global keys are assumed to apply to the output as a whole.
893 The ``metadata`` dict can also contain mime-type keys, which will be sub-dictionaries,
897 The ``metadata`` dict can also contain mime-type keys, which will be sub-dictionaries,
894 which are interpreted as applying only to output of that type.
898 which are interpreted as applying only to output of that type.
895 Third parties should put any data they write into a single dict
899 Third parties should put any data they write into a single dict
896 with a reasonably unique name to avoid conflicts.
900 with a reasonably unique name to avoid conflicts.
897
901
898 The only metadata keys currently defined in IPython are the width and height
902 The only metadata keys currently defined in IPython are the width and height
899 of images::
903 of images::
900
904
901 metadata = {
905 metadata = {
902 'image/png' : {
906 'image/png' : {
903 'width': 640,
907 'width': 640,
904 'height': 480
908 'height': 480
905 }
909 }
906 }
910 }
907
911
908
912
909 .. versionchanged:: 5.0
913 .. versionchanged:: 5.0
910
914
911 `application/json` data should be unpacked JSON data,
915 `application/json` data should be unpacked JSON data,
912 not double-serialized as a JSON string.
916 not double-serialized as a JSON string.
913
917
914
918
915 Raw Data Publication
919 Raw Data Publication
916 --------------------
920 --------------------
917
921
918 ``display_data`` lets you publish *representations* of data, such as images and html.
922 ``display_data`` lets you publish *representations* of data, such as images and html.
919 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
923 This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers.
920
924
921 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
925 data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function:
922
926
923 .. sourcecode:: python
927 .. sourcecode:: python
924
928
925 from IPython.kernel.zmq.datapub import publish_data
929 from IPython.kernel.zmq.datapub import publish_data
926 ns = dict(x=my_array)
930 ns = dict(x=my_array)
927 publish_data(ns)
931 publish_data(ns)
928
932
929
933
930 Message type: ``data_pub``::
934 Message type: ``data_pub``::
931
935
932 content = {
936 content = {
933 # the keys of the data dict, after it has been unserialized
937 # the keys of the data dict, after it has been unserialized
934 'keys' : ['a', 'b']
938 'keys' : ['a', 'b']
935 }
939 }
936 # the namespace dict will be serialized in the message buffers,
940 # the namespace dict will be serialized in the message buffers,
937 # which will have a length of at least one
941 # which will have a length of at least one
938 buffers = [b'pdict', ...]
942 buffers = [b'pdict', ...]
939
943
940
944
941 The interpretation of a sequence of data_pub messages for a given parent request should be
945 The interpretation of a sequence of data_pub messages for a given parent request should be
942 to update a single namespace with subsequent results.
946 to update a single namespace with subsequent results.
943
947
944 .. note::
948 .. note::
945
949
946 No frontends directly handle data_pub messages at this time.
950 No frontends directly handle data_pub messages at this time.
947 It is currently only used by the client/engines in :mod:`IPython.parallel`,
951 It is currently only used by the client/engines in :mod:`IPython.parallel`,
948 where engines may publish *data* to the Client,
952 where engines may publish *data* to the Client,
949 of which the Client can then publish *representations* via ``display_data``
953 of which the Client can then publish *representations* via ``display_data``
950 to various frontends.
954 to various frontends.
951
955
952 Code inputs
956 Code inputs
953 -----------
957 -----------
954
958
955 To let all frontends know what code is being executed at any given time, these
959 To let all frontends know what code is being executed at any given time, these
956 messages contain a re-broadcast of the ``code`` portion of an
960 messages contain a re-broadcast of the ``code`` portion of an
957 :ref:`execute_request <execute>`, along with the :ref:`execution_count
961 :ref:`execute_request <execute>`, along with the :ref:`execution_count
958 <execution_counter>`.
962 <execution_counter>`.
959
963
960 Message type: ``execute_input``::
964 Message type: ``execute_input``::
961
965
962 content = {
966 content = {
963 'code' : str, # Source code to be executed, one or more lines
967 'code' : str, # Source code to be executed, one or more lines
964
968
965 # The counter for this execution is also provided so that clients can
969 # The counter for this execution is also provided so that clients can
966 # display it, since IPython automatically creates variables called _iN
970 # display it, since IPython automatically creates variables called _iN
967 # (for input prompt In[N]).
971 # (for input prompt In[N]).
968 'execution_count' : int
972 'execution_count' : int
969 }
973 }
970
974
971 .. versionchanged:: 5.0
975 .. versionchanged:: 5.0
972
976
973 ``pyin`` is renamed to ``execute_input``.
977 ``pyin`` is renamed to ``execute_input``.
974
978
975
979
976 Execution results
980 Execution results
977 -----------------
981 -----------------
978
982
979 Results of an execution are published as an ``execute_result``.
983 Results of an execution are published as an ``execute_result``.
980 These are identical to `display_data`_ messages, with the addition of an ``execution_count`` key.
984 These are identical to `display_data`_ messages, with the addition of an ``execution_count`` key.
981
985
982 Results can have multiple simultaneous formats depending on its
986 Results can have multiple simultaneous formats depending on its
983 configuration. A plain text representation should always be provided
987 configuration. A plain text representation should always be provided
984 in the ``text/plain`` mime-type. Frontends are free to display any or all of these
988 in the ``text/plain`` mime-type. Frontends are free to display any or all of these
985 according to its capabilities.
989 according to its capabilities.
986 Frontends should ignore mime-types they do not understand. The data itself is
990 Frontends should ignore mime-types they do not understand. The data itself is
987 any JSON object and depends on the format. It is often, but not always a string.
991 any JSON object and depends on the format. It is often, but not always a string.
988
992
989 Message type: ``execute_result``::
993 Message type: ``execute_result``::
990
994
991 content = {
995 content = {
992
996
993 # The counter for this execution is also provided so that clients can
997 # The counter for this execution is also provided so that clients can
994 # display it, since IPython automatically creates variables called _N
998 # display it, since IPython automatically creates variables called _N
995 # (for prompt N).
999 # (for prompt N).
996 'execution_count' : int,
1000 'execution_count' : int,
997
1001
998 # data and metadata are identical to a display_data message.
1002 # data and metadata are identical to a display_data message.
999 # the object being displayed is that passed to the display hook,
1003 # the object being displayed is that passed to the display hook,
1000 # i.e. the *result* of the execution.
1004 # i.e. the *result* of the execution.
1001 'data' : dict,
1005 'data' : dict,
1002 'metadata' : dict,
1006 'metadata' : dict,
1003 }
1007 }
1004
1008
1005 Execution errors
1009 Execution errors
1006 ----------------
1010 ----------------
1007
1011
1008 When an error occurs during code execution
1012 When an error occurs during code execution
1009
1013
1010 Message type: ``error``::
1014 Message type: ``error``::
1011
1015
1012 content = {
1016 content = {
1013 # Similar content to the execute_reply messages for the 'error' case,
1017 # Similar content to the execute_reply messages for the 'error' case,
1014 # except the 'status' field is omitted.
1018 # except the 'status' field is omitted.
1015 }
1019 }
1016
1020
1017 .. versionchanged:: 5.0
1021 .. versionchanged:: 5.0
1018
1022
1019 ``pyerr`` renamed to ``error``
1023 ``pyerr`` renamed to ``error``
1020
1024
1021 Kernel status
1025 Kernel status
1022 -------------
1026 -------------
1023
1027
1024 This message type is used by frontends to monitor the status of the kernel.
1028 This message type is used by frontends to monitor the status of the kernel.
1025
1029
1026 Message type: ``status``::
1030 Message type: ``status``::
1027
1031
1028 content = {
1032 content = {
1029 # When the kernel starts to handle a message, it will enter the 'busy'
1033 # When the kernel starts to handle a message, it will enter the 'busy'
1030 # state and when it finishes, it will enter the 'idle' state.
1034 # state and when it finishes, it will enter the 'idle' state.
1031 # The kernel will publish state 'starting' exactly once at process startup.
1035 # The kernel will publish state 'starting' exactly once at process startup.
1032 execution_state : ('busy', 'idle', 'starting')
1036 execution_state : ('busy', 'idle', 'starting')
1033 }
1037 }
1034
1038
1035 .. versionchanged:: 5.0
1039 .. versionchanged:: 5.0
1036
1040
1037 Busy and idle messages should be sent before/after handling every message,
1041 Busy and idle messages should be sent before/after handling every message,
1038 not just execution.
1042 not just execution.
1039
1043
1040 Clear output
1044 Clear output
1041 ------------
1045 ------------
1042
1046
1043 This message type is used to clear the output that is visible on the frontend.
1047 This message type is used to clear the output that is visible on the frontend.
1044
1048
1045 Message type: ``clear_output``::
1049 Message type: ``clear_output``::
1046
1050
1047 content = {
1051 content = {
1048
1052
1049 # Wait to clear the output until new output is available. Clears the
1053 # Wait to clear the output until new output is available. Clears the
1050 # existing output immediately before the new output is displayed.
1054 # existing output immediately before the new output is displayed.
1051 # Useful for creating simple animations with minimal flickering.
1055 # Useful for creating simple animations with minimal flickering.
1052 'wait' : bool,
1056 'wait' : bool,
1053 }
1057 }
1054
1058
1055 .. versionchanged:: 4.1
1059 .. versionchanged:: 4.1
1056
1060
1057 ``stdout``, ``stderr``, and ``display`` boolean keys for selective clearing are removed,
1061 ``stdout``, ``stderr``, and ``display`` boolean keys for selective clearing are removed,
1058 and ``wait`` is added.
1062 and ``wait`` is added.
1059 The selective clearing keys are ignored in v4 and the default behavior remains the same,
1063 The selective clearing keys are ignored in v4 and the default behavior remains the same,
1060 so v4 clear_output messages will be safely handled by a v4.1 frontend.
1064 so v4 clear_output messages will be safely handled by a v4.1 frontend.
1061
1065
1062
1066
1063 Messages on the stdin ROUTER/DEALER sockets
1067 Messages on the stdin ROUTER/DEALER sockets
1064 ===========================================
1068 ===========================================
1065
1069
1066 This is a socket where the request/reply pattern goes in the opposite direction:
1070 This is a socket where the request/reply pattern goes in the opposite direction:
1067 from the kernel to a *single* frontend, and its purpose is to allow
1071 from the kernel to a *single* frontend, and its purpose is to allow
1068 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
1072 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
1069 to be fulfilled by the client. The request should be made to the frontend that
1073 to be fulfilled by the client. The request should be made to the frontend that
1070 made the execution request that prompted ``raw_input`` to be called. For now we
1074 made the execution request that prompted ``raw_input`` to be called. For now we
1071 will keep these messages as simple as possible, since they only mean to convey
1075 will keep these messages as simple as possible, since they only mean to convey
1072 the ``raw_input(prompt)`` call.
1076 the ``raw_input(prompt)`` call.
1073
1077
1074 Message type: ``input_request``::
1078 Message type: ``input_request``::
1075
1079
1076 content = {
1080 content = {
1077 # the text to show at the prompt
1081 # the text to show at the prompt
1078 'prompt' : str,
1082 'prompt' : str,
1079 # Is the request for a password?
1083 # Is the request for a password?
1080 # If so, the frontend shouldn't echo input.
1084 # If so, the frontend shouldn't echo input.
1081 'password' : bool
1085 'password' : bool
1082 }
1086 }
1083
1087
1084 Message type: ``input_reply``::
1088 Message type: ``input_reply``::
1085
1089
1086 content = { 'value' : str }
1090 content = { 'value' : str }
1087
1091
1088
1092
1089 When ``password`` is True, the frontend should not echo the input as it is entered.
1093 When ``password`` is True, the frontend should not echo the input as it is entered.
1090
1094
1091 .. versionchanged:: 5.0
1095 .. versionchanged:: 5.0
1092
1096
1093 ``password`` key added.
1097 ``password`` key added.
1094
1098
1095 .. note::
1099 .. note::
1096
1100
1097 The stdin socket of the client is required to have the same zmq IDENTITY
1101 The stdin socket of the client is required to have the same zmq IDENTITY
1098 as the client's shell socket.
1102 as the client's shell socket.
1099 Because of this, the ``input_request`` must be sent with the same IDENTITY
1103 Because of this, the ``input_request`` must be sent with the same IDENTITY
1100 routing prefix as the ``execute_reply`` in order for the frontend to receive
1104 routing prefix as the ``execute_reply`` in order for the frontend to receive
1101 the message.
1105 the message.
1102
1106
1103 .. note::
1107 .. note::
1104
1108
1105 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
1109 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
1106 practice the kernel should behave like an interactive program. When a
1110 practice the kernel should behave like an interactive program. When a
1107 program is opened on the console, the keyboard effectively takes over the
1111 program is opened on the console, the keyboard effectively takes over the
1108 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
1112 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
1109 Since the IPython kernel effectively behaves like a console program (albeit
1113 Since the IPython kernel effectively behaves like a console program (albeit
1110 one whose "keyboard" is actually living in a separate process and
1114 one whose "keyboard" is actually living in a separate process and
1111 transported over the zmq connection), raw ``stdin`` isn't expected to be
1115 transported over the zmq connection), raw ``stdin`` isn't expected to be
1112 available.
1116 available.
1113
1117
1114 .. _kernel_heartbeat:
1118 .. _kernel_heartbeat:
1115
1119
1116 Heartbeat for kernels
1120 Heartbeat for kernels
1117 =====================
1121 =====================
1118
1122
1119 Clients send ping messages on a REQ socket, which are echoed right back
1123 Clients send ping messages on a REQ socket, which are echoed right back
1120 from the Kernel's REP socket. These are simple bytestrings, not full JSON messages described above.
1124 from the Kernel's REP socket. These are simple bytestrings, not full JSON messages described above.
1121
1125
1122
1126
1123 Custom Messages
1127 Custom Messages
1124 ===============
1128 ===============
1125
1129
1126 .. versionadded:: 4.1
1130 .. versionadded:: 4.1
1127
1131
1128 IPython 2.0 (msgspec v4.1) adds a messaging system for developers to add their own objects with Frontend
1132 IPython 2.0 (msgspec v4.1) adds a messaging system for developers to add their own objects with Frontend
1129 and Kernel-side components, and allow them to communicate with each other.
1133 and Kernel-side components, and allow them to communicate with each other.
1130 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
1134 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
1131 and can communicate in either direction.
1135 and can communicate in either direction.
1132
1136
1133 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
1137 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
1134 and no messages expect a reply.
1138 and no messages expect a reply.
1135 The Kernel listens for these messages on the Shell channel,
1139 The Kernel listens for these messages on the Shell channel,
1136 and the Frontend listens for them on the IOPub channel.
1140 and the Frontend listens for them on the IOPub channel.
1137
1141
1138 Opening a Comm
1142 Opening a Comm
1139 --------------
1143 --------------
1140
1144
1141 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
1145 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
1142
1146
1143 {
1147 {
1144 'comm_id' : 'u-u-i-d',
1148 'comm_id' : 'u-u-i-d',
1145 'target_name' : 'my_comm',
1149 'target_name' : 'my_comm',
1146 'data' : {}
1150 'data' : {}
1147 }
1151 }
1148
1152
1149 Every Comm has an ID and a target name.
1153 Every Comm has an ID and a target name.
1150 The code handling the message on the receiving side is responsible for maintaining a mapping
1154 The code handling the message on the receiving side is responsible for maintaining a mapping
1151 of target_name keys to constructors.
1155 of target_name keys to constructors.
1152 After a ``comm_open`` message has been sent,
1156 After a ``comm_open`` message has been sent,
1153 there should be a corresponding Comm instance on both sides.
1157 there should be a corresponding Comm instance on both sides.
1154 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1158 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1155
1159
1156 If the ``target_name`` key is not found on the receiving side,
1160 If the ``target_name`` key is not found on the receiving side,
1157 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1161 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1158
1162
1159 Comm Messages
1163 Comm Messages
1160 -------------
1164 -------------
1161
1165
1162 Comm messages are one-way communications to update comm state,
1166 Comm messages are one-way communications to update comm state,
1163 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1167 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1164
1168
1165 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1169 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1166
1170
1167 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1171 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1168
1172
1169 Message type: ``comm_msg``::
1173 Message type: ``comm_msg``::
1170
1174
1171 {
1175 {
1172 'comm_id' : 'u-u-i-d',
1176 'comm_id' : 'u-u-i-d',
1173 'data' : {}
1177 'data' : {}
1174 }
1178 }
1175
1179
1176 Tearing Down Comms
1180 Tearing Down Comms
1177 ------------------
1181 ------------------
1178
1182
1179 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1183 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1180 This is done with a ``comm_close`` message.
1184 This is done with a ``comm_close`` message.
1181
1185
1182 Message type: ``comm_close``::
1186 Message type: ``comm_close``::
1183
1187
1184 {
1188 {
1185 'comm_id' : 'u-u-i-d',
1189 'comm_id' : 'u-u-i-d',
1186 'data' : {}
1190 'data' : {}
1187 }
1191 }
1188
1192
1189 Output Side Effects
1193 Output Side Effects
1190 -------------------
1194 -------------------
1191
1195
1192 Since comm messages can execute arbitrary user code,
1196 Since comm messages can execute arbitrary user code,
1193 handlers should set the parent header and publish status busy / idle,
1197 handlers should set the parent header and publish status busy / idle,
1194 just like an execute request.
1198 just like an execute request.
1195
1199
1196
1200
1197 To Do
1201 To Do
1198 =====
1202 =====
1199
1203
1200 Missing things include:
1204 Missing things include:
1201
1205
1202 * Important: finish thinking through the payload concept and API.
1206 * Important: finish thinking through the payload concept and API.
1203
1207
1204 .. include:: ../links.txt
1208 .. include:: ../links.txt
General Comments 0
You need to be logged in to leave comments. Login now