##// END OF EJS Templates
Make top-level metadata dictionary not optional.
Jason Grout -
Show More
@@ -1,390 +1,390 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008-2011 The IPython Development Team
2 // Copyright (C) 2008-2011 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // Kernel
9 // Kernel
10 //============================================================================
10 //============================================================================
11
11
12 var IPython = (function (IPython) {
12 var IPython = (function (IPython) {
13
13
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16 // Initialization and connection.
16 // Initialization and connection.
17
17
18 var Kernel = function (base_url) {
18 var Kernel = function (base_url) {
19 this.kernel_id = null;
19 this.kernel_id = null;
20 this.shell_channel = null;
20 this.shell_channel = null;
21 this.iopub_channel = null;
21 this.iopub_channel = null;
22 this.base_url = base_url;
22 this.base_url = base_url;
23 this.running = false;
23 this.running = false;
24 this.username = "username";
24 this.username = "username";
25 this.session_id = utils.uuid();
25 this.session_id = utils.uuid();
26 this._msg_callbacks = {};
26 this._msg_callbacks = {};
27
27
28 if (typeof(WebSocket) !== 'undefined') {
28 if (typeof(WebSocket) !== 'undefined') {
29 this.WebSocket = WebSocket;
29 this.WebSocket = WebSocket;
30 } else if (typeof(MozWebSocket) !== 'undefined') {
30 } else if (typeof(MozWebSocket) !== 'undefined') {
31 this.WebSocket = MozWebSocket;
31 this.WebSocket = MozWebSocket;
32 } else {
32 } else {
33 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
33 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox β‰₯ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
34 };
34 };
35 };
35 };
36
36
37
37
38 Kernel.prototype._get_msg = function (msg_type, content) {
38 Kernel.prototype._get_msg = function (msg_type, content) {
39 var msg = {
39 var msg = {
40 header : {
40 header : {
41 msg_id : utils.uuid(),
41 msg_id : utils.uuid(),
42 username : this.username,
42 username : this.username,
43 session : this.session_id,
43 session : this.session_id,
44 msg_type : msg_type
44 msg_type : msg_type
45 },
45 },
46 content : content,
46 content : content,
47 parent_header : {}
47 parent_header : {}
48 };
48 };
49 return msg;
49 return msg;
50 };
50 };
51
51
52 Kernel.prototype.start = function (notebook_id) {
52 Kernel.prototype.start = function (notebook_id) {
53 var that = this;
53 var that = this;
54 if (!this.running) {
54 if (!this.running) {
55 var qs = $.param({notebook:notebook_id});
55 var qs = $.param({notebook:notebook_id});
56 var url = this.base_url + '?' + qs;
56 var url = this.base_url + '?' + qs;
57 $.post(url,
57 $.post(url,
58 $.proxy(that._kernel_started,that),
58 $.proxy(that._kernel_started,that),
59 'json'
59 'json'
60 );
60 );
61 };
61 };
62 };
62 };
63
63
64
64
65 Kernel.prototype.restart = function () {
65 Kernel.prototype.restart = function () {
66 $([IPython.events]).trigger('status_restarting.Kernel');
66 $([IPython.events]).trigger('status_restarting.Kernel');
67 var that = this;
67 var that = this;
68 if (this.running) {
68 if (this.running) {
69 this.stop_channels();
69 this.stop_channels();
70 var url = this.kernel_url + "/restart";
70 var url = this.kernel_url + "/restart";
71 $.post(url,
71 $.post(url,
72 $.proxy(that._kernel_started, that),
72 $.proxy(that._kernel_started, that),
73 'json'
73 'json'
74 );
74 );
75 };
75 };
76 };
76 };
77
77
78
78
79 Kernel.prototype._kernel_started = function (json) {
79 Kernel.prototype._kernel_started = function (json) {
80 console.log("Kernel started: ", json.kernel_id);
80 console.log("Kernel started: ", json.kernel_id);
81 this.running = true;
81 this.running = true;
82 this.kernel_id = json.kernel_id;
82 this.kernel_id = json.kernel_id;
83 this.ws_url = json.ws_url;
83 this.ws_url = json.ws_url;
84 this.kernel_url = this.base_url + "/" + this.kernel_id;
84 this.kernel_url = this.base_url + "/" + this.kernel_id;
85 this.start_channels();
85 this.start_channels();
86 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
86 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this);
87 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
87 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this);
88 };
88 };
89
89
90
90
91 Kernel.prototype._websocket_closed = function(ws_url, early){
91 Kernel.prototype._websocket_closed = function(ws_url, early){
92 var msg;
92 var msg;
93 var parent_item = $('body');
93 var parent_item = $('body');
94 if (early) {
94 if (early) {
95 msg = "Websocket connection to " + ws_url + " could not be established." +
95 msg = "Websocket connection to " + ws_url + " could not be established." +
96 " You will NOT be able to run code." +
96 " You will NOT be able to run code." +
97 " Your browser may not be compatible with the websocket version in the server," +
97 " Your browser may not be compatible with the websocket version in the server," +
98 " or if the url does not look right, there could be an error in the" +
98 " or if the url does not look right, there could be an error in the" +
99 " server's configuration.";
99 " server's configuration.";
100 } else {
100 } else {
101 IPython.notification_widget.set_message('Reconnecting Websockets', 1000);
101 IPython.notification_widget.set_message('Reconnecting Websockets', 1000);
102 this.start_channels();
102 this.start_channels();
103 return;
103 return;
104 }
104 }
105 var dialog = $('<div/>');
105 var dialog = $('<div/>');
106 dialog.html(msg);
106 dialog.html(msg);
107 parent_item.append(dialog);
107 parent_item.append(dialog);
108 dialog.dialog({
108 dialog.dialog({
109 resizable: false,
109 resizable: false,
110 modal: true,
110 modal: true,
111 title: "Websocket closed",
111 title: "Websocket closed",
112 closeText: "",
112 closeText: "",
113 close: function(event, ui) {$(this).dialog('destroy').remove();},
113 close: function(event, ui) {$(this).dialog('destroy').remove();},
114 buttons : {
114 buttons : {
115 "OK": function () {
115 "OK": function () {
116 $(this).dialog('close');
116 $(this).dialog('close');
117 }
117 }
118 }
118 }
119 });
119 });
120
120
121 };
121 };
122
122
123 Kernel.prototype.start_channels = function () {
123 Kernel.prototype.start_channels = function () {
124 var that = this;
124 var that = this;
125 this.stop_channels();
125 this.stop_channels();
126 var ws_url = this.ws_url + this.kernel_url;
126 var ws_url = this.ws_url + this.kernel_url;
127 console.log("Starting WS:", ws_url);
127 console.log("Starting WS:", ws_url);
128 this.shell_channel = new this.WebSocket(ws_url + "/shell");
128 this.shell_channel = new this.WebSocket(ws_url + "/shell");
129 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
129 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
130 send_cookie = function(){
130 send_cookie = function(){
131 this.send(document.cookie);
131 this.send(document.cookie);
132 };
132 };
133 var already_called_onclose = false; // only alert once
133 var already_called_onclose = false; // only alert once
134 ws_closed_early = function(evt){
134 ws_closed_early = function(evt){
135 if (already_called_onclose){
135 if (already_called_onclose){
136 return;
136 return;
137 }
137 }
138 already_called_onclose = true;
138 already_called_onclose = true;
139 if ( ! evt.wasClean ){
139 if ( ! evt.wasClean ){
140 that._websocket_closed(ws_url, true);
140 that._websocket_closed(ws_url, true);
141 }
141 }
142 };
142 };
143 ws_closed_late = function(evt){
143 ws_closed_late = function(evt){
144 if (already_called_onclose){
144 if (already_called_onclose){
145 return;
145 return;
146 }
146 }
147 already_called_onclose = true;
147 already_called_onclose = true;
148 if ( ! evt.wasClean ){
148 if ( ! evt.wasClean ){
149 that._websocket_closed(ws_url, false);
149 that._websocket_closed(ws_url, false);
150 }
150 }
151 };
151 };
152 this.shell_channel.onopen = send_cookie;
152 this.shell_channel.onopen = send_cookie;
153 this.shell_channel.onclose = ws_closed_early;
153 this.shell_channel.onclose = ws_closed_early;
154 this.iopub_channel.onopen = send_cookie;
154 this.iopub_channel.onopen = send_cookie;
155 this.iopub_channel.onclose = ws_closed_early;
155 this.iopub_channel.onclose = ws_closed_early;
156 // switch from early-close to late-close message after 1s
156 // switch from early-close to late-close message after 1s
157 setTimeout(function(){
157 setTimeout(function(){
158 that.shell_channel.onclose = ws_closed_late;
158 that.shell_channel.onclose = ws_closed_late;
159 that.iopub_channel.onclose = ws_closed_late;
159 that.iopub_channel.onclose = ws_closed_late;
160 }, 1000);
160 }, 1000);
161 };
161 };
162
162
163
163
164 Kernel.prototype.stop_channels = function () {
164 Kernel.prototype.stop_channels = function () {
165 if (this.shell_channel !== null) {
165 if (this.shell_channel !== null) {
166 this.shell_channel.onclose = function (evt) {};
166 this.shell_channel.onclose = function (evt) {};
167 this.shell_channel.close();
167 this.shell_channel.close();
168 this.shell_channel = null;
168 this.shell_channel = null;
169 };
169 };
170 if (this.iopub_channel !== null) {
170 if (this.iopub_channel !== null) {
171 this.iopub_channel.onclose = function (evt) {};
171 this.iopub_channel.onclose = function (evt) {};
172 this.iopub_channel.close();
172 this.iopub_channel.close();
173 this.iopub_channel = null;
173 this.iopub_channel = null;
174 };
174 };
175 };
175 };
176
176
177 // Main public methods.
177 // Main public methods.
178
178
179 Kernel.prototype.object_info_request = function (objname, callbacks) {
179 Kernel.prototype.object_info_request = function (objname, callbacks) {
180 // When calling this method pass a callbacks structure of the form:
180 // When calling this method pass a callbacks structure of the form:
181 //
181 //
182 // callbacks = {
182 // callbacks = {
183 // 'object_info_reply': object_into_reply_callback
183 // 'object_info_reply': object_into_reply_callback
184 // }
184 // }
185 //
185 //
186 // The object_info_reply_callback will be passed the content object of the
186 // The object_info_reply_callback will be passed the content object of the
187 // object_into_reply message documented here:
187 // object_into_reply message documented here:
188 //
188 //
189 // http://ipython.org/ipython-doc/dev/development/messaging.html#object-information
189 // http://ipython.org/ipython-doc/dev/development/messaging.html#object-information
190 if(typeof(objname)!=null && objname!=null)
190 if(typeof(objname)!=null && objname!=null)
191 {
191 {
192 var content = {
192 var content = {
193 oname : objname.toString(),
193 oname : objname.toString(),
194 };
194 };
195 var msg = this._get_msg("object_info_request", content);
195 var msg = this._get_msg("object_info_request", content);
196 this.shell_channel.send(JSON.stringify(msg));
196 this.shell_channel.send(JSON.stringify(msg));
197 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
197 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
198 return msg.header.msg_id;
198 return msg.header.msg_id;
199 }
199 }
200 return;
200 return;
201 }
201 }
202
202
203 Kernel.prototype.execute = function (code, callbacks, options) {
203 Kernel.prototype.execute = function (code, callbacks, options) {
204 // The options object should contain the options for the execute call. Its default
204 // The options object should contain the options for the execute call. Its default
205 // values are:
205 // values are:
206 //
206 //
207 // options = {
207 // options = {
208 // silent : true,
208 // silent : true,
209 // user_variables : [],
209 // user_variables : [],
210 // user_expressions : {},
210 // user_expressions : {},
211 // allow_stdin : false
211 // allow_stdin : false
212 // }
212 // }
213 //
213 //
214 // When calling this method pass a callbacks structure of the form:
214 // When calling this method pass a callbacks structure of the form:
215 //
215 //
216 // callbacks = {
216 // callbacks = {
217 // 'execute_reply': execute_reply_callback,
217 // 'execute_reply': execute_reply_callback,
218 // 'output': output_callback,
218 // 'output': output_callback,
219 // 'clear_output': clear_output_callback,
219 // 'clear_output': clear_output_callback,
220 // 'set_next_input': set_next_input_callback
220 // 'set_next_input': set_next_input_callback
221 // }
221 // }
222 //
222 //
223 // The execute_reply_callback will be passed the content and metadata objects of the execute_reply
223 // The execute_reply_callback will be passed the content and metadata objects of the execute_reply
224 // message documented here:
224 // message documented here:
225 //
225 //
226 // http://ipython.org/ipython-doc/dev/development/messaging.html#execute
226 // http://ipython.org/ipython-doc/dev/development/messaging.html#execute
227 //
227 //
228 // The output_callback will be passed msg_type ('stream','display_data','pyout','pyerr')
228 // The output_callback will be passed msg_type ('stream','display_data','pyout','pyerr')
229 // of the output and the content and metadata objects of the PUB/SUB channel that contains the
229 // of the output and the content and metadata objects of the PUB/SUB channel that contains the
230 // output:
230 // output:
231 //
231 //
232 // http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
232 // http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
233 //
233 //
234 // The clear_output_callback will be passed a content object that contains
234 // The clear_output_callback will be passed a content object that contains
235 // stdout, stderr and other fields that are booleans, as well as the metadata object.
235 // stdout, stderr and other fields that are booleans, as well as the metadata object.
236 //
236 //
237 // The set_next_input_callback will be passed the text that should become the next
237 // The set_next_input_callback will be passed the text that should become the next
238 // input cell.
238 // input cell.
239
239
240 var content = {
240 var content = {
241 code : code,
241 code : code,
242 silent : true,
242 silent : true,
243 user_variables : [],
243 user_variables : [],
244 user_expressions : {},
244 user_expressions : {},
245 allow_stdin : false
245 allow_stdin : false
246 };
246 };
247 $.extend(true, content, options)
247 $.extend(true, content, options)
248 var msg = this._get_msg("execute_request", content);
248 var msg = this._get_msg("execute_request", content);
249 this.shell_channel.send(JSON.stringify(msg));
249 this.shell_channel.send(JSON.stringify(msg));
250 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
250 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
251 return msg.header.msg_id;
251 return msg.header.msg_id;
252 };
252 };
253
253
254
254
255 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
255 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
256 // When calling this method pass a callbacks structure of the form:
256 // When calling this method pass a callbacks structure of the form:
257 //
257 //
258 // callbacks = {
258 // callbacks = {
259 // 'complete_reply': complete_reply_callback
259 // 'complete_reply': complete_reply_callback
260 // }
260 // }
261 //
261 //
262 // The complete_reply_callback will be passed the content object of the
262 // The complete_reply_callback will be passed the content object of the
263 // complete_reply message documented here:
263 // complete_reply message documented here:
264 //
264 //
265 // http://ipython.org/ipython-doc/dev/development/messaging.html#complete
265 // http://ipython.org/ipython-doc/dev/development/messaging.html#complete
266 callbacks = callbacks || {};
266 callbacks = callbacks || {};
267 var content = {
267 var content = {
268 text : '',
268 text : '',
269 line : line,
269 line : line,
270 cursor_pos : cursor_pos
270 cursor_pos : cursor_pos
271 };
271 };
272 var msg = this._get_msg("complete_request", content);
272 var msg = this._get_msg("complete_request", content);
273 this.shell_channel.send(JSON.stringify(msg));
273 this.shell_channel.send(JSON.stringify(msg));
274 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
274 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
275 return msg.header.msg_id;
275 return msg.header.msg_id;
276 };
276 };
277
277
278
278
279 Kernel.prototype.interrupt = function () {
279 Kernel.prototype.interrupt = function () {
280 if (this.running) {
280 if (this.running) {
281 $([IPython.events]).trigger('status_interrupting.Kernel');
281 $([IPython.events]).trigger('status_interrupting.Kernel');
282 $.post(this.kernel_url + "/interrupt");
282 $.post(this.kernel_url + "/interrupt");
283 };
283 };
284 };
284 };
285
285
286
286
287 Kernel.prototype.kill = function () {
287 Kernel.prototype.kill = function () {
288 if (this.running) {
288 if (this.running) {
289 this.running = false;
289 this.running = false;
290 var settings = {
290 var settings = {
291 cache : false,
291 cache : false,
292 type : "DELETE"
292 type : "DELETE"
293 };
293 };
294 $.ajax(this.kernel_url, settings);
294 $.ajax(this.kernel_url, settings);
295 };
295 };
296 };
296 };
297
297
298
298
299 // Reply handlers.
299 // Reply handlers.
300
300
301 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
301 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
302 var callbacks = this._msg_callbacks[msg_id];
302 var callbacks = this._msg_callbacks[msg_id];
303 return callbacks;
303 return callbacks;
304 };
304 };
305
305
306
306
307 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
307 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
308 this._msg_callbacks[msg_id] = callbacks || {};
308 this._msg_callbacks[msg_id] = callbacks || {};
309 }
309 }
310
310
311
311
312 Kernel.prototype._handle_shell_reply = function (e) {
312 Kernel.prototype._handle_shell_reply = function (e) {
313 reply = $.parseJSON(e.data);
313 reply = $.parseJSON(e.data);
314 var header = reply.header;
314 var header = reply.header;
315 var content = reply.content;
315 var content = reply.content;
316 var metadata = reply.metadata;
316 var metadata = reply.metadata;
317 var msg_type = header.msg_type;
317 var msg_type = header.msg_type;
318 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
318 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
319 if (callbacks !== undefined) {
319 if (callbacks !== undefined) {
320 var cb = callbacks[msg_type];
320 var cb = callbacks[msg_type];
321 if (cb !== undefined) {
321 if (cb !== undefined) {
322 cb(content, metadata);
322 cb(content, metadata);
323 }
323 }
324 };
324 };
325
325
326 if (content.payload !== undefined) {
326 if (content.payload !== undefined) {
327 var payload = content.payload || [];
327 var payload = content.payload || [];
328 this._handle_payload(callbacks, payload);
328 this._handle_payload(callbacks, payload);
329 }
329 }
330 };
330 };
331
331
332
332
333 Kernel.prototype._handle_payload = function (callbacks, payload) {
333 Kernel.prototype._handle_payload = function (callbacks, payload) {
334 var l = payload.length;
334 var l = payload.length;
335 // Payloads are handled by triggering events because we don't want the Kernel
335 // Payloads are handled by triggering events because we don't want the Kernel
336 // to depend on the Notebook or Pager classes.
336 // to depend on the Notebook or Pager classes.
337 for (var i=0; i<l; i++) {
337 for (var i=0; i<l; i++) {
338 if (payload[i].source === 'IPython.zmq.page.page') {
338 if (payload[i].source === 'IPython.zmq.page.page') {
339 var data = {'text':payload[i].text}
339 var data = {'text':payload[i].text}
340 $([IPython.events]).trigger('open_with_text.Pager', data);
340 $([IPython.events]).trigger('open_with_text.Pager', data);
341 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
341 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
342 if (callbacks.set_next_input !== undefined) {
342 if (callbacks.set_next_input !== undefined) {
343 callbacks.set_next_input(payload[i].text)
343 callbacks.set_next_input(payload[i].text)
344 }
344 }
345 }
345 }
346 };
346 };
347 };
347 };
348
348
349
349
350 Kernel.prototype._handle_iopub_reply = function (e) {
350 Kernel.prototype._handle_iopub_reply = function (e) {
351 var reply = $.parseJSON(e.data);
351 var reply = $.parseJSON(e.data);
352 var content = reply.content;
352 var content = reply.content;
353 var msg_type = reply.header.msg_type;
353 var msg_type = reply.header.msg_type;
354 var metadata = reply.metadata || {};
354 var metadata = reply.metadata;
355 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
355 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
356 if (msg_type !== 'status' && callbacks === undefined) {
356 if (msg_type !== 'status' && callbacks === undefined) {
357 // Message not from one of this notebook's cells and there are no
357 // Message not from one of this notebook's cells and there are no
358 // callbacks to handle it.
358 // callbacks to handle it.
359 return;
359 return;
360 }
360 }
361 var output_types = ['stream','display_data','pyout','pyerr'];
361 var output_types = ['stream','display_data','pyout','pyerr'];
362 if (output_types.indexOf(msg_type) >= 0) {
362 if (output_types.indexOf(msg_type) >= 0) {
363 var cb = callbacks['output'];
363 var cb = callbacks['output'];
364 if (cb !== undefined) {
364 if (cb !== undefined) {
365 cb(msg_type, content, metadata);
365 cb(msg_type, content, metadata);
366 }
366 }
367 } else if (msg_type === 'status') {
367 } else if (msg_type === 'status') {
368 if (content.execution_state === 'busy') {
368 if (content.execution_state === 'busy') {
369 $([IPython.events]).trigger('status_busy.Kernel');
369 $([IPython.events]).trigger('status_busy.Kernel');
370 } else if (content.execution_state === 'idle') {
370 } else if (content.execution_state === 'idle') {
371 $([IPython.events]).trigger('status_idle.Kernel');
371 $([IPython.events]).trigger('status_idle.Kernel');
372 } else if (content.execution_state === 'dead') {
372 } else if (content.execution_state === 'dead') {
373 this.stop_channels();
373 this.stop_channels();
374 $([IPython.events]).trigger('status_dead.Kernel');
374 $([IPython.events]).trigger('status_dead.Kernel');
375 };
375 };
376 } else if (msg_type === 'clear_output') {
376 } else if (msg_type === 'clear_output') {
377 var cb = callbacks['clear_output'];
377 var cb = callbacks['clear_output'];
378 if (cb !== undefined) {
378 if (cb !== undefined) {
379 cb(content, metadata);
379 cb(content, metadata);
380 }
380 }
381 };
381 };
382 };
382 };
383
383
384
384
385 IPython.Kernel = Kernel;
385 IPython.Kernel = Kernel;
386
386
387 return IPython;
387 return IPython;
388
388
389 }(IPython));
389 }(IPython));
390
390
@@ -1,767 +1,767 b''
1 """Session object for building, serializing, sending, and receiving messages in
1 """Session object for building, serializing, sending, and receiving messages in
2 IPython. The Session object supports serialization, HMAC signatures, and
2 IPython. The Session object supports serialization, HMAC signatures, and
3 metadata on messages.
3 metadata on messages.
4
4
5 Also defined here are utilities for working with Sessions:
5 Also defined here are utilities for working with Sessions:
6 * A SessionFactory to be used as a base class for configurables that work with
6 * A SessionFactory to be used as a base class for configurables that work with
7 Sessions.
7 Sessions.
8 * A Message object for convenience that allows attribute-access to the msg dict.
8 * A Message object for convenience that allows attribute-access to the msg dict.
9
9
10 Authors:
10 Authors:
11
11
12 * Min RK
12 * Min RK
13 * Brian Granger
13 * Brian Granger
14 * Fernando Perez
14 * Fernando Perez
15 """
15 """
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2010-2011 The IPython Development Team
17 # Copyright (C) 2010-2011 The IPython Development Team
18 #
18 #
19 # Distributed under the terms of the BSD License. The full license is in
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Imports
24 # Imports
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 import hmac
27 import hmac
28 import logging
28 import logging
29 import os
29 import os
30 import pprint
30 import pprint
31 import uuid
31 import uuid
32 from datetime import datetime
32 from datetime import datetime
33
33
34 try:
34 try:
35 import cPickle
35 import cPickle
36 pickle = cPickle
36 pickle = cPickle
37 except:
37 except:
38 cPickle = None
38 cPickle = None
39 import pickle
39 import pickle
40
40
41 import zmq
41 import zmq
42 from zmq.utils import jsonapi
42 from zmq.utils import jsonapi
43 from zmq.eventloop.ioloop import IOLoop
43 from zmq.eventloop.ioloop import IOLoop
44 from zmq.eventloop.zmqstream import ZMQStream
44 from zmq.eventloop.zmqstream import ZMQStream
45
45
46 from IPython.config.application import Application, boolean_flag
46 from IPython.config.application import Application, boolean_flag
47 from IPython.config.configurable import Configurable, LoggingConfigurable
47 from IPython.config.configurable import Configurable, LoggingConfigurable
48 from IPython.utils.importstring import import_item
48 from IPython.utils.importstring import import_item
49 from IPython.utils.jsonutil import extract_dates, squash_dates, date_default
49 from IPython.utils.jsonutil import extract_dates, squash_dates, date_default
50 from IPython.utils.py3compat import str_to_bytes
50 from IPython.utils.py3compat import str_to_bytes
51 from IPython.utils.traitlets import (CBytes, Unicode, Bool, Any, Instance, Set,
51 from IPython.utils.traitlets import (CBytes, Unicode, Bool, Any, Instance, Set,
52 DottedObjectName, CUnicode, Dict)
52 DottedObjectName, CUnicode, Dict)
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # utility functions
55 # utility functions
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57
57
58 def squash_unicode(obj):
58 def squash_unicode(obj):
59 """coerce unicode back to bytestrings."""
59 """coerce unicode back to bytestrings."""
60 if isinstance(obj,dict):
60 if isinstance(obj,dict):
61 for key in obj.keys():
61 for key in obj.keys():
62 obj[key] = squash_unicode(obj[key])
62 obj[key] = squash_unicode(obj[key])
63 if isinstance(key, unicode):
63 if isinstance(key, unicode):
64 obj[squash_unicode(key)] = obj.pop(key)
64 obj[squash_unicode(key)] = obj.pop(key)
65 elif isinstance(obj, list):
65 elif isinstance(obj, list):
66 for i,v in enumerate(obj):
66 for i,v in enumerate(obj):
67 obj[i] = squash_unicode(v)
67 obj[i] = squash_unicode(v)
68 elif isinstance(obj, unicode):
68 elif isinstance(obj, unicode):
69 obj = obj.encode('utf8')
69 obj = obj.encode('utf8')
70 return obj
70 return obj
71
71
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73 # globals and defaults
73 # globals and defaults
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75
75
76
76
77 # ISO8601-ify datetime objects
77 # ISO8601-ify datetime objects
78 json_packer = lambda obj: jsonapi.dumps(obj, default=date_default)
78 json_packer = lambda obj: jsonapi.dumps(obj, default=date_default)
79 json_unpacker = lambda s: extract_dates(jsonapi.loads(s))
79 json_unpacker = lambda s: extract_dates(jsonapi.loads(s))
80
80
81 pickle_packer = lambda o: pickle.dumps(o,-1)
81 pickle_packer = lambda o: pickle.dumps(o,-1)
82 pickle_unpacker = pickle.loads
82 pickle_unpacker = pickle.loads
83
83
84 default_packer = json_packer
84 default_packer = json_packer
85 default_unpacker = json_unpacker
85 default_unpacker = json_unpacker
86
86
87 DELIM=b"<IDS|MSG>"
87 DELIM=b"<IDS|MSG>"
88
88
89
89
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91 # Mixin tools for apps that use Sessions
91 # Mixin tools for apps that use Sessions
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93
93
94 session_aliases = dict(
94 session_aliases = dict(
95 ident = 'Session.session',
95 ident = 'Session.session',
96 user = 'Session.username',
96 user = 'Session.username',
97 keyfile = 'Session.keyfile',
97 keyfile = 'Session.keyfile',
98 )
98 )
99
99
100 session_flags = {
100 session_flags = {
101 'secure' : ({'Session' : { 'key' : str_to_bytes(str(uuid.uuid4())),
101 'secure' : ({'Session' : { 'key' : str_to_bytes(str(uuid.uuid4())),
102 'keyfile' : '' }},
102 'keyfile' : '' }},
103 """Use HMAC digests for authentication of messages.
103 """Use HMAC digests for authentication of messages.
104 Setting this flag will generate a new UUID to use as the HMAC key.
104 Setting this flag will generate a new UUID to use as the HMAC key.
105 """),
105 """),
106 'no-secure' : ({'Session' : { 'key' : b'', 'keyfile' : '' }},
106 'no-secure' : ({'Session' : { 'key' : b'', 'keyfile' : '' }},
107 """Don't authenticate messages."""),
107 """Don't authenticate messages."""),
108 }
108 }
109
109
110 def default_secure(cfg):
110 def default_secure(cfg):
111 """Set the default behavior for a config environment to be secure.
111 """Set the default behavior for a config environment to be secure.
112
112
113 If Session.key/keyfile have not been set, set Session.key to
113 If Session.key/keyfile have not been set, set Session.key to
114 a new random UUID.
114 a new random UUID.
115 """
115 """
116
116
117 if 'Session' in cfg:
117 if 'Session' in cfg:
118 if 'key' in cfg.Session or 'keyfile' in cfg.Session:
118 if 'key' in cfg.Session or 'keyfile' in cfg.Session:
119 return
119 return
120 # key/keyfile not specified, generate new UUID:
120 # key/keyfile not specified, generate new UUID:
121 cfg.Session.key = str_to_bytes(str(uuid.uuid4()))
121 cfg.Session.key = str_to_bytes(str(uuid.uuid4()))
122
122
123
123
124 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
125 # Classes
125 # Classes
126 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
127
127
128 class SessionFactory(LoggingConfigurable):
128 class SessionFactory(LoggingConfigurable):
129 """The Base class for configurables that have a Session, Context, logger,
129 """The Base class for configurables that have a Session, Context, logger,
130 and IOLoop.
130 and IOLoop.
131 """
131 """
132
132
133 logname = Unicode('')
133 logname = Unicode('')
134 def _logname_changed(self, name, old, new):
134 def _logname_changed(self, name, old, new):
135 self.log = logging.getLogger(new)
135 self.log = logging.getLogger(new)
136
136
137 # not configurable:
137 # not configurable:
138 context = Instance('zmq.Context')
138 context = Instance('zmq.Context')
139 def _context_default(self):
139 def _context_default(self):
140 return zmq.Context.instance()
140 return zmq.Context.instance()
141
141
142 session = Instance('IPython.zmq.session.Session')
142 session = Instance('IPython.zmq.session.Session')
143
143
144 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
144 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
145 def _loop_default(self):
145 def _loop_default(self):
146 return IOLoop.instance()
146 return IOLoop.instance()
147
147
148 def __init__(self, **kwargs):
148 def __init__(self, **kwargs):
149 super(SessionFactory, self).__init__(**kwargs)
149 super(SessionFactory, self).__init__(**kwargs)
150
150
151 if self.session is None:
151 if self.session is None:
152 # construct the session
152 # construct the session
153 self.session = Session(**kwargs)
153 self.session = Session(**kwargs)
154
154
155
155
156 class Message(object):
156 class Message(object):
157 """A simple message object that maps dict keys to attributes.
157 """A simple message object that maps dict keys to attributes.
158
158
159 A Message can be created from a dict and a dict from a Message instance
159 A Message can be created from a dict and a dict from a Message instance
160 simply by calling dict(msg_obj)."""
160 simply by calling dict(msg_obj)."""
161
161
162 def __init__(self, msg_dict):
162 def __init__(self, msg_dict):
163 dct = self.__dict__
163 dct = self.__dict__
164 for k, v in dict(msg_dict).iteritems():
164 for k, v in dict(msg_dict).iteritems():
165 if isinstance(v, dict):
165 if isinstance(v, dict):
166 v = Message(v)
166 v = Message(v)
167 dct[k] = v
167 dct[k] = v
168
168
169 # Having this iterator lets dict(msg_obj) work out of the box.
169 # Having this iterator lets dict(msg_obj) work out of the box.
170 def __iter__(self):
170 def __iter__(self):
171 return iter(self.__dict__.iteritems())
171 return iter(self.__dict__.iteritems())
172
172
173 def __repr__(self):
173 def __repr__(self):
174 return repr(self.__dict__)
174 return repr(self.__dict__)
175
175
176 def __str__(self):
176 def __str__(self):
177 return pprint.pformat(self.__dict__)
177 return pprint.pformat(self.__dict__)
178
178
179 def __contains__(self, k):
179 def __contains__(self, k):
180 return k in self.__dict__
180 return k in self.__dict__
181
181
182 def __getitem__(self, k):
182 def __getitem__(self, k):
183 return self.__dict__[k]
183 return self.__dict__[k]
184
184
185
185
186 def msg_header(msg_id, msg_type, username, session):
186 def msg_header(msg_id, msg_type, username, session):
187 date = datetime.now()
187 date = datetime.now()
188 return locals()
188 return locals()
189
189
190 def extract_header(msg_or_header):
190 def extract_header(msg_or_header):
191 """Given a message or header, return the header."""
191 """Given a message or header, return the header."""
192 if not msg_or_header:
192 if not msg_or_header:
193 return {}
193 return {}
194 try:
194 try:
195 # See if msg_or_header is the entire message.
195 # See if msg_or_header is the entire message.
196 h = msg_or_header['header']
196 h = msg_or_header['header']
197 except KeyError:
197 except KeyError:
198 try:
198 try:
199 # See if msg_or_header is just the header
199 # See if msg_or_header is just the header
200 h = msg_or_header['msg_id']
200 h = msg_or_header['msg_id']
201 except KeyError:
201 except KeyError:
202 raise
202 raise
203 else:
203 else:
204 h = msg_or_header
204 h = msg_or_header
205 if not isinstance(h, dict):
205 if not isinstance(h, dict):
206 h = dict(h)
206 h = dict(h)
207 return h
207 return h
208
208
209 class Session(Configurable):
209 class Session(Configurable):
210 """Object for handling serialization and sending of messages.
210 """Object for handling serialization and sending of messages.
211
211
212 The Session object handles building messages and sending them
212 The Session object handles building messages and sending them
213 with ZMQ sockets or ZMQStream objects. Objects can communicate with each
213 with ZMQ sockets or ZMQStream objects. Objects can communicate with each
214 other over the network via Session objects, and only need to work with the
214 other over the network via Session objects, and only need to work with the
215 dict-based IPython message spec. The Session will handle
215 dict-based IPython message spec. The Session will handle
216 serialization/deserialization, security, and metadata.
216 serialization/deserialization, security, and metadata.
217
217
218 Sessions support configurable serialiization via packer/unpacker traits,
218 Sessions support configurable serialiization via packer/unpacker traits,
219 and signing with HMAC digests via the key/keyfile traits.
219 and signing with HMAC digests via the key/keyfile traits.
220
220
221 Parameters
221 Parameters
222 ----------
222 ----------
223
223
224 debug : bool
224 debug : bool
225 whether to trigger extra debugging statements
225 whether to trigger extra debugging statements
226 packer/unpacker : str : 'json', 'pickle' or import_string
226 packer/unpacker : str : 'json', 'pickle' or import_string
227 importstrings for methods to serialize message parts. If just
227 importstrings for methods to serialize message parts. If just
228 'json' or 'pickle', predefined JSON and pickle packers will be used.
228 'json' or 'pickle', predefined JSON and pickle packers will be used.
229 Otherwise, the entire importstring must be used.
229 Otherwise, the entire importstring must be used.
230
230
231 The functions must accept at least valid JSON input, and output *bytes*.
231 The functions must accept at least valid JSON input, and output *bytes*.
232
232
233 For example, to use msgpack:
233 For example, to use msgpack:
234 packer = 'msgpack.packb', unpacker='msgpack.unpackb'
234 packer = 'msgpack.packb', unpacker='msgpack.unpackb'
235 pack/unpack : callables
235 pack/unpack : callables
236 You can also set the pack/unpack callables for serialization directly.
236 You can also set the pack/unpack callables for serialization directly.
237 session : bytes
237 session : bytes
238 the ID of this Session object. The default is to generate a new UUID.
238 the ID of this Session object. The default is to generate a new UUID.
239 username : unicode
239 username : unicode
240 username added to message headers. The default is to ask the OS.
240 username added to message headers. The default is to ask the OS.
241 key : bytes
241 key : bytes
242 The key used to initialize an HMAC signature. If unset, messages
242 The key used to initialize an HMAC signature. If unset, messages
243 will not be signed or checked.
243 will not be signed or checked.
244 keyfile : filepath
244 keyfile : filepath
245 The file containing a key. If this is set, `key` will be initialized
245 The file containing a key. If this is set, `key` will be initialized
246 to the contents of the file.
246 to the contents of the file.
247
247
248 """
248 """
249
249
250 debug=Bool(False, config=True, help="""Debug output in the Session""")
250 debug=Bool(False, config=True, help="""Debug output in the Session""")
251
251
252 packer = DottedObjectName('json',config=True,
252 packer = DottedObjectName('json',config=True,
253 help="""The name of the packer for serializing messages.
253 help="""The name of the packer for serializing messages.
254 Should be one of 'json', 'pickle', or an import name
254 Should be one of 'json', 'pickle', or an import name
255 for a custom callable serializer.""")
255 for a custom callable serializer.""")
256 def _packer_changed(self, name, old, new):
256 def _packer_changed(self, name, old, new):
257 if new.lower() == 'json':
257 if new.lower() == 'json':
258 self.pack = json_packer
258 self.pack = json_packer
259 self.unpack = json_unpacker
259 self.unpack = json_unpacker
260 elif new.lower() == 'pickle':
260 elif new.lower() == 'pickle':
261 self.pack = pickle_packer
261 self.pack = pickle_packer
262 self.unpack = pickle_unpacker
262 self.unpack = pickle_unpacker
263 else:
263 else:
264 self.pack = import_item(str(new))
264 self.pack = import_item(str(new))
265
265
266 unpacker = DottedObjectName('json', config=True,
266 unpacker = DottedObjectName('json', config=True,
267 help="""The name of the unpacker for unserializing messages.
267 help="""The name of the unpacker for unserializing messages.
268 Only used with custom functions for `packer`.""")
268 Only used with custom functions for `packer`.""")
269 def _unpacker_changed(self, name, old, new):
269 def _unpacker_changed(self, name, old, new):
270 if new.lower() == 'json':
270 if new.lower() == 'json':
271 self.pack = json_packer
271 self.pack = json_packer
272 self.unpack = json_unpacker
272 self.unpack = json_unpacker
273 elif new.lower() == 'pickle':
273 elif new.lower() == 'pickle':
274 self.pack = pickle_packer
274 self.pack = pickle_packer
275 self.unpack = pickle_unpacker
275 self.unpack = pickle_unpacker
276 else:
276 else:
277 self.unpack = import_item(str(new))
277 self.unpack = import_item(str(new))
278
278
279 session = CUnicode(u'', config=True,
279 session = CUnicode(u'', config=True,
280 help="""The UUID identifying this session.""")
280 help="""The UUID identifying this session.""")
281 def _session_default(self):
281 def _session_default(self):
282 u = unicode(uuid.uuid4())
282 u = unicode(uuid.uuid4())
283 self.bsession = u.encode('ascii')
283 self.bsession = u.encode('ascii')
284 return u
284 return u
285
285
286 def _session_changed(self, name, old, new):
286 def _session_changed(self, name, old, new):
287 self.bsession = self.session.encode('ascii')
287 self.bsession = self.session.encode('ascii')
288
288
289 # bsession is the session as bytes
289 # bsession is the session as bytes
290 bsession = CBytes(b'')
290 bsession = CBytes(b'')
291
291
292 username = Unicode(os.environ.get('USER',u'username'), config=True,
292 username = Unicode(os.environ.get('USER',u'username'), config=True,
293 help="""Username for the Session. Default is your system username.""")
293 help="""Username for the Session. Default is your system username.""")
294
294
295 metadata = Dict({}, config=True,
295 metadata = Dict({}, config=True,
296 help="""Metadata dictionary, which serves as the default top-level metadata dict for each message.""")
296 help="""Metadata dictionary, which serves as the default top-level metadata dict for each message.""")
297
297
298 # message signature related traits:
298 # message signature related traits:
299
299
300 key = CBytes(b'', config=True,
300 key = CBytes(b'', config=True,
301 help="""execution key, for extra authentication.""")
301 help="""execution key, for extra authentication.""")
302 def _key_changed(self, name, old, new):
302 def _key_changed(self, name, old, new):
303 if new:
303 if new:
304 self.auth = hmac.HMAC(new)
304 self.auth = hmac.HMAC(new)
305 else:
305 else:
306 self.auth = None
306 self.auth = None
307 auth = Instance(hmac.HMAC)
307 auth = Instance(hmac.HMAC)
308 digest_history = Set()
308 digest_history = Set()
309
309
310 keyfile = Unicode('', config=True,
310 keyfile = Unicode('', config=True,
311 help="""path to file containing execution key.""")
311 help="""path to file containing execution key.""")
312 def _keyfile_changed(self, name, old, new):
312 def _keyfile_changed(self, name, old, new):
313 with open(new, 'rb') as f:
313 with open(new, 'rb') as f:
314 self.key = f.read().strip()
314 self.key = f.read().strip()
315
315
316 # serialization traits:
316 # serialization traits:
317
317
318 pack = Any(default_packer) # the actual packer function
318 pack = Any(default_packer) # the actual packer function
319 def _pack_changed(self, name, old, new):
319 def _pack_changed(self, name, old, new):
320 if not callable(new):
320 if not callable(new):
321 raise TypeError("packer must be callable, not %s"%type(new))
321 raise TypeError("packer must be callable, not %s"%type(new))
322
322
323 unpack = Any(default_unpacker) # the actual packer function
323 unpack = Any(default_unpacker) # the actual packer function
324 def _unpack_changed(self, name, old, new):
324 def _unpack_changed(self, name, old, new):
325 # unpacker is not checked - it is assumed to be
325 # unpacker is not checked - it is assumed to be
326 if not callable(new):
326 if not callable(new):
327 raise TypeError("unpacker must be callable, not %s"%type(new))
327 raise TypeError("unpacker must be callable, not %s"%type(new))
328
328
329 def __init__(self, **kwargs):
329 def __init__(self, **kwargs):
330 """create a Session object
330 """create a Session object
331
331
332 Parameters
332 Parameters
333 ----------
333 ----------
334
334
335 debug : bool
335 debug : bool
336 whether to trigger extra debugging statements
336 whether to trigger extra debugging statements
337 packer/unpacker : str : 'json', 'pickle' or import_string
337 packer/unpacker : str : 'json', 'pickle' or import_string
338 importstrings for methods to serialize message parts. If just
338 importstrings for methods to serialize message parts. If just
339 'json' or 'pickle', predefined JSON and pickle packers will be used.
339 'json' or 'pickle', predefined JSON and pickle packers will be used.
340 Otherwise, the entire importstring must be used.
340 Otherwise, the entire importstring must be used.
341
341
342 The functions must accept at least valid JSON input, and output
342 The functions must accept at least valid JSON input, and output
343 *bytes*.
343 *bytes*.
344
344
345 For example, to use msgpack:
345 For example, to use msgpack:
346 packer = 'msgpack.packb', unpacker='msgpack.unpackb'
346 packer = 'msgpack.packb', unpacker='msgpack.unpackb'
347 pack/unpack : callables
347 pack/unpack : callables
348 You can also set the pack/unpack callables for serialization
348 You can also set the pack/unpack callables for serialization
349 directly.
349 directly.
350 session : unicode (must be ascii)
350 session : unicode (must be ascii)
351 the ID of this Session object. The default is to generate a new
351 the ID of this Session object. The default is to generate a new
352 UUID.
352 UUID.
353 bsession : bytes
353 bsession : bytes
354 The session as bytes
354 The session as bytes
355 username : unicode
355 username : unicode
356 username added to message headers. The default is to ask the OS.
356 username added to message headers. The default is to ask the OS.
357 key : bytes
357 key : bytes
358 The key used to initialize an HMAC signature. If unset, messages
358 The key used to initialize an HMAC signature. If unset, messages
359 will not be signed or checked.
359 will not be signed or checked.
360 keyfile : filepath
360 keyfile : filepath
361 The file containing a key. If this is set, `key` will be
361 The file containing a key. If this is set, `key` will be
362 initialized to the contents of the file.
362 initialized to the contents of the file.
363 """
363 """
364 super(Session, self).__init__(**kwargs)
364 super(Session, self).__init__(**kwargs)
365 self._check_packers()
365 self._check_packers()
366 self.none = self.pack({})
366 self.none = self.pack({})
367 # ensure self._session_default() if necessary, so bsession is defined:
367 # ensure self._session_default() if necessary, so bsession is defined:
368 self.session
368 self.session
369
369
370 @property
370 @property
371 def msg_id(self):
371 def msg_id(self):
372 """always return new uuid"""
372 """always return new uuid"""
373 return str(uuid.uuid4())
373 return str(uuid.uuid4())
374
374
375 def _check_packers(self):
375 def _check_packers(self):
376 """check packers for binary data and datetime support."""
376 """check packers for binary data and datetime support."""
377 pack = self.pack
377 pack = self.pack
378 unpack = self.unpack
378 unpack = self.unpack
379
379
380 # check simple serialization
380 # check simple serialization
381 msg = dict(a=[1,'hi'])
381 msg = dict(a=[1,'hi'])
382 try:
382 try:
383 packed = pack(msg)
383 packed = pack(msg)
384 except Exception:
384 except Exception:
385 raise ValueError("packer could not serialize a simple message")
385 raise ValueError("packer could not serialize a simple message")
386
386
387 # ensure packed message is bytes
387 # ensure packed message is bytes
388 if not isinstance(packed, bytes):
388 if not isinstance(packed, bytes):
389 raise ValueError("message packed to %r, but bytes are required"%type(packed))
389 raise ValueError("message packed to %r, but bytes are required"%type(packed))
390
390
391 # check that unpack is pack's inverse
391 # check that unpack is pack's inverse
392 try:
392 try:
393 unpacked = unpack(packed)
393 unpacked = unpack(packed)
394 except Exception:
394 except Exception:
395 raise ValueError("unpacker could not handle the packer's output")
395 raise ValueError("unpacker could not handle the packer's output")
396
396
397 # check datetime support
397 # check datetime support
398 msg = dict(t=datetime.now())
398 msg = dict(t=datetime.now())
399 try:
399 try:
400 unpacked = unpack(pack(msg))
400 unpacked = unpack(pack(msg))
401 except Exception:
401 except Exception:
402 self.pack = lambda o: pack(squash_dates(o))
402 self.pack = lambda o: pack(squash_dates(o))
403 self.unpack = lambda s: extract_dates(unpack(s))
403 self.unpack = lambda s: extract_dates(unpack(s))
404
404
405 def msg_header(self, msg_type):
405 def msg_header(self, msg_type):
406 return msg_header(self.msg_id, msg_type, self.username, self.session)
406 return msg_header(self.msg_id, msg_type, self.username, self.session)
407
407
408 def msg(self, msg_type, content=None, parent=None, subheader=None, header=None, metadata=None):
408 def msg(self, msg_type, content=None, parent=None, subheader=None, header=None, metadata=None):
409 """Return the nested message dict.
409 """Return the nested message dict.
410
410
411 This format is different from what is sent over the wire. The
411 This format is different from what is sent over the wire. The
412 serialize/unserialize methods converts this nested message dict to the wire
412 serialize/unserialize methods converts this nested message dict to the wire
413 format, which is a list of message parts.
413 format, which is a list of message parts.
414 """
414 """
415 msg = {}
415 msg = {}
416 header = self.msg_header(msg_type) if header is None else header
416 header = self.msg_header(msg_type) if header is None else header
417 msg['header'] = header
417 msg['header'] = header
418 if subheader is not None:
418 if subheader is not None:
419 msg['header'].update(subheader)
419 msg['header'].update(subheader)
420 msg['msg_id'] = header['msg_id']
420 msg['msg_id'] = header['msg_id']
421 msg['msg_type'] = header['msg_type']
421 msg['msg_type'] = header['msg_type']
422 msg['parent_header'] = {} if parent is None else extract_header(parent)
422 msg['parent_header'] = {} if parent is None else extract_header(parent)
423 msg['content'] = {} if content is None else content
423 msg['content'] = {} if content is None else content
424 metadata_dict = self.metadata.copy()
424 msg['metadata'] = self.metadata.copy()
425 if metadata is not None:
425 if metadata is not None:
426 metadata_dict.update(metadata)
426 msg['metadata'].update(metadata)
427 if metadata_dict:
428 msg['metadata'] = metadata_dict
429 return msg
427 return msg
430
428
431 def sign(self, msg_list):
429 def sign(self, msg_list):
432 """Sign a message with HMAC digest. If no auth, return b''.
430 """Sign a message with HMAC digest. If no auth, return b''.
433
431
434 Parameters
432 Parameters
435 ----------
433 ----------
436 msg_list : list
434 msg_list : list
437 The [p_header,p_parent,p_content] part of the message list.
435 The [p_header,p_parent,p_content] part of the message list.
438 """
436 """
439 if self.auth is None:
437 if self.auth is None:
440 return b''
438 return b''
441 h = self.auth.copy()
439 h = self.auth.copy()
442 for m in msg_list:
440 for m in msg_list:
443 h.update(m)
441 h.update(m)
444 return str_to_bytes(h.hexdigest())
442 return str_to_bytes(h.hexdigest())
445
443
446 def serialize(self, msg, ident=None):
444 def serialize(self, msg, ident=None):
447 """Serialize the message components to bytes.
445 """Serialize the message components to bytes.
448
446
449 This is roughly the inverse of unserialize. The serialize/unserialize
447 This is roughly the inverse of unserialize. The serialize/unserialize
450 methods work with full message lists, whereas pack/unpack work with
448 methods work with full message lists, whereas pack/unpack work with
451 the individual message parts in the message list.
449 the individual message parts in the message list.
452
450
453 Parameters
451 Parameters
454 ----------
452 ----------
455 msg : dict or Message
453 msg : dict or Message
456 The nexted message dict as returned by the self.msg method.
454 The nexted message dict as returned by the self.msg method.
457
455
458 Returns
456 Returns
459 -------
457 -------
460 msg_list : list
458 msg_list : list
461 The list of bytes objects to be sent with the format:
459 The list of bytes objects to be sent with the format:
462 [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_content,
460 [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_metadata,p_content,
463 buffer1,buffer2,...]. In this list, the p_* entities are
461 buffer1,buffer2,...]. In this list, the p_* entities are
464 the packed or serialized versions, so if JSON is used, these
462 the packed or serialized versions, so if JSON is used, these
465 are utf8 encoded JSON strings.
463 are utf8 encoded JSON strings.
466 """
464 """
467 content = msg.get('content', {})
465 content = msg.get('content', {})
468 if content is None:
466 if content is None:
469 content = self.none
467 content = self.none
470 elif isinstance(content, dict):
468 elif isinstance(content, dict):
471 content = self.pack(content)
469 content = self.pack(content)
472 elif isinstance(content, bytes):
470 elif isinstance(content, bytes):
473 # content is already packed, as in a relayed message
471 # content is already packed, as in a relayed message
474 pass
472 pass
475 elif isinstance(content, unicode):
473 elif isinstance(content, unicode):
476 # should be bytes, but JSON often spits out unicode
474 # should be bytes, but JSON often spits out unicode
477 content = content.encode('utf8')
475 content = content.encode('utf8')
478 else:
476 else:
479 raise TypeError("Content incorrect type: %s"%type(content))
477 raise TypeError("Content incorrect type: %s"%type(content))
480
478
481 real_message = [self.pack(msg['header']),
479 real_message = [self.pack(msg['header']),
482 self.pack(msg['parent_header']),
480 self.pack(msg['parent_header']),
483 content
481 self.pack(msg['metadata']),
482 content,
484 ]
483 ]
485
484
486 to_send = []
485 to_send = []
487
486
488 if isinstance(ident, list):
487 if isinstance(ident, list):
489 # accept list of idents
488 # accept list of idents
490 to_send.extend(ident)
489 to_send.extend(ident)
491 elif ident is not None:
490 elif ident is not None:
492 to_send.append(ident)
491 to_send.append(ident)
493 to_send.append(DELIM)
492 to_send.append(DELIM)
494
493
495 signature = self.sign(real_message)
494 signature = self.sign(real_message)
496 to_send.append(signature)
495 to_send.append(signature)
497
496
498 to_send.extend(real_message)
497 to_send.extend(real_message)
499
498
500 return to_send
499 return to_send
501
500
502 def send(self, stream, msg_or_type, content=None, parent=None, ident=None,
501 def send(self, stream, msg_or_type, content=None, parent=None, ident=None,
503 buffers=None, subheader=None, track=False, header=None, metadata=None):
502 buffers=None, subheader=None, track=False, header=None, metadata=None):
504 """Build and send a message via stream or socket.
503 """Build and send a message via stream or socket.
505
504
506 The message format used by this function internally is as follows:
505 The message format used by this function internally is as follows:
507
506
508 [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_content,
507 [ident1,ident2,...,DELIM,HMAC,p_header,p_parent,p_content,
509 buffer1,buffer2,...]
508 buffer1,buffer2,...]
510
509
511 The serialize/unserialize methods convert the nested message dict into this
510 The serialize/unserialize methods convert the nested message dict into this
512 format.
511 format.
513
512
514 Parameters
513 Parameters
515 ----------
514 ----------
516
515
517 stream : zmq.Socket or ZMQStream
516 stream : zmq.Socket or ZMQStream
518 The socket-like object used to send the data.
517 The socket-like object used to send the data.
519 msg_or_type : str or Message/dict
518 msg_or_type : str or Message/dict
520 Normally, msg_or_type will be a msg_type unless a message is being
519 Normally, msg_or_type will be a msg_type unless a message is being
521 sent more than once. If a header is supplied, this can be set to
520 sent more than once. If a header is supplied, this can be set to
522 None and the msg_type will be pulled from the header.
521 None and the msg_type will be pulled from the header.
523
522
524 content : dict or None
523 content : dict or None
525 The content of the message (ignored if msg_or_type is a message).
524 The content of the message (ignored if msg_or_type is a message).
526 header : dict or None
525 header : dict or None
527 The header dict for the message (ignored if msg_to_type is a message).
526 The header dict for the message (ignored if msg_to_type is a message).
528 parent : Message or dict or None
527 parent : Message or dict or None
529 The parent or parent header describing the parent of this message
528 The parent or parent header describing the parent of this message
530 (ignored if msg_or_type is a message).
529 (ignored if msg_or_type is a message).
531 ident : bytes or list of bytes
530 ident : bytes or list of bytes
532 The zmq.IDENTITY routing path.
531 The zmq.IDENTITY routing path.
533 subheader : dict or None
532 subheader : dict or None
534 Extra header keys for this message's header (ignored if msg_or_type
533 Extra header keys for this message's header (ignored if msg_or_type
535 is a message).
534 is a message).
536 metadata : dict or None
535 metadata : dict or None
537 The metadata describing the message
536 The metadata describing the message
538 buffers : list or None
537 buffers : list or None
539 The already-serialized buffers to be appended to the message.
538 The already-serialized buffers to be appended to the message.
540 track : bool
539 track : bool
541 Whether to track. Only for use with Sockets, because ZMQStream
540 Whether to track. Only for use with Sockets, because ZMQStream
542 objects cannot track messages.
541 objects cannot track messages.
543
542
544
543
545 Returns
544 Returns
546 -------
545 -------
547 msg : dict
546 msg : dict
548 The constructed message.
547 The constructed message.
549 (msg,tracker) : (dict, MessageTracker)
548 (msg,tracker) : (dict, MessageTracker)
550 if track=True, then a 2-tuple will be returned,
549 if track=True, then a 2-tuple will be returned,
551 the first element being the constructed
550 the first element being the constructed
552 message, and the second being the MessageTracker
551 message, and the second being the MessageTracker
553
552
554 """
553 """
555
554
556 if not isinstance(stream, (zmq.Socket, ZMQStream)):
555 if not isinstance(stream, (zmq.Socket, ZMQStream)):
557 raise TypeError("stream must be Socket or ZMQStream, not %r"%type(stream))
556 raise TypeError("stream must be Socket or ZMQStream, not %r"%type(stream))
558 elif track and isinstance(stream, ZMQStream):
557 elif track and isinstance(stream, ZMQStream):
559 raise TypeError("ZMQStream cannot track messages")
558 raise TypeError("ZMQStream cannot track messages")
560
559
561 if isinstance(msg_or_type, (Message, dict)):
560 if isinstance(msg_or_type, (Message, dict)):
562 # We got a Message or message dict, not a msg_type so don't
561 # We got a Message or message dict, not a msg_type so don't
563 # build a new Message.
562 # build a new Message.
564 msg = msg_or_type
563 msg = msg_or_type
565 else:
564 else:
566 msg = self.msg(msg_or_type, content=content, parent=parent,
565 msg = self.msg(msg_or_type, content=content, parent=parent,
567 subheader=subheader, header=header, metadata=metadata)
566 subheader=subheader, header=header, metadata=metadata)
568
567
569 buffers = [] if buffers is None else buffers
568 buffers = [] if buffers is None else buffers
570 to_send = self.serialize(msg, ident)
569 to_send = self.serialize(msg, ident)
571 flag = 0
570 flag = 0
572 if buffers:
571 if buffers:
573 flag = zmq.SNDMORE
572 flag = zmq.SNDMORE
574 _track = False
573 _track = False
575 else:
574 else:
576 _track=track
575 _track=track
577 if track:
576 if track:
578 tracker = stream.send_multipart(to_send, flag, copy=False, track=_track)
577 tracker = stream.send_multipart(to_send, flag, copy=False, track=_track)
579 else:
578 else:
580 tracker = stream.send_multipart(to_send, flag, copy=False)
579 tracker = stream.send_multipart(to_send, flag, copy=False)
581 for b in buffers[:-1]:
580 for b in buffers[:-1]:
582 stream.send(b, flag, copy=False)
581 stream.send(b, flag, copy=False)
583 if buffers:
582 if buffers:
584 if track:
583 if track:
585 tracker = stream.send(buffers[-1], copy=False, track=track)
584 tracker = stream.send(buffers[-1], copy=False, track=track)
586 else:
585 else:
587 tracker = stream.send(buffers[-1], copy=False)
586 tracker = stream.send(buffers[-1], copy=False)
588
587
589 # omsg = Message(msg)
588 # omsg = Message(msg)
590 if self.debug:
589 if self.debug:
591 pprint.pprint(msg)
590 pprint.pprint(msg)
592 pprint.pprint(to_send)
591 pprint.pprint(to_send)
593 pprint.pprint(buffers)
592 pprint.pprint(buffers)
594
593
595 msg['tracker'] = tracker
594 msg['tracker'] = tracker
596
595
597 return msg
596 return msg
598
597
599 def send_raw(self, stream, msg_list, flags=0, copy=True, ident=None):
598 def send_raw(self, stream, msg_list, flags=0, copy=True, ident=None):
600 """Send a raw message via ident path.
599 """Send a raw message via ident path.
601
600
602 This method is used to send a already serialized message.
601 This method is used to send a already serialized message.
603
602
604 Parameters
603 Parameters
605 ----------
604 ----------
606 stream : ZMQStream or Socket
605 stream : ZMQStream or Socket
607 The ZMQ stream or socket to use for sending the message.
606 The ZMQ stream or socket to use for sending the message.
608 msg_list : list
607 msg_list : list
609 The serialized list of messages to send. This only includes the
608 The serialized list of messages to send. This only includes the
610 [p_header,p_parent,p_content,buffer1,buffer2,...] portion of
609 [p_header,p_parent,p_metadata,p_content,buffer1,buffer2,...] portion of
611 the message.
610 the message.
612 ident : ident or list
611 ident : ident or list
613 A single ident or a list of idents to use in sending.
612 A single ident or a list of idents to use in sending.
614 """
613 """
615 to_send = []
614 to_send = []
616 if isinstance(ident, bytes):
615 if isinstance(ident, bytes):
617 ident = [ident]
616 ident = [ident]
618 if ident is not None:
617 if ident is not None:
619 to_send.extend(ident)
618 to_send.extend(ident)
620
619
621 to_send.append(DELIM)
620 to_send.append(DELIM)
622 to_send.append(self.sign(msg_list))
621 to_send.append(self.sign(msg_list))
623 to_send.extend(msg_list)
622 to_send.extend(msg_list)
624 stream.send_multipart(msg_list, flags, copy=copy)
623 stream.send_multipart(msg_list, flags, copy=copy)
625
624
626 def recv(self, socket, mode=zmq.NOBLOCK, content=True, copy=True):
625 def recv(self, socket, mode=zmq.NOBLOCK, content=True, copy=True):
627 """Receive and unpack a message.
626 """Receive and unpack a message.
628
627
629 Parameters
628 Parameters
630 ----------
629 ----------
631 socket : ZMQStream or Socket
630 socket : ZMQStream or Socket
632 The socket or stream to use in receiving.
631 The socket or stream to use in receiving.
633
632
634 Returns
633 Returns
635 -------
634 -------
636 [idents], msg
635 [idents], msg
637 [idents] is a list of idents and msg is a nested message dict of
636 [idents] is a list of idents and msg is a nested message dict of
638 same format as self.msg returns.
637 same format as self.msg returns.
639 """
638 """
640 if isinstance(socket, ZMQStream):
639 if isinstance(socket, ZMQStream):
641 socket = socket.socket
640 socket = socket.socket
642 try:
641 try:
643 msg_list = socket.recv_multipart(mode, copy=copy)
642 msg_list = socket.recv_multipart(mode, copy=copy)
644 except zmq.ZMQError as e:
643 except zmq.ZMQError as e:
645 if e.errno == zmq.EAGAIN:
644 if e.errno == zmq.EAGAIN:
646 # We can convert EAGAIN to None as we know in this case
645 # We can convert EAGAIN to None as we know in this case
647 # recv_multipart won't return None.
646 # recv_multipart won't return None.
648 return None,None
647 return None,None
649 else:
648 else:
650 raise
649 raise
651 # split multipart message into identity list and message dict
650 # split multipart message into identity list and message dict
652 # invalid large messages can cause very expensive string comparisons
651 # invalid large messages can cause very expensive string comparisons
653 idents, msg_list = self.feed_identities(msg_list, copy)
652 idents, msg_list = self.feed_identities(msg_list, copy)
654 try:
653 try:
655 return idents, self.unserialize(msg_list, content=content, copy=copy)
654 return idents, self.unserialize(msg_list, content=content, copy=copy)
656 except Exception as e:
655 except Exception as e:
657 # TODO: handle it
656 # TODO: handle it
658 raise e
657 raise e
659
658
660 def feed_identities(self, msg_list, copy=True):
659 def feed_identities(self, msg_list, copy=True):
661 """Split the identities from the rest of the message.
660 """Split the identities from the rest of the message.
662
661
663 Feed until DELIM is reached, then return the prefix as idents and
662 Feed until DELIM is reached, then return the prefix as idents and
664 remainder as msg_list. This is easily broken by setting an IDENT to DELIM,
663 remainder as msg_list. This is easily broken by setting an IDENT to DELIM,
665 but that would be silly.
664 but that would be silly.
666
665
667 Parameters
666 Parameters
668 ----------
667 ----------
669 msg_list : a list of Message or bytes objects
668 msg_list : a list of Message or bytes objects
670 The message to be split.
669 The message to be split.
671 copy : bool
670 copy : bool
672 flag determining whether the arguments are bytes or Messages
671 flag determining whether the arguments are bytes or Messages
673
672
674 Returns
673 Returns
675 -------
674 -------
676 (idents, msg_list) : two lists
675 (idents, msg_list) : two lists
677 idents will always be a list of bytes, each of which is a ZMQ
676 idents will always be a list of bytes, each of which is a ZMQ
678 identity. msg_list will be a list of bytes or zmq.Messages of the
677 identity. msg_list will be a list of bytes or zmq.Messages of the
679 form [HMAC,p_header,p_parent,p_content,buffer1,buffer2,...] and
678 form [HMAC,p_header,p_parent,p_content,buffer1,buffer2,...] and
680 should be unpackable/unserializable via self.unserialize at this
679 should be unpackable/unserializable via self.unserialize at this
681 point.
680 point.
682 """
681 """
683 if copy:
682 if copy:
684 idx = msg_list.index(DELIM)
683 idx = msg_list.index(DELIM)
685 return msg_list[:idx], msg_list[idx+1:]
684 return msg_list[:idx], msg_list[idx+1:]
686 else:
685 else:
687 failed = True
686 failed = True
688 for idx,m in enumerate(msg_list):
687 for idx,m in enumerate(msg_list):
689 if m.bytes == DELIM:
688 if m.bytes == DELIM:
690 failed = False
689 failed = False
691 break
690 break
692 if failed:
691 if failed:
693 raise ValueError("DELIM not in msg_list")
692 raise ValueError("DELIM not in msg_list")
694 idents, msg_list = msg_list[:idx], msg_list[idx+1:]
693 idents, msg_list = msg_list[:idx], msg_list[idx+1:]
695 return [m.bytes for m in idents], msg_list
694 return [m.bytes for m in idents], msg_list
696
695
697 def unserialize(self, msg_list, content=True, copy=True):
696 def unserialize(self, msg_list, content=True, copy=True):
698 """Unserialize a msg_list to a nested message dict.
697 """Unserialize a msg_list to a nested message dict.
699
698
700 This is roughly the inverse of serialize. The serialize/unserialize
699 This is roughly the inverse of serialize. The serialize/unserialize
701 methods work with full message lists, whereas pack/unpack work with
700 methods work with full message lists, whereas pack/unpack work with
702 the individual message parts in the message list.
701 the individual message parts in the message list.
703
702
704 Parameters:
703 Parameters:
705 -----------
704 -----------
706 msg_list : list of bytes or Message objects
705 msg_list : list of bytes or Message objects
707 The list of message parts of the form [HMAC,p_header,p_parent,
706 The list of message parts of the form [HMAC,p_header,p_parent,
708 p_content,buffer1,buffer2,...].
707 p_metadata,p_content,buffer1,buffer2,...].
709 content : bool (True)
708 content : bool (True)
710 Whether to unpack the content dict (True), or leave it packed
709 Whether to unpack the content dict (True), or leave it packed
711 (False).
710 (False).
712 copy : bool (True)
711 copy : bool (True)
713 Whether to return the bytes (True), or the non-copying Message
712 Whether to return the bytes (True), or the non-copying Message
714 object in each place (False).
713 object in each place (False).
715
714
716 Returns
715 Returns
717 -------
716 -------
718 msg : dict
717 msg : dict
719 The nested message dict with top-level keys [header, parent_header,
718 The nested message dict with top-level keys [header, parent_header,
720 content, buffers].
719 content, buffers].
721 """
720 """
722 minlen = 4
721 minlen = 5
723 message = {}
722 message = {}
724 if not copy:
723 if not copy:
725 for i in range(minlen):
724 for i in range(minlen):
726 msg_list[i] = msg_list[i].bytes
725 msg_list[i] = msg_list[i].bytes
727 if self.auth is not None:
726 if self.auth is not None:
728 signature = msg_list[0]
727 signature = msg_list[0]
729 if not signature:
728 if not signature:
730 raise ValueError("Unsigned Message")
729 raise ValueError("Unsigned Message")
731 if signature in self.digest_history:
730 if signature in self.digest_history:
732 raise ValueError("Duplicate Signature: %r"%signature)
731 raise ValueError("Duplicate Signature: %r"%signature)
733 self.digest_history.add(signature)
732 self.digest_history.add(signature)
734 check = self.sign(msg_list[1:4])
733 check = self.sign(msg_list[1:4])
735 if not signature == check:
734 if not signature == check:
736 raise ValueError("Invalid Signature: %r"%signature)
735 raise ValueError("Invalid Signature: %r"%signature)
737 if not len(msg_list) >= minlen:
736 if not len(msg_list) >= minlen:
738 raise TypeError("malformed message, must have at least %i elements"%minlen)
737 raise TypeError("malformed message, must have at least %i elements"%minlen)
739 header = self.unpack(msg_list[1])
738 header = self.unpack(msg_list[1])
740 message['header'] = header
739 message['header'] = header
741 message['msg_id'] = header['msg_id']
740 message['msg_id'] = header['msg_id']
742 message['msg_type'] = header['msg_type']
741 message['msg_type'] = header['msg_type']
743 message['parent_header'] = self.unpack(msg_list[2])
742 message['parent_header'] = self.unpack(msg_list[2])
743 message['metadata'] = self.unpack(msg_list[3])
744 if content:
744 if content:
745 message['content'] = self.unpack(msg_list[3])
745 message['content'] = self.unpack(msg_list[4])
746 else:
746 else:
747 message['content'] = msg_list[3]
747 message['content'] = msg_list[4]
748
748
749 message['buffers'] = msg_list[4:]
749 message['buffers'] = msg_list[5:]
750 return message
750 return message
751
751
752 def test_msg2obj():
752 def test_msg2obj():
753 am = dict(x=1)
753 am = dict(x=1)
754 ao = Message(am)
754 ao = Message(am)
755 assert ao.x == am['x']
755 assert ao.x == am['x']
756
756
757 am['y'] = dict(z=1)
757 am['y'] = dict(z=1)
758 ao = Message(am)
758 ao = Message(am)
759 assert ao.y.z == am['y']['z']
759 assert ao.y.z == am['y']['z']
760
760
761 k1, k2 = 'y', 'z'
761 k1, k2 = 'y', 'z'
762 assert ao[k1][k2] == am[k1][k2]
762 assert ao[k1][k2] == am[k1][k2]
763
763
764 am2 = dict(ao)
764 am2 = dict(ao)
765 assert am['x'] == am2['x']
765 assert am['x'] == am2['x']
766 assert am['y']['z'] == am2['y']['z']
766 assert am['y']['z'] == am2['y']['z']
767
767
@@ -1,212 +1,218 b''
1 """test building messages with streamsession"""
1 """test building messages with streamsession"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2011 The IPython Development Team
4 # Copyright (C) 2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 import os
14 import os
15 import uuid
15 import uuid
16 import zmq
16 import zmq
17
17
18 from zmq.tests import BaseZMQTestCase
18 from zmq.tests import BaseZMQTestCase
19 from zmq.eventloop.zmqstream import ZMQStream
19 from zmq.eventloop.zmqstream import ZMQStream
20
20
21 from IPython.zmq import session as ss
21 from IPython.zmq import session as ss
22
22
23 class SessionTestCase(BaseZMQTestCase):
23 class SessionTestCase(BaseZMQTestCase):
24
24
25 def setUp(self):
25 def setUp(self):
26 BaseZMQTestCase.setUp(self)
26 BaseZMQTestCase.setUp(self)
27 self.session = ss.Session()
27 self.session = ss.Session()
28
28
29
29
30 class MockSocket(zmq.Socket):
30 class MockSocket(zmq.Socket):
31
31
32 def __init__(self, *args, **kwargs):
32 def __init__(self, *args, **kwargs):
33 super(MockSocket,self).__init__(*args,**kwargs)
33 super(MockSocket,self).__init__(*args,**kwargs)
34 self.data = []
34 self.data = []
35
35
36 def send_multipart(self, msgparts, *args, **kwargs):
36 def send_multipart(self, msgparts, *args, **kwargs):
37 self.data.extend(msgparts)
37 self.data.extend(msgparts)
38
38
39 def send(self, part, *args, **kwargs):
39 def send(self, part, *args, **kwargs):
40 self.data.append(part)
40 self.data.append(part)
41
41
42 def recv_multipart(self, *args, **kwargs):
42 def recv_multipart(self, *args, **kwargs):
43 return self.data
43 return self.data
44
44
45 class TestSession(SessionTestCase):
45 class TestSession(SessionTestCase):
46
46
47 def test_msg(self):
47 def test_msg(self):
48 """message format"""
48 """message format"""
49 msg = self.session.msg('execute')
49 msg = self.session.msg('execute')
50 thekeys = set('header parent_header content msg_type msg_id'.split())
50 thekeys = set('header parent_header metadata content msg_type msg_id'.split())
51 s = set(msg.keys())
51 s = set(msg.keys())
52 self.assertEqual(s, thekeys)
52 self.assertEqual(s, thekeys)
53 self.assertTrue(isinstance(msg['content'],dict))
53 self.assertTrue(isinstance(msg['content'],dict))
54 self.assertTrue(isinstance(msg['metadata'],dict))
54 self.assertTrue(isinstance(msg['header'],dict))
55 self.assertTrue(isinstance(msg['header'],dict))
55 self.assertTrue(isinstance(msg['parent_header'],dict))
56 self.assertTrue(isinstance(msg['parent_header'],dict))
56 self.assertTrue(isinstance(msg['msg_id'],str))
57 self.assertTrue(isinstance(msg['msg_id'],str))
57 self.assertTrue(isinstance(msg['msg_type'],str))
58 self.assertTrue(isinstance(msg['msg_type'],str))
58 self.assertEqual(msg['header']['msg_type'], 'execute')
59 self.assertEqual(msg['header']['msg_type'], 'execute')
59 self.assertEqual(msg['msg_type'], 'execute')
60 self.assertEqual(msg['msg_type'], 'execute')
60
61
61 def test_serialize(self):
62 def test_serialize(self):
62 msg = self.session.msg('execute', content=dict(a=10, b=1.1))
63 msg = self.session.msg('execute', content=dict(a=10, b=1.1))
63 msg_list = self.session.serialize(msg, ident=b'foo')
64 msg_list = self.session.serialize(msg, ident=b'foo')
64 ident, msg_list = self.session.feed_identities(msg_list)
65 ident, msg_list = self.session.feed_identities(msg_list)
65 new_msg = self.session.unserialize(msg_list)
66 new_msg = self.session.unserialize(msg_list)
66 self.assertEqual(ident[0], b'foo')
67 self.assertEqual(ident[0], b'foo')
67 self.assertEqual(new_msg['msg_id'],msg['msg_id'])
68 self.assertEqual(new_msg['msg_id'],msg['msg_id'])
68 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
69 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
69 self.assertEqual(new_msg['header'],msg['header'])
70 self.assertEqual(new_msg['header'],msg['header'])
70 self.assertEqual(new_msg['content'],msg['content'])
71 self.assertEqual(new_msg['content'],msg['content'])
71 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
72 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
73 self.assertEqual(new_msg['metadata'],msg['metadata'])
72 # ensure floats don't come out as Decimal:
74 # ensure floats don't come out as Decimal:
73 self.assertEqual(type(new_msg['content']['b']),type(new_msg['content']['b']))
75 self.assertEqual(type(new_msg['content']['b']),type(new_msg['content']['b']))
74
76
75 def test_send(self):
77 def test_send(self):
76 socket = MockSocket(zmq.Context.instance(),zmq.PAIR)
78 socket = MockSocket(zmq.Context.instance(),zmq.PAIR)
77
79
78 msg = self.session.msg('execute', content=dict(a=10))
80 msg = self.session.msg('execute', content=dict(a=10))
79 self.session.send(socket, msg, ident=b'foo', buffers=[b'bar'])
81 self.session.send(socket, msg, ident=b'foo', buffers=[b'bar'])
80 ident, msg_list = self.session.feed_identities(socket.data)
82 ident, msg_list = self.session.feed_identities(socket.data)
81 new_msg = self.session.unserialize(msg_list)
83 new_msg = self.session.unserialize(msg_list)
82 self.assertEqual(ident[0], b'foo')
84 self.assertEqual(ident[0], b'foo')
83 self.assertEqual(new_msg['msg_id'],msg['msg_id'])
85 self.assertEqual(new_msg['msg_id'],msg['msg_id'])
84 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
86 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
85 self.assertEqual(new_msg['header'],msg['header'])
87 self.assertEqual(new_msg['header'],msg['header'])
86 self.assertEqual(new_msg['content'],msg['content'])
88 self.assertEqual(new_msg['content'],msg['content'])
87 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
89 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
90 self.assertEqual(new_msg['metadata'],msg['metadata'])
88 self.assertEqual(new_msg['buffers'],[b'bar'])
91 self.assertEqual(new_msg['buffers'],[b'bar'])
89
92
90 socket.data = []
93 socket.data = []
91
94
92 content = msg['content']
95 content = msg['content']
93 header = msg['header']
96 header = msg['header']
94 parent = msg['parent_header']
97 parent = msg['parent_header']
98 metadata = msg['metadata']
95 msg_type = header['msg_type']
99 msg_type = header['msg_type']
96 self.session.send(socket, None, content=content, parent=parent,
100 self.session.send(socket, None, content=content, parent=parent,
97 header=header, ident=b'foo', buffers=[b'bar'])
101 header=header, metadata=metadata, ident=b'foo', buffers=[b'bar'])
98 ident, msg_list = self.session.feed_identities(socket.data)
102 ident, msg_list = self.session.feed_identities(socket.data)
99 new_msg = self.session.unserialize(msg_list)
103 new_msg = self.session.unserialize(msg_list)
100 self.assertEqual(ident[0], b'foo')
104 self.assertEqual(ident[0], b'foo')
101 self.assertEqual(new_msg['msg_id'],msg['msg_id'])
105 self.assertEqual(new_msg['msg_id'],msg['msg_id'])
102 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
106 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
103 self.assertEqual(new_msg['header'],msg['header'])
107 self.assertEqual(new_msg['header'],msg['header'])
104 self.assertEqual(new_msg['content'],msg['content'])
108 self.assertEqual(new_msg['content'],msg['content'])
109 self.assertEqual(new_msg['metadata'],msg['metadata'])
105 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
110 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
106 self.assertEqual(new_msg['buffers'],[b'bar'])
111 self.assertEqual(new_msg['buffers'],[b'bar'])
107
112
108 socket.data = []
113 socket.data = []
109
114
110 self.session.send(socket, msg, ident=b'foo', buffers=[b'bar'])
115 self.session.send(socket, msg, ident=b'foo', buffers=[b'bar'])
111 ident, new_msg = self.session.recv(socket)
116 ident, new_msg = self.session.recv(socket)
112 self.assertEqual(ident[0], b'foo')
117 self.assertEqual(ident[0], b'foo')
113 self.assertEqual(new_msg['msg_id'],msg['msg_id'])
118 self.assertEqual(new_msg['msg_id'],msg['msg_id'])
114 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
119 self.assertEqual(new_msg['msg_type'],msg['msg_type'])
115 self.assertEqual(new_msg['header'],msg['header'])
120 self.assertEqual(new_msg['header'],msg['header'])
116 self.assertEqual(new_msg['content'],msg['content'])
121 self.assertEqual(new_msg['content'],msg['content'])
122 self.assertEqual(new_msg['metadata'],msg['metadata'])
117 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
123 self.assertEqual(new_msg['parent_header'],msg['parent_header'])
118 self.assertEqual(new_msg['buffers'],[b'bar'])
124 self.assertEqual(new_msg['buffers'],[b'bar'])
119
125
120 socket.close()
126 socket.close()
121
127
122 def test_args(self):
128 def test_args(self):
123 """initialization arguments for Session"""
129 """initialization arguments for Session"""
124 s = self.session
130 s = self.session
125 self.assertTrue(s.pack is ss.default_packer)
131 self.assertTrue(s.pack is ss.default_packer)
126 self.assertTrue(s.unpack is ss.default_unpacker)
132 self.assertTrue(s.unpack is ss.default_unpacker)
127 self.assertEqual(s.username, os.environ.get('USER', u'username'))
133 self.assertEqual(s.username, os.environ.get('USER', u'username'))
128
134
129 s = ss.Session()
135 s = ss.Session()
130 self.assertEqual(s.username, os.environ.get('USER', u'username'))
136 self.assertEqual(s.username, os.environ.get('USER', u'username'))
131
137
132 self.assertRaises(TypeError, ss.Session, pack='hi')
138 self.assertRaises(TypeError, ss.Session, pack='hi')
133 self.assertRaises(TypeError, ss.Session, unpack='hi')
139 self.assertRaises(TypeError, ss.Session, unpack='hi')
134 u = str(uuid.uuid4())
140 u = str(uuid.uuid4())
135 s = ss.Session(username=u'carrot', session=u)
141 s = ss.Session(username=u'carrot', session=u)
136 self.assertEqual(s.session, u)
142 self.assertEqual(s.session, u)
137 self.assertEqual(s.username, u'carrot')
143 self.assertEqual(s.username, u'carrot')
138
144
139 def test_tracking(self):
145 def test_tracking(self):
140 """test tracking messages"""
146 """test tracking messages"""
141 a,b = self.create_bound_pair(zmq.PAIR, zmq.PAIR)
147 a,b = self.create_bound_pair(zmq.PAIR, zmq.PAIR)
142 s = self.session
148 s = self.session
143 stream = ZMQStream(a)
149 stream = ZMQStream(a)
144 msg = s.send(a, 'hello', track=False)
150 msg = s.send(a, 'hello', track=False)
145 self.assertTrue(msg['tracker'] is None)
151 self.assertTrue(msg['tracker'] is None)
146 msg = s.send(a, 'hello', track=True)
152 msg = s.send(a, 'hello', track=True)
147 self.assertTrue(isinstance(msg['tracker'], zmq.MessageTracker))
153 self.assertTrue(isinstance(msg['tracker'], zmq.MessageTracker))
148 M = zmq.Message(b'hi there', track=True)
154 M = zmq.Message(b'hi there', track=True)
149 msg = s.send(a, 'hello', buffers=[M], track=True)
155 msg = s.send(a, 'hello', buffers=[M], track=True)
150 t = msg['tracker']
156 t = msg['tracker']
151 self.assertTrue(isinstance(t, zmq.MessageTracker))
157 self.assertTrue(isinstance(t, zmq.MessageTracker))
152 self.assertRaises(zmq.NotDone, t.wait, .1)
158 self.assertRaises(zmq.NotDone, t.wait, .1)
153 del M
159 del M
154 t.wait(1) # this will raise
160 t.wait(1) # this will raise
155
161
156
162
157 # def test_rekey(self):
163 # def test_rekey(self):
158 # """rekeying dict around json str keys"""
164 # """rekeying dict around json str keys"""
159 # d = {'0': uuid.uuid4(), 0:uuid.uuid4()}
165 # d = {'0': uuid.uuid4(), 0:uuid.uuid4()}
160 # self.assertRaises(KeyError, ss.rekey, d)
166 # self.assertRaises(KeyError, ss.rekey, d)
161 #
167 #
162 # d = {'0': uuid.uuid4(), 1:uuid.uuid4(), 'asdf':uuid.uuid4()}
168 # d = {'0': uuid.uuid4(), 1:uuid.uuid4(), 'asdf':uuid.uuid4()}
163 # d2 = {0:d['0'],1:d[1],'asdf':d['asdf']}
169 # d2 = {0:d['0'],1:d[1],'asdf':d['asdf']}
164 # rd = ss.rekey(d)
170 # rd = ss.rekey(d)
165 # self.assertEqual(d2,rd)
171 # self.assertEqual(d2,rd)
166 #
172 #
167 # d = {'1.5':uuid.uuid4(),'1':uuid.uuid4()}
173 # d = {'1.5':uuid.uuid4(),'1':uuid.uuid4()}
168 # d2 = {1.5:d['1.5'],1:d['1']}
174 # d2 = {1.5:d['1.5'],1:d['1']}
169 # rd = ss.rekey(d)
175 # rd = ss.rekey(d)
170 # self.assertEqual(d2,rd)
176 # self.assertEqual(d2,rd)
171 #
177 #
172 # d = {'1.0':uuid.uuid4(),'1':uuid.uuid4()}
178 # d = {'1.0':uuid.uuid4(),'1':uuid.uuid4()}
173 # self.assertRaises(KeyError, ss.rekey, d)
179 # self.assertRaises(KeyError, ss.rekey, d)
174 #
180 #
175 def test_unique_msg_ids(self):
181 def test_unique_msg_ids(self):
176 """test that messages receive unique ids"""
182 """test that messages receive unique ids"""
177 ids = set()
183 ids = set()
178 for i in range(2**12):
184 for i in range(2**12):
179 h = self.session.msg_header('test')
185 h = self.session.msg_header('test')
180 msg_id = h['msg_id']
186 msg_id = h['msg_id']
181 self.assertTrue(msg_id not in ids)
187 self.assertTrue(msg_id not in ids)
182 ids.add(msg_id)
188 ids.add(msg_id)
183
189
184 def test_feed_identities(self):
190 def test_feed_identities(self):
185 """scrub the front for zmq IDENTITIES"""
191 """scrub the front for zmq IDENTITIES"""
186 theids = "engine client other".split()
192 theids = "engine client other".split()
187 content = dict(code='whoda',stuff=object())
193 content = dict(code='whoda',stuff=object())
188 themsg = self.session.msg('execute',content=content)
194 themsg = self.session.msg('execute',content=content)
189 pmsg = theids
195 pmsg = theids
190
196
191 def test_session_id(self):
197 def test_session_id(self):
192 session = ss.Session()
198 session = ss.Session()
193 # get bs before us
199 # get bs before us
194 bs = session.bsession
200 bs = session.bsession
195 us = session.session
201 us = session.session
196 self.assertEqual(us.encode('ascii'), bs)
202 self.assertEqual(us.encode('ascii'), bs)
197 session = ss.Session()
203 session = ss.Session()
198 # get us before bs
204 # get us before bs
199 us = session.session
205 us = session.session
200 bs = session.bsession
206 bs = session.bsession
201 self.assertEqual(us.encode('ascii'), bs)
207 self.assertEqual(us.encode('ascii'), bs)
202 # change propagates:
208 # change propagates:
203 session.session = 'something else'
209 session.session = 'something else'
204 bs = session.bsession
210 bs = session.bsession
205 us = session.session
211 us = session.session
206 self.assertEqual(us.encode('ascii'), bs)
212 self.assertEqual(us.encode('ascii'), bs)
207 session = ss.Session(session='stuff')
213 session = ss.Session(session='stuff')
208 # get us before bs
214 # get us before bs
209 self.assertEqual(session.bsession, session.session.encode('ascii'))
215 self.assertEqual(session.bsession, session.session.encode('ascii'))
210 self.assertEqual(b'stuff', session.bsession)
216 self.assertEqual(b'stuff', session.bsession)
211
217
212
218
@@ -1,959 +1,959 b''
1 .. _messaging:
1 .. _messaging:
2
2
3 ======================
3 ======================
4 Messaging in IPython
4 Messaging in IPython
5 ======================
5 ======================
6
6
7
7
8 Introduction
8 Introduction
9 ============
9 ============
10
10
11 This document explains the basic communications design and messaging
11 This document explains the basic communications design and messaging
12 specification for how the various IPython objects interact over a network
12 specification for how the various IPython objects interact over a network
13 transport. The current implementation uses the ZeroMQ_ library for messaging
13 transport. The current implementation uses the ZeroMQ_ library for messaging
14 within and between hosts.
14 within and between hosts.
15
15
16 .. Note::
16 .. Note::
17
17
18 This document should be considered the authoritative description of the
18 This document should be considered the authoritative description of the
19 IPython messaging protocol, and all developers are strongly encouraged to
19 IPython messaging protocol, and all developers are strongly encouraged to
20 keep it updated as the implementation evolves, so that we have a single
20 keep it updated as the implementation evolves, so that we have a single
21 common reference for all protocol details.
21 common reference for all protocol details.
22
22
23 The basic design is explained in the following diagram:
23 The basic design is explained in the following diagram:
24
24
25 .. image:: figs/frontend-kernel.png
25 .. image:: figs/frontend-kernel.png
26 :width: 450px
26 :width: 450px
27 :alt: IPython kernel/frontend messaging architecture.
27 :alt: IPython kernel/frontend messaging architecture.
28 :align: center
28 :align: center
29 :target: ../_images/frontend-kernel.png
29 :target: ../_images/frontend-kernel.png
30
30
31 A single kernel can be simultaneously connected to one or more frontends. The
31 A single kernel can be simultaneously connected to one or more frontends. The
32 kernel has three sockets that serve the following functions:
32 kernel has three sockets that serve the following functions:
33
33
34 1. stdin: this ROUTER socket is connected to all frontends, and it allows
34 1. stdin: this ROUTER socket is connected to all frontends, and it allows
35 the kernel to request input from the active frontend when :func:`raw_input` is called.
35 the kernel to request input from the active frontend when :func:`raw_input` is called.
36 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
36 The frontend that executed the code has a DEALER socket that acts as a 'virtual keyboard'
37 for the kernel while this communication is happening (illustrated in the
37 for the kernel while this communication is happening (illustrated in the
38 figure by the black outline around the central keyboard). In practice,
38 figure by the black outline around the central keyboard). In practice,
39 frontends may display such kernel requests using a special input widget or
39 frontends may display such kernel requests using a special input widget or
40 otherwise indicating that the user is to type input for the kernel instead
40 otherwise indicating that the user is to type input for the kernel instead
41 of normal commands in the frontend.
41 of normal commands in the frontend.
42
42
43 2. Shell: this single ROUTER socket allows multiple incoming connections from
43 2. Shell: this single ROUTER socket allows multiple incoming connections from
44 frontends, and this is the socket where requests for code execution, object
44 frontends, and this is the socket where requests for code execution, object
45 information, prompts, etc. are made to the kernel by any frontend. The
45 information, prompts, etc. are made to the kernel by any frontend. The
46 communication on this socket is a sequence of request/reply actions from
46 communication on this socket is a sequence of request/reply actions from
47 each frontend and the kernel.
47 each frontend and the kernel.
48
48
49 3. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
49 3. IOPub: this socket is the 'broadcast channel' where the kernel publishes all
50 side effects (stdout, stderr, etc.) as well as the requests coming from any
50 side effects (stdout, stderr, etc.) as well as the requests coming from any
51 client over the shell socket and its own requests on the stdin socket. There
51 client over the shell socket and its own requests on the stdin socket. There
52 are a number of actions in Python which generate side effects: :func:`print`
52 are a number of actions in Python which generate side effects: :func:`print`
53 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
53 writes to ``sys.stdout``, errors generate tracebacks, etc. Additionally, in
54 a multi-client scenario, we want all frontends to be able to know what each
54 a multi-client scenario, we want all frontends to be able to know what each
55 other has sent to the kernel (this can be useful in collaborative scenarios,
55 other has sent to the kernel (this can be useful in collaborative scenarios,
56 for example). This socket allows both side effects and the information
56 for example). This socket allows both side effects and the information
57 about communications taking place with one client over the shell channel
57 about communications taking place with one client over the shell channel
58 to be made available to all clients in a uniform manner.
58 to be made available to all clients in a uniform manner.
59
59
60 All messages are tagged with enough information (details below) for clients
60 All messages are tagged with enough information (details below) for clients
61 to know which messages come from their own interaction with the kernel and
61 to know which messages come from their own interaction with the kernel and
62 which ones are from other clients, so they can display each type
62 which ones are from other clients, so they can display each type
63 appropriately.
63 appropriately.
64
64
65 The actual format of the messages allowed on each of these channels is
65 The actual format of the messages allowed on each of these channels is
66 specified below. Messages are dicts of dicts with string keys and values that
66 specified below. Messages are dicts of dicts with string keys and values that
67 are reasonably representable in JSON. Our current implementation uses JSON
67 are reasonably representable in JSON. Our current implementation uses JSON
68 explicitly as its message format, but this shouldn't be considered a permanent
68 explicitly as its message format, but this shouldn't be considered a permanent
69 feature. As we've discovered that JSON has non-trivial performance issues due
69 feature. As we've discovered that JSON has non-trivial performance issues due
70 to excessive copying, we may in the future move to a pure pickle-based raw
70 to excessive copying, we may in the future move to a pure pickle-based raw
71 message format. However, it should be possible to easily convert from the raw
71 message format. However, it should be possible to easily convert from the raw
72 objects to JSON, since we may have non-python clients (e.g. a web frontend).
72 objects to JSON, since we may have non-python clients (e.g. a web frontend).
73 As long as it's easy to make a JSON version of the objects that is a faithful
73 As long as it's easy to make a JSON version of the objects that is a faithful
74 representation of all the data, we can communicate with such clients.
74 representation of all the data, we can communicate with such clients.
75
75
76 .. Note::
76 .. Note::
77
77
78 Not all of these have yet been fully fleshed out, but the key ones are, see
78 Not all of these have yet been fully fleshed out, but the key ones are, see
79 kernel and frontend files for actual implementation details.
79 kernel and frontend files for actual implementation details.
80
80
81 General Message Format
81 General Message Format
82 ======================
82 ======================
83
83
84 A message is defined by the following four-dictionary structure::
84 A message is defined by the following four-dictionary structure::
85
85
86 {
86 {
87 # The message header contains a pair of unique identifiers for the
87 # The message header contains a pair of unique identifiers for the
88 # originating session and the actual message id, in addition to the
88 # originating session and the actual message id, in addition to the
89 # username for the process that generated the message. This is useful in
89 # username for the process that generated the message. This is useful in
90 # collaborative settings where multiple users may be interacting with the
90 # collaborative settings where multiple users may be interacting with the
91 # same kernel simultaneously, so that frontends can label the various
91 # same kernel simultaneously, so that frontends can label the various
92 # messages in a meaningful way.
92 # messages in a meaningful way.
93 'header' : {
93 'header' : {
94 'msg_id' : uuid,
94 'msg_id' : uuid,
95 'username' : str,
95 'username' : str,
96 'session' : uuid
96 'session' : uuid
97 # All recognized message type strings are listed below.
97 # All recognized message type strings are listed below.
98 'msg_type' : str,
98 'msg_type' : str,
99 },
99 },
100
100
101 # In a chain of messages, the header from the parent is copied so that
101 # In a chain of messages, the header from the parent is copied so that
102 # clients can track where messages come from.
102 # clients can track where messages come from.
103 'parent_header' : dict,
103 'parent_header' : dict,
104
104
105 # The actual content of the message must be a dict, whose structure
105 # The actual content of the message must be a dict, whose structure
106 # depends on the message type.
106 # depends on the message type.
107 'content' : dict,
107 'content' : dict,
108
108
109 # Any metadata associated with the message; this dictionary is optional.
109 # Any metadata associated with the message.
110 'metadata' : dict,
110 'metadata' : dict,
111 }
111 }
112
112
113
113
114 Python functional API
114 Python functional API
115 =====================
115 =====================
116
116
117 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
117 As messages are dicts, they map naturally to a ``func(**kw)`` call form. We
118 should develop, at a few key points, functional forms of all the requests that
118 should develop, at a few key points, functional forms of all the requests that
119 take arguments in this manner and automatically construct the necessary dict
119 take arguments in this manner and automatically construct the necessary dict
120 for sending.
120 for sending.
121
121
122 In addition, the Python implementation of the message specification extends
122 In addition, the Python implementation of the message specification extends
123 messages upon deserialization to the following form for convenience::
123 messages upon deserialization to the following form for convenience::
124
124
125 {
125 {
126 'header' : dict,
126 'header' : dict,
127 # The msg's unique identifier and type are always stored in the header,
127 # The msg's unique identifier and type are always stored in the header,
128 # but the Python implementation copies them to the top level.
128 # but the Python implementation copies them to the top level.
129 'msg_id' : uuid,
129 'msg_id' : uuid,
130 'msg_type' : str,
130 'msg_type' : str,
131 'parent_header' : dict,
131 'parent_header' : dict,
132 'content' : dict,
132 'content' : dict,
133 'metadata' : dict, # optional
133 'metadata' : dict,
134 }
134 }
135
135
136 All messages sent to or received by any IPython process should have this
136 All messages sent to or received by any IPython process should have this
137 extended structure.
137 extended structure.
138
138
139
139
140 Messages on the shell ROUTER/DEALER sockets
140 Messages on the shell ROUTER/DEALER sockets
141 ===========================================
141 ===========================================
142
142
143 .. _execute:
143 .. _execute:
144
144
145 Execute
145 Execute
146 -------
146 -------
147
147
148 This message type is used by frontends to ask the kernel to execute code on
148 This message type is used by frontends to ask the kernel to execute code on
149 behalf of the user, in a namespace reserved to the user's variables (and thus
149 behalf of the user, in a namespace reserved to the user's variables (and thus
150 separate from the kernel's own internal code and variables).
150 separate from the kernel's own internal code and variables).
151
151
152 Message type: ``execute_request``::
152 Message type: ``execute_request``::
153
153
154 content = {
154 content = {
155 # Source code to be executed by the kernel, one or more lines.
155 # Source code to be executed by the kernel, one or more lines.
156 'code' : str,
156 'code' : str,
157
157
158 # A boolean flag which, if True, signals the kernel to execute
158 # A boolean flag which, if True, signals the kernel to execute
159 # this code as quietly as possible. This means that the kernel
159 # this code as quietly as possible. This means that the kernel
160 # will compile the code with 'exec' instead of 'single' (so
160 # will compile the code with 'exec' instead of 'single' (so
161 # sys.displayhook will not fire), and will *not*:
161 # sys.displayhook will not fire), and will *not*:
162 # - broadcast exceptions on the PUB socket
162 # - broadcast exceptions on the PUB socket
163 # - do any logging
163 # - do any logging
164 # - populate any history
164 # - populate any history
165 #
165 #
166 # The default is False.
166 # The default is False.
167 'silent' : bool,
167 'silent' : bool,
168
168
169 # A list of variable names from the user's namespace to be retrieved. What
169 # A list of variable names from the user's namespace to be retrieved. What
170 # returns is a JSON string of the variable's repr(), not a python object.
170 # returns is a JSON string of the variable's repr(), not a python object.
171 'user_variables' : list,
171 'user_variables' : list,
172
172
173 # Similarly, a dict mapping names to expressions to be evaluated in the
173 # Similarly, a dict mapping names to expressions to be evaluated in the
174 # user's dict.
174 # user's dict.
175 'user_expressions' : dict,
175 'user_expressions' : dict,
176
176
177 # Some frontends (e.g. the Notebook) do not support stdin requests. If
177 # Some frontends (e.g. the Notebook) do not support stdin requests. If
178 # raw_input is called from code executed from such a frontend, a
178 # raw_input is called from code executed from such a frontend, a
179 # StdinNotImplementedError will be raised.
179 # StdinNotImplementedError will be raised.
180 'allow_stdin' : True,
180 'allow_stdin' : True,
181
181
182 }
182 }
183
183
184 The ``code`` field contains a single string (possibly multiline). The kernel
184 The ``code`` field contains a single string (possibly multiline). The kernel
185 is responsible for splitting this into one or more independent execution blocks
185 is responsible for splitting this into one or more independent execution blocks
186 and deciding whether to compile these in 'single' or 'exec' mode (see below for
186 and deciding whether to compile these in 'single' or 'exec' mode (see below for
187 detailed execution semantics).
187 detailed execution semantics).
188
188
189 The ``user_`` fields deserve a detailed explanation. In the past, IPython had
189 The ``user_`` fields deserve a detailed explanation. In the past, IPython had
190 the notion of a prompt string that allowed arbitrary code to be evaluated, and
190 the notion of a prompt string that allowed arbitrary code to be evaluated, and
191 this was put to good use by many in creating prompts that displayed system
191 this was put to good use by many in creating prompts that displayed system
192 status, path information, and even more esoteric uses like remote instrument
192 status, path information, and even more esoteric uses like remote instrument
193 status aqcuired over the network. But now that IPython has a clean separation
193 status aqcuired over the network. But now that IPython has a clean separation
194 between the kernel and the clients, the kernel has no prompt knowledge; prompts
194 between the kernel and the clients, the kernel has no prompt knowledge; prompts
195 are a frontend-side feature, and it should be even possible for different
195 are a frontend-side feature, and it should be even possible for different
196 frontends to display different prompts while interacting with the same kernel.
196 frontends to display different prompts while interacting with the same kernel.
197
197
198 The kernel now provides the ability to retrieve data from the user's namespace
198 The kernel now provides the ability to retrieve data from the user's namespace
199 after the execution of the main ``code``, thanks to two fields in the
199 after the execution of the main ``code``, thanks to two fields in the
200 ``execute_request`` message:
200 ``execute_request`` message:
201
201
202 - ``user_variables``: If only variables from the user's namespace are needed, a
202 - ``user_variables``: If only variables from the user's namespace are needed, a
203 list of variable names can be passed and a dict with these names as keys and
203 list of variable names can be passed and a dict with these names as keys and
204 their :func:`repr()` as values will be returned.
204 their :func:`repr()` as values will be returned.
205
205
206 - ``user_expressions``: For more complex expressions that require function
206 - ``user_expressions``: For more complex expressions that require function
207 evaluations, a dict can be provided with string keys and arbitrary python
207 evaluations, a dict can be provided with string keys and arbitrary python
208 expressions as values. The return message will contain also a dict with the
208 expressions as values. The return message will contain also a dict with the
209 same keys and the :func:`repr()` of the evaluated expressions as value.
209 same keys and the :func:`repr()` of the evaluated expressions as value.
210
210
211 With this information, frontends can display any status information they wish
211 With this information, frontends can display any status information they wish
212 in the form that best suits each frontend (a status line, a popup, inline for a
212 in the form that best suits each frontend (a status line, a popup, inline for a
213 terminal, etc).
213 terminal, etc).
214
214
215 .. Note::
215 .. Note::
216
216
217 In order to obtain the current execution counter for the purposes of
217 In order to obtain the current execution counter for the purposes of
218 displaying input prompts, frontends simply make an execution request with an
218 displaying input prompts, frontends simply make an execution request with an
219 empty code string and ``silent=True``.
219 empty code string and ``silent=True``.
220
220
221 Execution semantics
221 Execution semantics
222 ~~~~~~~~~~~~~~~~~~~
222 ~~~~~~~~~~~~~~~~~~~
223
223
224 When the silent flag is false, the execution of use code consists of the
224 When the silent flag is false, the execution of use code consists of the
225 following phases (in silent mode, only the ``code`` field is executed):
225 following phases (in silent mode, only the ``code`` field is executed):
226
226
227 1. Run the ``pre_runcode_hook``.
227 1. Run the ``pre_runcode_hook``.
228
228
229 2. Execute the ``code`` field, see below for details.
229 2. Execute the ``code`` field, see below for details.
230
230
231 3. If #2 succeeds, compute ``user_variables`` and ``user_expressions`` are
231 3. If #2 succeeds, compute ``user_variables`` and ``user_expressions`` are
232 computed. This ensures that any error in the latter don't harm the main
232 computed. This ensures that any error in the latter don't harm the main
233 code execution.
233 code execution.
234
234
235 4. Call any method registered with :meth:`register_post_execute`.
235 4. Call any method registered with :meth:`register_post_execute`.
236
236
237 .. warning::
237 .. warning::
238
238
239 The API for running code before/after the main code block is likely to
239 The API for running code before/after the main code block is likely to
240 change soon. Both the ``pre_runcode_hook`` and the
240 change soon. Both the ``pre_runcode_hook`` and the
241 :meth:`register_post_execute` are susceptible to modification, as we find a
241 :meth:`register_post_execute` are susceptible to modification, as we find a
242 consistent model for both.
242 consistent model for both.
243
243
244 To understand how the ``code`` field is executed, one must know that Python
244 To understand how the ``code`` field is executed, one must know that Python
245 code can be compiled in one of three modes (controlled by the ``mode`` argument
245 code can be compiled in one of three modes (controlled by the ``mode`` argument
246 to the :func:`compile` builtin):
246 to the :func:`compile` builtin):
247
247
248 *single*
248 *single*
249 Valid for a single interactive statement (though the source can contain
249 Valid for a single interactive statement (though the source can contain
250 multiple lines, such as a for loop). When compiled in this mode, the
250 multiple lines, such as a for loop). When compiled in this mode, the
251 generated bytecode contains special instructions that trigger the calling of
251 generated bytecode contains special instructions that trigger the calling of
252 :func:`sys.displayhook` for any expression in the block that returns a value.
252 :func:`sys.displayhook` for any expression in the block that returns a value.
253 This means that a single statement can actually produce multiple calls to
253 This means that a single statement can actually produce multiple calls to
254 :func:`sys.displayhook`, if for example it contains a loop where each
254 :func:`sys.displayhook`, if for example it contains a loop where each
255 iteration computes an unassigned expression would generate 10 calls::
255 iteration computes an unassigned expression would generate 10 calls::
256
256
257 for i in range(10):
257 for i in range(10):
258 i**2
258 i**2
259
259
260 *exec*
260 *exec*
261 An arbitrary amount of source code, this is how modules are compiled.
261 An arbitrary amount of source code, this is how modules are compiled.
262 :func:`sys.displayhook` is *never* implicitly called.
262 :func:`sys.displayhook` is *never* implicitly called.
263
263
264 *eval*
264 *eval*
265 A single expression that returns a value. :func:`sys.displayhook` is *never*
265 A single expression that returns a value. :func:`sys.displayhook` is *never*
266 implicitly called.
266 implicitly called.
267
267
268
268
269 The ``code`` field is split into individual blocks each of which is valid for
269 The ``code`` field is split into individual blocks each of which is valid for
270 execution in 'single' mode, and then:
270 execution in 'single' mode, and then:
271
271
272 - If there is only a single block: it is executed in 'single' mode.
272 - If there is only a single block: it is executed in 'single' mode.
273
273
274 - If there is more than one block:
274 - If there is more than one block:
275
275
276 * if the last one is a single line long, run all but the last in 'exec' mode
276 * if the last one is a single line long, run all but the last in 'exec' mode
277 and the very last one in 'single' mode. This makes it easy to type simple
277 and the very last one in 'single' mode. This makes it easy to type simple
278 expressions at the end to see computed values.
278 expressions at the end to see computed values.
279
279
280 * if the last one is no more than two lines long, run all but the last in
280 * if the last one is no more than two lines long, run all but the last in
281 'exec' mode and the very last one in 'single' mode. This makes it easy to
281 'exec' mode and the very last one in 'single' mode. This makes it easy to
282 type simple expressions at the end to see computed values. - otherwise
282 type simple expressions at the end to see computed values. - otherwise
283 (last one is also multiline), run all in 'exec' mode
283 (last one is also multiline), run all in 'exec' mode
284
284
285 * otherwise (last one is also multiline), run all in 'exec' mode as a single
285 * otherwise (last one is also multiline), run all in 'exec' mode as a single
286 unit.
286 unit.
287
287
288 Any error in retrieving the ``user_variables`` or evaluating the
288 Any error in retrieving the ``user_variables`` or evaluating the
289 ``user_expressions`` will result in a simple error message in the return fields
289 ``user_expressions`` will result in a simple error message in the return fields
290 of the form::
290 of the form::
291
291
292 [ERROR] ExceptionType: Exception message
292 [ERROR] ExceptionType: Exception message
293
293
294 The user can simply send the same variable name or expression for evaluation to
294 The user can simply send the same variable name or expression for evaluation to
295 see a regular traceback.
295 see a regular traceback.
296
296
297 Errors in any registered post_execute functions are also reported similarly,
297 Errors in any registered post_execute functions are also reported similarly,
298 and the failing function is removed from the post_execution set so that it does
298 and the failing function is removed from the post_execution set so that it does
299 not continue triggering failures.
299 not continue triggering failures.
300
300
301 Upon completion of the execution request, the kernel *always* sends a reply,
301 Upon completion of the execution request, the kernel *always* sends a reply,
302 with a status code indicating what happened and additional data depending on
302 with a status code indicating what happened and additional data depending on
303 the outcome. See :ref:`below <execution_results>` for the possible return
303 the outcome. See :ref:`below <execution_results>` for the possible return
304 codes and associated data.
304 codes and associated data.
305
305
306
306
307 Execution counter (old prompt number)
307 Execution counter (old prompt number)
308 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
308 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
309
309
310 The kernel has a single, monotonically increasing counter of all execution
310 The kernel has a single, monotonically increasing counter of all execution
311 requests that are made with ``silent=False``. This counter is used to populate
311 requests that are made with ``silent=False``. This counter is used to populate
312 the ``In[n]``, ``Out[n]`` and ``_n`` variables, so clients will likely want to
312 the ``In[n]``, ``Out[n]`` and ``_n`` variables, so clients will likely want to
313 display it in some form to the user, which will typically (but not necessarily)
313 display it in some form to the user, which will typically (but not necessarily)
314 be done in the prompts. The value of this counter will be returned as the
314 be done in the prompts. The value of this counter will be returned as the
315 ``execution_count`` field of all ``execute_reply`` messages.
315 ``execution_count`` field of all ``execute_reply`` messages.
316
316
317 .. _execution_results:
317 .. _execution_results:
318
318
319 Execution results
319 Execution results
320 ~~~~~~~~~~~~~~~~~
320 ~~~~~~~~~~~~~~~~~
321
321
322 Message type: ``execute_reply``::
322 Message type: ``execute_reply``::
323
323
324 content = {
324 content = {
325 # One of: 'ok' OR 'error' OR 'abort'
325 # One of: 'ok' OR 'error' OR 'abort'
326 'status' : str,
326 'status' : str,
327
327
328 # The global kernel counter that increases by one with each non-silent
328 # The global kernel counter that increases by one with each non-silent
329 # executed request. This will typically be used by clients to display
329 # executed request. This will typically be used by clients to display
330 # prompt numbers to the user. If the request was a silent one, this will
330 # prompt numbers to the user. If the request was a silent one, this will
331 # be the current value of the counter in the kernel.
331 # be the current value of the counter in the kernel.
332 'execution_count' : int,
332 'execution_count' : int,
333 }
333 }
334
334
335 When status is 'ok', the following extra fields are present::
335 When status is 'ok', the following extra fields are present::
336
336
337 {
337 {
338 # 'payload' will be a list of payload dicts.
338 # 'payload' will be a list of payload dicts.
339 # Each execution payload is a dict with string keys that may have been
339 # Each execution payload is a dict with string keys that may have been
340 # produced by the code being executed. It is retrieved by the kernel at
340 # produced by the code being executed. It is retrieved by the kernel at
341 # the end of the execution and sent back to the front end, which can take
341 # the end of the execution and sent back to the front end, which can take
342 # action on it as needed. See main text for further details.
342 # action on it as needed. See main text for further details.
343 'payload' : list(dict),
343 'payload' : list(dict),
344
344
345 # Results for the user_variables and user_expressions.
345 # Results for the user_variables and user_expressions.
346 'user_variables' : dict,
346 'user_variables' : dict,
347 'user_expressions' : dict,
347 'user_expressions' : dict,
348 }
348 }
349
349
350 .. admonition:: Execution payloads
350 .. admonition:: Execution payloads
351
351
352 The notion of an 'execution payload' is different from a return value of a
352 The notion of an 'execution payload' is different from a return value of a
353 given set of code, which normally is just displayed on the pyout stream
353 given set of code, which normally is just displayed on the pyout stream
354 through the PUB socket. The idea of a payload is to allow special types of
354 through the PUB socket. The idea of a payload is to allow special types of
355 code, typically magics, to populate a data container in the IPython kernel
355 code, typically magics, to populate a data container in the IPython kernel
356 that will be shipped back to the caller via this channel. The kernel
356 that will be shipped back to the caller via this channel. The kernel
357 has an API for this in the PayloadManager::
357 has an API for this in the PayloadManager::
358
358
359 ip.payload_manager.write_payload(payload_dict)
359 ip.payload_manager.write_payload(payload_dict)
360
360
361 which appends a dictionary to the list of payloads.
361 which appends a dictionary to the list of payloads.
362
362
363
363
364 When status is 'error', the following extra fields are present::
364 When status is 'error', the following extra fields are present::
365
365
366 {
366 {
367 'ename' : str, # Exception name, as a string
367 'ename' : str, # Exception name, as a string
368 'evalue' : str, # Exception value, as a string
368 'evalue' : str, # Exception value, as a string
369
369
370 # The traceback will contain a list of frames, represented each as a
370 # The traceback will contain a list of frames, represented each as a
371 # string. For now we'll stick to the existing design of ultraTB, which
371 # string. For now we'll stick to the existing design of ultraTB, which
372 # controls exception level of detail statefully. But eventually we'll
372 # controls exception level of detail statefully. But eventually we'll
373 # want to grow into a model where more information is collected and
373 # want to grow into a model where more information is collected and
374 # packed into the traceback object, with clients deciding how little or
374 # packed into the traceback object, with clients deciding how little or
375 # how much of it to unpack. But for now, let's start with a simple list
375 # how much of it to unpack. But for now, let's start with a simple list
376 # of strings, since that requires only minimal changes to ultratb as
376 # of strings, since that requires only minimal changes to ultratb as
377 # written.
377 # written.
378 'traceback' : list,
378 'traceback' : list,
379 }
379 }
380
380
381
381
382 When status is 'abort', there are for now no additional data fields. This
382 When status is 'abort', there are for now no additional data fields. This
383 happens when the kernel was interrupted by a signal.
383 happens when the kernel was interrupted by a signal.
384
384
385 Kernel attribute access
385 Kernel attribute access
386 -----------------------
386 -----------------------
387
387
388 .. warning::
388 .. warning::
389
389
390 This part of the messaging spec is not actually implemented in the kernel
390 This part of the messaging spec is not actually implemented in the kernel
391 yet.
391 yet.
392
392
393 While this protocol does not specify full RPC access to arbitrary methods of
393 While this protocol does not specify full RPC access to arbitrary methods of
394 the kernel object, the kernel does allow read (and in some cases write) access
394 the kernel object, the kernel does allow read (and in some cases write) access
395 to certain attributes.
395 to certain attributes.
396
396
397 The policy for which attributes can be read is: any attribute of the kernel, or
397 The policy for which attributes can be read is: any attribute of the kernel, or
398 its sub-objects, that belongs to a :class:`Configurable` object and has been
398 its sub-objects, that belongs to a :class:`Configurable` object and has been
399 declared at the class-level with Traits validation, is in principle accessible
399 declared at the class-level with Traits validation, is in principle accessible
400 as long as its name does not begin with a leading underscore. The attribute
400 as long as its name does not begin with a leading underscore. The attribute
401 itself will have metadata indicating whether it allows remote read and/or write
401 itself will have metadata indicating whether it allows remote read and/or write
402 access. The message spec follows for attribute read and write requests.
402 access. The message spec follows for attribute read and write requests.
403
403
404 Message type: ``getattr_request``::
404 Message type: ``getattr_request``::
405
405
406 content = {
406 content = {
407 # The (possibly dotted) name of the attribute
407 # The (possibly dotted) name of the attribute
408 'name' : str,
408 'name' : str,
409 }
409 }
410
410
411 When a ``getattr_request`` fails, there are two possible error types:
411 When a ``getattr_request`` fails, there are two possible error types:
412
412
413 - AttributeError: this type of error was raised when trying to access the
413 - AttributeError: this type of error was raised when trying to access the
414 given name by the kernel itself. This means that the attribute likely
414 given name by the kernel itself. This means that the attribute likely
415 doesn't exist.
415 doesn't exist.
416
416
417 - AccessError: the attribute exists but its value is not readable remotely.
417 - AccessError: the attribute exists but its value is not readable remotely.
418
418
419
419
420 Message type: ``getattr_reply``::
420 Message type: ``getattr_reply``::
421
421
422 content = {
422 content = {
423 # One of ['ok', 'AttributeError', 'AccessError'].
423 # One of ['ok', 'AttributeError', 'AccessError'].
424 'status' : str,
424 'status' : str,
425 # If status is 'ok', a JSON object.
425 # If status is 'ok', a JSON object.
426 'value' : object,
426 'value' : object,
427 }
427 }
428
428
429 Message type: ``setattr_request``::
429 Message type: ``setattr_request``::
430
430
431 content = {
431 content = {
432 # The (possibly dotted) name of the attribute
432 # The (possibly dotted) name of the attribute
433 'name' : str,
433 'name' : str,
434
434
435 # A JSON-encoded object, that will be validated by the Traits
435 # A JSON-encoded object, that will be validated by the Traits
436 # information in the kernel
436 # information in the kernel
437 'value' : object,
437 'value' : object,
438 }
438 }
439
439
440 When a ``setattr_request`` fails, there are also two possible error types with
440 When a ``setattr_request`` fails, there are also two possible error types with
441 similar meanings as those of the ``getattr_request`` case, but for writing.
441 similar meanings as those of the ``getattr_request`` case, but for writing.
442
442
443 Message type: ``setattr_reply``::
443 Message type: ``setattr_reply``::
444
444
445 content = {
445 content = {
446 # One of ['ok', 'AttributeError', 'AccessError'].
446 # One of ['ok', 'AttributeError', 'AccessError'].
447 'status' : str,
447 'status' : str,
448 }
448 }
449
449
450
450
451
451
452 Object information
452 Object information
453 ------------------
453 ------------------
454
454
455 One of IPython's most used capabilities is the introspection of Python objects
455 One of IPython's most used capabilities is the introspection of Python objects
456 in the user's namespace, typically invoked via the ``?`` and ``??`` characters
456 in the user's namespace, typically invoked via the ``?`` and ``??`` characters
457 (which in reality are shorthands for the ``%pinfo`` magic). This is used often
457 (which in reality are shorthands for the ``%pinfo`` magic). This is used often
458 enough that it warrants an explicit message type, especially because frontends
458 enough that it warrants an explicit message type, especially because frontends
459 may want to get object information in response to user keystrokes (like Tab or
459 may want to get object information in response to user keystrokes (like Tab or
460 F1) besides from the user explicitly typing code like ``x??``.
460 F1) besides from the user explicitly typing code like ``x??``.
461
461
462 Message type: ``object_info_request``::
462 Message type: ``object_info_request``::
463
463
464 content = {
464 content = {
465 # The (possibly dotted) name of the object to be searched in all
465 # The (possibly dotted) name of the object to be searched in all
466 # relevant namespaces
466 # relevant namespaces
467 'name' : str,
467 'name' : str,
468
468
469 # The level of detail desired. The default (0) is equivalent to typing
469 # The level of detail desired. The default (0) is equivalent to typing
470 # 'x?' at the prompt, 1 is equivalent to 'x??'.
470 # 'x?' at the prompt, 1 is equivalent to 'x??'.
471 'detail_level' : int,
471 'detail_level' : int,
472 }
472 }
473
473
474 The returned information will be a dictionary with keys very similar to the
474 The returned information will be a dictionary with keys very similar to the
475 field names that IPython prints at the terminal.
475 field names that IPython prints at the terminal.
476
476
477 Message type: ``object_info_reply``::
477 Message type: ``object_info_reply``::
478
478
479 content = {
479 content = {
480 # The name the object was requested under
480 # The name the object was requested under
481 'name' : str,
481 'name' : str,
482
482
483 # Boolean flag indicating whether the named object was found or not. If
483 # Boolean flag indicating whether the named object was found or not. If
484 # it's false, all other fields will be empty.
484 # it's false, all other fields will be empty.
485 'found' : bool,
485 'found' : bool,
486
486
487 # Flags for magics and system aliases
487 # Flags for magics and system aliases
488 'ismagic' : bool,
488 'ismagic' : bool,
489 'isalias' : bool,
489 'isalias' : bool,
490
490
491 # The name of the namespace where the object was found ('builtin',
491 # The name of the namespace where the object was found ('builtin',
492 # 'magics', 'alias', 'interactive', etc.)
492 # 'magics', 'alias', 'interactive', etc.)
493 'namespace' : str,
493 'namespace' : str,
494
494
495 # The type name will be type.__name__ for normal Python objects, but it
495 # The type name will be type.__name__ for normal Python objects, but it
496 # can also be a string like 'Magic function' or 'System alias'
496 # can also be a string like 'Magic function' or 'System alias'
497 'type_name' : str,
497 'type_name' : str,
498
498
499 # The string form of the object, possibly truncated for length if
499 # The string form of the object, possibly truncated for length if
500 # detail_level is 0
500 # detail_level is 0
501 'string_form' : str,
501 'string_form' : str,
502
502
503 # For objects with a __class__ attribute this will be set
503 # For objects with a __class__ attribute this will be set
504 'base_class' : str,
504 'base_class' : str,
505
505
506 # For objects with a __len__ attribute this will be set
506 # For objects with a __len__ attribute this will be set
507 'length' : int,
507 'length' : int,
508
508
509 # If the object is a function, class or method whose file we can find,
509 # If the object is a function, class or method whose file we can find,
510 # we give its full path
510 # we give its full path
511 'file' : str,
511 'file' : str,
512
512
513 # For pure Python callable objects, we can reconstruct the object
513 # For pure Python callable objects, we can reconstruct the object
514 # definition line which provides its call signature. For convenience this
514 # definition line which provides its call signature. For convenience this
515 # is returned as a single 'definition' field, but below the raw parts that
515 # is returned as a single 'definition' field, but below the raw parts that
516 # compose it are also returned as the argspec field.
516 # compose it are also returned as the argspec field.
517 'definition' : str,
517 'definition' : str,
518
518
519 # The individual parts that together form the definition string. Clients
519 # The individual parts that together form the definition string. Clients
520 # with rich display capabilities may use this to provide a richer and more
520 # with rich display capabilities may use this to provide a richer and more
521 # precise representation of the definition line (e.g. by highlighting
521 # precise representation of the definition line (e.g. by highlighting
522 # arguments based on the user's cursor position). For non-callable
522 # arguments based on the user's cursor position). For non-callable
523 # objects, this field is empty.
523 # objects, this field is empty.
524 'argspec' : { # The names of all the arguments
524 'argspec' : { # The names of all the arguments
525 args : list,
525 args : list,
526 # The name of the varargs (*args), if any
526 # The name of the varargs (*args), if any
527 varargs : str,
527 varargs : str,
528 # The name of the varkw (**kw), if any
528 # The name of the varkw (**kw), if any
529 varkw : str,
529 varkw : str,
530 # The values (as strings) of all default arguments. Note
530 # The values (as strings) of all default arguments. Note
531 # that these must be matched *in reverse* with the 'args'
531 # that these must be matched *in reverse* with the 'args'
532 # list above, since the first positional args have no default
532 # list above, since the first positional args have no default
533 # value at all.
533 # value at all.
534 defaults : list,
534 defaults : list,
535 },
535 },
536
536
537 # For instances, provide the constructor signature (the definition of
537 # For instances, provide the constructor signature (the definition of
538 # the __init__ method):
538 # the __init__ method):
539 'init_definition' : str,
539 'init_definition' : str,
540
540
541 # Docstrings: for any object (function, method, module, package) with a
541 # Docstrings: for any object (function, method, module, package) with a
542 # docstring, we show it. But in addition, we may provide additional
542 # docstring, we show it. But in addition, we may provide additional
543 # docstrings. For example, for instances we will show the constructor
543 # docstrings. For example, for instances we will show the constructor
544 # and class docstrings as well, if available.
544 # and class docstrings as well, if available.
545 'docstring' : str,
545 'docstring' : str,
546
546
547 # For instances, provide the constructor and class docstrings
547 # For instances, provide the constructor and class docstrings
548 'init_docstring' : str,
548 'init_docstring' : str,
549 'class_docstring' : str,
549 'class_docstring' : str,
550
550
551 # If it's a callable object whose call method has a separate docstring and
551 # If it's a callable object whose call method has a separate docstring and
552 # definition line:
552 # definition line:
553 'call_def' : str,
553 'call_def' : str,
554 'call_docstring' : str,
554 'call_docstring' : str,
555
555
556 # If detail_level was 1, we also try to find the source code that
556 # If detail_level was 1, we also try to find the source code that
557 # defines the object, if possible. The string 'None' will indicate
557 # defines the object, if possible. The string 'None' will indicate
558 # that no source was found.
558 # that no source was found.
559 'source' : str,
559 'source' : str,
560 }
560 }
561
561
562
562
563 Complete
563 Complete
564 --------
564 --------
565
565
566 Message type: ``complete_request``::
566 Message type: ``complete_request``::
567
567
568 content = {
568 content = {
569 # The text to be completed, such as 'a.is'
569 # The text to be completed, such as 'a.is'
570 'text' : str,
570 'text' : str,
571
571
572 # The full line, such as 'print a.is'. This allows completers to
572 # The full line, such as 'print a.is'. This allows completers to
573 # make decisions that may require information about more than just the
573 # make decisions that may require information about more than just the
574 # current word.
574 # current word.
575 'line' : str,
575 'line' : str,
576
576
577 # The entire block of text where the line is. This may be useful in the
577 # The entire block of text where the line is. This may be useful in the
578 # case of multiline completions where more context may be needed. Note: if
578 # case of multiline completions where more context may be needed. Note: if
579 # in practice this field proves unnecessary, remove it to lighten the
579 # in practice this field proves unnecessary, remove it to lighten the
580 # messages.
580 # messages.
581
581
582 'block' : str,
582 'block' : str,
583
583
584 # The position of the cursor where the user hit 'TAB' on the line.
584 # The position of the cursor where the user hit 'TAB' on the line.
585 'cursor_pos' : int,
585 'cursor_pos' : int,
586 }
586 }
587
587
588 Message type: ``complete_reply``::
588 Message type: ``complete_reply``::
589
589
590 content = {
590 content = {
591 # The list of all matches to the completion request, such as
591 # The list of all matches to the completion request, such as
592 # ['a.isalnum', 'a.isalpha'] for the above example.
592 # ['a.isalnum', 'a.isalpha'] for the above example.
593 'matches' : list
593 'matches' : list
594 }
594 }
595
595
596
596
597 History
597 History
598 -------
598 -------
599
599
600 For clients to explicitly request history from a kernel. The kernel has all
600 For clients to explicitly request history from a kernel. The kernel has all
601 the actual execution history stored in a single location, so clients can
601 the actual execution history stored in a single location, so clients can
602 request it from the kernel when needed.
602 request it from the kernel when needed.
603
603
604 Message type: ``history_request``::
604 Message type: ``history_request``::
605
605
606 content = {
606 content = {
607
607
608 # If True, also return output history in the resulting dict.
608 # If True, also return output history in the resulting dict.
609 'output' : bool,
609 'output' : bool,
610
610
611 # If True, return the raw input history, else the transformed input.
611 # If True, return the raw input history, else the transformed input.
612 'raw' : bool,
612 'raw' : bool,
613
613
614 # So far, this can be 'range', 'tail' or 'search'.
614 # So far, this can be 'range', 'tail' or 'search'.
615 'hist_access_type' : str,
615 'hist_access_type' : str,
616
616
617 # If hist_access_type is 'range', get a range of input cells. session can
617 # If hist_access_type is 'range', get a range of input cells. session can
618 # be a positive session number, or a negative number to count back from
618 # be a positive session number, or a negative number to count back from
619 # the current session.
619 # the current session.
620 'session' : int,
620 'session' : int,
621 # start and stop are line numbers within that session.
621 # start and stop are line numbers within that session.
622 'start' : int,
622 'start' : int,
623 'stop' : int,
623 'stop' : int,
624
624
625 # If hist_access_type is 'tail', get the last n cells.
625 # If hist_access_type is 'tail', get the last n cells.
626 'n' : int,
626 'n' : int,
627
627
628 # If hist_access_type is 'search', get cells matching the specified glob
628 # If hist_access_type is 'search', get cells matching the specified glob
629 # pattern (with * and ? as wildcards).
629 # pattern (with * and ? as wildcards).
630 'pattern' : str,
630 'pattern' : str,
631
631
632 }
632 }
633
633
634 Message type: ``history_reply``::
634 Message type: ``history_reply``::
635
635
636 content = {
636 content = {
637 # A list of 3 tuples, either:
637 # A list of 3 tuples, either:
638 # (session, line_number, input) or
638 # (session, line_number, input) or
639 # (session, line_number, (input, output)),
639 # (session, line_number, (input, output)),
640 # depending on whether output was False or True, respectively.
640 # depending on whether output was False or True, respectively.
641 'history' : list,
641 'history' : list,
642 }
642 }
643
643
644
644
645 Connect
645 Connect
646 -------
646 -------
647
647
648 When a client connects to the request/reply socket of the kernel, it can issue
648 When a client connects to the request/reply socket of the kernel, it can issue
649 a connect request to get basic information about the kernel, such as the ports
649 a connect request to get basic information about the kernel, such as the ports
650 the other ZeroMQ sockets are listening on. This allows clients to only have
650 the other ZeroMQ sockets are listening on. This allows clients to only have
651 to know about a single port (the shell channel) to connect to a kernel.
651 to know about a single port (the shell channel) to connect to a kernel.
652
652
653 Message type: ``connect_request``::
653 Message type: ``connect_request``::
654
654
655 content = {
655 content = {
656 }
656 }
657
657
658 Message type: ``connect_reply``::
658 Message type: ``connect_reply``::
659
659
660 content = {
660 content = {
661 'shell_port' : int # The port the shell ROUTER socket is listening on.
661 'shell_port' : int # The port the shell ROUTER socket is listening on.
662 'iopub_port' : int # The port the PUB socket is listening on.
662 'iopub_port' : int # The port the PUB socket is listening on.
663 'stdin_port' : int # The port the stdin ROUTER socket is listening on.
663 'stdin_port' : int # The port the stdin ROUTER socket is listening on.
664 'hb_port' : int # The port the heartbeat socket is listening on.
664 'hb_port' : int # The port the heartbeat socket is listening on.
665 }
665 }
666
666
667
667
668
668
669 Kernel shutdown
669 Kernel shutdown
670 ---------------
670 ---------------
671
671
672 The clients can request the kernel to shut itself down; this is used in
672 The clients can request the kernel to shut itself down; this is used in
673 multiple cases:
673 multiple cases:
674
674
675 - when the user chooses to close the client application via a menu or window
675 - when the user chooses to close the client application via a menu or window
676 control.
676 control.
677 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
677 - when the user types 'exit' or 'quit' (or their uppercase magic equivalents).
678 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
678 - when the user chooses a GUI method (like the 'Ctrl-C' shortcut in the
679 IPythonQt client) to force a kernel restart to get a clean kernel without
679 IPythonQt client) to force a kernel restart to get a clean kernel without
680 losing client-side state like history or inlined figures.
680 losing client-side state like history or inlined figures.
681
681
682 The client sends a shutdown request to the kernel, and once it receives the
682 The client sends a shutdown request to the kernel, and once it receives the
683 reply message (which is otherwise empty), it can assume that the kernel has
683 reply message (which is otherwise empty), it can assume that the kernel has
684 completed shutdown safely.
684 completed shutdown safely.
685
685
686 Upon their own shutdown, client applications will typically execute a last
686 Upon their own shutdown, client applications will typically execute a last
687 minute sanity check and forcefully terminate any kernel that is still alive, to
687 minute sanity check and forcefully terminate any kernel that is still alive, to
688 avoid leaving stray processes in the user's machine.
688 avoid leaving stray processes in the user's machine.
689
689
690 For both shutdown request and reply, there is no actual content that needs to
690 For both shutdown request and reply, there is no actual content that needs to
691 be sent, so the content dict is empty.
691 be sent, so the content dict is empty.
692
692
693 Message type: ``shutdown_request``::
693 Message type: ``shutdown_request``::
694
694
695 content = {
695 content = {
696 'restart' : bool # whether the shutdown is final, or precedes a restart
696 'restart' : bool # whether the shutdown is final, or precedes a restart
697 }
697 }
698
698
699 Message type: ``shutdown_reply``::
699 Message type: ``shutdown_reply``::
700
700
701 content = {
701 content = {
702 'restart' : bool # whether the shutdown is final, or precedes a restart
702 'restart' : bool # whether the shutdown is final, or precedes a restart
703 }
703 }
704
704
705 .. Note::
705 .. Note::
706
706
707 When the clients detect a dead kernel thanks to inactivity on the heartbeat
707 When the clients detect a dead kernel thanks to inactivity on the heartbeat
708 socket, they simply send a forceful process termination signal, since a dead
708 socket, they simply send a forceful process termination signal, since a dead
709 process is unlikely to respond in any useful way to messages.
709 process is unlikely to respond in any useful way to messages.
710
710
711
711
712 Messages on the PUB/SUB socket
712 Messages on the PUB/SUB socket
713 ==============================
713 ==============================
714
714
715 Streams (stdout, stderr, etc)
715 Streams (stdout, stderr, etc)
716 ------------------------------
716 ------------------------------
717
717
718 Message type: ``stream``::
718 Message type: ``stream``::
719
719
720 content = {
720 content = {
721 # The name of the stream is one of 'stdin', 'stdout', 'stderr'
721 # The name of the stream is one of 'stdin', 'stdout', 'stderr'
722 'name' : str,
722 'name' : str,
723
723
724 # The data is an arbitrary string to be written to that stream
724 # The data is an arbitrary string to be written to that stream
725 'data' : str,
725 'data' : str,
726 }
726 }
727
727
728 When a kernel receives a raw_input call, it should also broadcast it on the pub
728 When a kernel receives a raw_input call, it should also broadcast it on the pub
729 socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
729 socket with the names 'stdin' and 'stdin_reply'. This will allow other clients
730 to monitor/display kernel interactions and possibly replay them to their user
730 to monitor/display kernel interactions and possibly replay them to their user
731 or otherwise expose them.
731 or otherwise expose them.
732
732
733 Display Data
733 Display Data
734 ------------
734 ------------
735
735
736 This type of message is used to bring back data that should be diplayed (text,
736 This type of message is used to bring back data that should be diplayed (text,
737 html, svg, etc.) in the frontends. This data is published to all frontends.
737 html, svg, etc.) in the frontends. This data is published to all frontends.
738 Each message can have multiple representations of the data; it is up to the
738 Each message can have multiple representations of the data; it is up to the
739 frontend to decide which to use and how. A single message should contain all
739 frontend to decide which to use and how. A single message should contain all
740 possible representations of the same information. Each representation should
740 possible representations of the same information. Each representation should
741 be a JSON'able data structure, and should be a valid MIME type.
741 be a JSON'able data structure, and should be a valid MIME type.
742
742
743 Some questions remain about this design:
743 Some questions remain about this design:
744
744
745 * Do we use this message type for pyout/displayhook? Probably not, because
745 * Do we use this message type for pyout/displayhook? Probably not, because
746 the displayhook also has to handle the Out prompt display. On the other hand
746 the displayhook also has to handle the Out prompt display. On the other hand
747 we could put that information into the metadata secion.
747 we could put that information into the metadata secion.
748
748
749 Message type: ``display_data``::
749 Message type: ``display_data``::
750
750
751 content = {
751 content = {
752
752
753 # Who create the data
753 # Who create the data
754 'source' : str,
754 'source' : str,
755
755
756 # The data dict contains key/value pairs, where the kids are MIME
756 # The data dict contains key/value pairs, where the kids are MIME
757 # types and the values are the raw data of the representation in that
757 # types and the values are the raw data of the representation in that
758 # format. The data dict must minimally contain the ``text/plain``
758 # format. The data dict must minimally contain the ``text/plain``
759 # MIME type which is used as a backup representation.
759 # MIME type which is used as a backup representation.
760 'data' : dict,
760 'data' : dict,
761
761
762 # Any metadata that describes the data
762 # Any metadata that describes the data
763 'metadata' : dict
763 'metadata' : dict
764 }
764 }
765
765
766 Python inputs
766 Python inputs
767 -------------
767 -------------
768
768
769 These messages are the re-broadcast of the ``execute_request``.
769 These messages are the re-broadcast of the ``execute_request``.
770
770
771 Message type: ``pyin``::
771 Message type: ``pyin``::
772
772
773 content = {
773 content = {
774 'code' : str, # Source code to be executed, one or more lines
774 'code' : str, # Source code to be executed, one or more lines
775
775
776 # The counter for this execution is also provided so that clients can
776 # The counter for this execution is also provided so that clients can
777 # display it, since IPython automatically creates variables called _iN
777 # display it, since IPython automatically creates variables called _iN
778 # (for input prompt In[N]).
778 # (for input prompt In[N]).
779 'execution_count' : int
779 'execution_count' : int
780 }
780 }
781
781
782 Python outputs
782 Python outputs
783 --------------
783 --------------
784
784
785 When Python produces output from code that has been compiled in with the
785 When Python produces output from code that has been compiled in with the
786 'single' flag to :func:`compile`, any expression that produces a value (such as
786 'single' flag to :func:`compile`, any expression that produces a value (such as
787 ``1+1``) is passed to ``sys.displayhook``, which is a callable that can do with
787 ``1+1``) is passed to ``sys.displayhook``, which is a callable that can do with
788 this value whatever it wants. The default behavior of ``sys.displayhook`` in
788 this value whatever it wants. The default behavior of ``sys.displayhook`` in
789 the Python interactive prompt is to print to ``sys.stdout`` the :func:`repr` of
789 the Python interactive prompt is to print to ``sys.stdout`` the :func:`repr` of
790 the value as long as it is not ``None`` (which isn't printed at all). In our
790 the value as long as it is not ``None`` (which isn't printed at all). In our
791 case, the kernel instantiates as ``sys.displayhook`` an object which has
791 case, the kernel instantiates as ``sys.displayhook`` an object which has
792 similar behavior, but which instead of printing to stdout, broadcasts these
792 similar behavior, but which instead of printing to stdout, broadcasts these
793 values as ``pyout`` messages for clients to display appropriately.
793 values as ``pyout`` messages for clients to display appropriately.
794
794
795 IPython's displayhook can handle multiple simultaneous formats depending on its
795 IPython's displayhook can handle multiple simultaneous formats depending on its
796 configuration. The default pretty-printed repr text is always given with the
796 configuration. The default pretty-printed repr text is always given with the
797 ``data`` entry in this message. Any other formats are provided in the
797 ``data`` entry in this message. Any other formats are provided in the
798 ``extra_formats`` list. Frontends are free to display any or all of these
798 ``extra_formats`` list. Frontends are free to display any or all of these
799 according to its capabilities. ``extra_formats`` list contains 3-tuples of an ID
799 according to its capabilities. ``extra_formats`` list contains 3-tuples of an ID
800 string, a type string, and the data. The ID is unique to the formatter
800 string, a type string, and the data. The ID is unique to the formatter
801 implementation that created the data. Frontends will typically ignore the ID
801 implementation that created the data. Frontends will typically ignore the ID
802 unless if it has requested a particular formatter. The type string tells the
802 unless if it has requested a particular formatter. The type string tells the
803 frontend how to interpret the data. It is often, but not always a MIME type.
803 frontend how to interpret the data. It is often, but not always a MIME type.
804 Frontends should ignore types that it does not understand. The data itself is
804 Frontends should ignore types that it does not understand. The data itself is
805 any JSON object and depends on the format. It is often, but not always a string.
805 any JSON object and depends on the format. It is often, but not always a string.
806
806
807 Message type: ``pyout``::
807 Message type: ``pyout``::
808
808
809 content = {
809 content = {
810
810
811 # The counter for this execution is also provided so that clients can
811 # The counter for this execution is also provided so that clients can
812 # display it, since IPython automatically creates variables called _N
812 # display it, since IPython automatically creates variables called _N
813 # (for prompt N).
813 # (for prompt N).
814 'execution_count' : int,
814 'execution_count' : int,
815
815
816 # The data dict contains key/value pairs, where the kids are MIME
816 # The data dict contains key/value pairs, where the kids are MIME
817 # types and the values are the raw data of the representation in that
817 # types and the values are the raw data of the representation in that
818 # format. The data dict must minimally contain the ``text/plain``
818 # format. The data dict must minimally contain the ``text/plain``
819 # MIME type which is used as a backup representation.
819 # MIME type which is used as a backup representation.
820 'data' : dict,
820 'data' : dict,
821
821
822 }
822 }
823
823
824 Python errors
824 Python errors
825 -------------
825 -------------
826
826
827 When an error occurs during code execution
827 When an error occurs during code execution
828
828
829 Message type: ``pyerr``::
829 Message type: ``pyerr``::
830
830
831 content = {
831 content = {
832 # Similar content to the execute_reply messages for the 'error' case,
832 # Similar content to the execute_reply messages for the 'error' case,
833 # except the 'status' field is omitted.
833 # except the 'status' field is omitted.
834 }
834 }
835
835
836 Kernel status
836 Kernel status
837 -------------
837 -------------
838
838
839 This message type is used by frontends to monitor the status of the kernel.
839 This message type is used by frontends to monitor the status of the kernel.
840
840
841 Message type: ``status``::
841 Message type: ``status``::
842
842
843 content = {
843 content = {
844 # When the kernel starts to execute code, it will enter the 'busy'
844 # When the kernel starts to execute code, it will enter the 'busy'
845 # state and when it finishes, it will enter the 'idle' state.
845 # state and when it finishes, it will enter the 'idle' state.
846 execution_state : ('busy', 'idle')
846 execution_state : ('busy', 'idle')
847 }
847 }
848
848
849 Kernel crashes
849 Kernel crashes
850 --------------
850 --------------
851
851
852 When the kernel has an unexpected exception, caught by the last-resort
852 When the kernel has an unexpected exception, caught by the last-resort
853 sys.excepthook, we should broadcast the crash handler's output before exiting.
853 sys.excepthook, we should broadcast the crash handler's output before exiting.
854 This will allow clients to notice that a kernel died, inform the user and
854 This will allow clients to notice that a kernel died, inform the user and
855 propose further actions.
855 propose further actions.
856
856
857 Message type: ``crash``::
857 Message type: ``crash``::
858
858
859 content = {
859 content = {
860 # Similarly to the 'error' case for execute_reply messages, this will
860 # Similarly to the 'error' case for execute_reply messages, this will
861 # contain ename, etype and traceback fields.
861 # contain ename, etype and traceback fields.
862
862
863 # An additional field with supplementary information such as where to
863 # An additional field with supplementary information such as where to
864 # send the crash message
864 # send the crash message
865 'info' : str,
865 'info' : str,
866 }
866 }
867
867
868
868
869 Future ideas
869 Future ideas
870 ------------
870 ------------
871
871
872 Other potential message types, currently unimplemented, listed below as ideas.
872 Other potential message types, currently unimplemented, listed below as ideas.
873
873
874 Message type: ``file``::
874 Message type: ``file``::
875
875
876 content = {
876 content = {
877 'path' : 'cool.jpg',
877 'path' : 'cool.jpg',
878 'mimetype' : str,
878 'mimetype' : str,
879 'data' : str,
879 'data' : str,
880 }
880 }
881
881
882
882
883 Messages on the stdin ROUTER/DEALER sockets
883 Messages on the stdin ROUTER/DEALER sockets
884 ===========================================
884 ===========================================
885
885
886 This is a socket where the request/reply pattern goes in the opposite direction:
886 This is a socket where the request/reply pattern goes in the opposite direction:
887 from the kernel to a *single* frontend, and its purpose is to allow
887 from the kernel to a *single* frontend, and its purpose is to allow
888 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
888 ``raw_input`` and similar operations that read from ``sys.stdin`` on the kernel
889 to be fulfilled by the client. The request should be made to the frontend that
889 to be fulfilled by the client. The request should be made to the frontend that
890 made the execution request that prompted ``raw_input`` to be called. For now we
890 made the execution request that prompted ``raw_input`` to be called. For now we
891 will keep these messages as simple as possible, since they only mean to convey
891 will keep these messages as simple as possible, since they only mean to convey
892 the ``raw_input(prompt)`` call.
892 the ``raw_input(prompt)`` call.
893
893
894 Message type: ``input_request``::
894 Message type: ``input_request``::
895
895
896 content = { 'prompt' : str }
896 content = { 'prompt' : str }
897
897
898 Message type: ``input_reply``::
898 Message type: ``input_reply``::
899
899
900 content = { 'value' : str }
900 content = { 'value' : str }
901
901
902 .. Note::
902 .. Note::
903
903
904 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
904 We do not explicitly try to forward the raw ``sys.stdin`` object, because in
905 practice the kernel should behave like an interactive program. When a
905 practice the kernel should behave like an interactive program. When a
906 program is opened on the console, the keyboard effectively takes over the
906 program is opened on the console, the keyboard effectively takes over the
907 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
907 ``stdin`` file descriptor, and it can't be used for raw reading anymore.
908 Since the IPython kernel effectively behaves like a console program (albeit
908 Since the IPython kernel effectively behaves like a console program (albeit
909 one whose "keyboard" is actually living in a separate process and
909 one whose "keyboard" is actually living in a separate process and
910 transported over the zmq connection), raw ``stdin`` isn't expected to be
910 transported over the zmq connection), raw ``stdin`` isn't expected to be
911 available.
911 available.
912
912
913
913
914 Heartbeat for kernels
914 Heartbeat for kernels
915 =====================
915 =====================
916
916
917 Initially we had considered using messages like those above over ZMQ for a
917 Initially we had considered using messages like those above over ZMQ for a
918 kernel 'heartbeat' (a way to detect quickly and reliably whether a kernel is
918 kernel 'heartbeat' (a way to detect quickly and reliably whether a kernel is
919 alive at all, even if it may be busy executing user code). But this has the
919 alive at all, even if it may be busy executing user code). But this has the
920 problem that if the kernel is locked inside extension code, it wouldn't execute
920 problem that if the kernel is locked inside extension code, it wouldn't execute
921 the python heartbeat code. But it turns out that we can implement a basic
921 the python heartbeat code. But it turns out that we can implement a basic
922 heartbeat with pure ZMQ, without using any Python messaging at all.
922 heartbeat with pure ZMQ, without using any Python messaging at all.
923
923
924 The monitor sends out a single zmq message (right now, it is a str of the
924 The monitor sends out a single zmq message (right now, it is a str of the
925 monitor's lifetime in seconds), and gets the same message right back, prefixed
925 monitor's lifetime in seconds), and gets the same message right back, prefixed
926 with the zmq identity of the DEALER socket in the heartbeat process. This can be
926 with the zmq identity of the DEALER socket in the heartbeat process. This can be
927 a uuid, or even a full message, but there doesn't seem to be a need for packing
927 a uuid, or even a full message, but there doesn't seem to be a need for packing
928 up a message when the sender and receiver are the exact same Python object.
928 up a message when the sender and receiver are the exact same Python object.
929
929
930 The model is this::
930 The model is this::
931
931
932 monitor.send(str(self.lifetime)) # '1.2345678910'
932 monitor.send(str(self.lifetime)) # '1.2345678910'
933
933
934 and the monitor receives some number of messages of the form::
934 and the monitor receives some number of messages of the form::
935
935
936 ['uuid-abcd-dead-beef', '1.2345678910']
936 ['uuid-abcd-dead-beef', '1.2345678910']
937
937
938 where the first part is the zmq.IDENTITY of the heart's DEALER on the engine, and
938 where the first part is the zmq.IDENTITY of the heart's DEALER on the engine, and
939 the rest is the message sent by the monitor. No Python code ever has any
939 the rest is the message sent by the monitor. No Python code ever has any
940 access to the message between the monitor's send, and the monitor's recv.
940 access to the message between the monitor's send, and the monitor's recv.
941
941
942
942
943 ToDo
943 ToDo
944 ====
944 ====
945
945
946 Missing things include:
946 Missing things include:
947
947
948 * Important: finish thinking through the payload concept and API.
948 * Important: finish thinking through the payload concept and API.
949
949
950 * Important: ensure that we have a good solution for magics like %edit. It's
950 * Important: ensure that we have a good solution for magics like %edit. It's
951 likely that with the payload concept we can build a full solution, but not
951 likely that with the payload concept we can build a full solution, but not
952 100% clear yet.
952 100% clear yet.
953
953
954 * Finishing the details of the heartbeat protocol.
954 * Finishing the details of the heartbeat protocol.
955
955
956 * Signal handling: specify what kind of information kernel should broadcast (or
956 * Signal handling: specify what kind of information kernel should broadcast (or
957 not) when it receives signals.
957 not) when it receives signals.
958
958
959 .. include:: ../links.rst
959 .. include:: ../links.rst
General Comments 0
You need to be logged in to leave comments. Login now