##// END OF EJS Templates
Add docstrings to kernel.js
Jessica B. Hamrick -
Show More
@@ -1,780 +1,950
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'services/kernels/js/comm',
8 'services/kernels/js/comm',
9 'widgets/js/init',
9 'widgets/js/init',
10 ], function(IPython, $, utils, comm, widgetmanager) {
10 ], function(IPython, $, utils, comm, widgetmanager) {
11 "use strict";
11 "use strict";
12
12
13 // Initialization and connection.
14 /**
13 /**
15 * A Kernel Class to communicate with the Python kernel
14 * A Kernel class to communicate with the Python kernel. This
16 * @Class Kernel
15 * should generally not be constructed directly, but be created
16 * by. the `Session` object. Once created, this object should be
17 * used to communicate with the kernel.
18 *
19 * @class Kernel
20 * @param {string} kernel_service_url - the URL to access the kernel REST api
21 * @param {string} ws_url - the websockets URL
22 * @param {Notebook} notebook - notebook object
23 * @param {string} id - the kernel id
24 * @param {string} name - the kernel type (e.g. python3)
17 */
25 */
18 var Kernel = function (kernel_service_url, ws_url, notebook, id, name) {
26 var Kernel = function (kernel_service_url, ws_url, notebook, id, name) {
19 this.events = notebook.events;
27 this.events = notebook.events;
20
28
21 this.id = id;
29 this.id = id;
22 this.name = name;
30 this.name = name;
23
31
24 this.channels = {
32 this.channels = {
25 'shell': null,
33 'shell': null,
26 'iopub': null,
34 'iopub': null,
27 'stdin': null
35 'stdin': null
28 };
36 };
29
37
30 this.kernel_service_url = kernel_service_url;
38 this.kernel_service_url = kernel_service_url;
31 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
39 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
32 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
40 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
33 if (!this.ws_url) {
41 if (!this.ws_url) {
34 // trailing 's' in https will become wss for secure web sockets
42 // trailing 's' in https will become wss for secure web sockets
35 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
43 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
36 }
44 }
37
45
38 this.username = "username";
46 this.username = "username";
39 this.session_id = utils.uuid();
47 this.session_id = utils.uuid();
40 this._msg_callbacks = {};
48 this._msg_callbacks = {};
41
49
42 if (typeof(WebSocket) !== 'undefined') {
50 if (typeof(WebSocket) !== 'undefined') {
43 this.WebSocket = WebSocket;
51 this.WebSocket = WebSocket;
44 } else if (typeof(MozWebSocket) !== 'undefined') {
52 } else if (typeof(MozWebSocket) !== 'undefined') {
45 this.WebSocket = MozWebSocket;
53 this.WebSocket = MozWebSocket;
46 } else {
54 } else {
47 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.');
55 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.');
48 }
56 }
49
57
50 this.bind_events();
58 this.bind_events();
51 this.init_iopub_handlers();
59 this.init_iopub_handlers();
52 this.comm_manager = new comm.CommManager(this);
60 this.comm_manager = new comm.CommManager(this);
53 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
61 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
54
62
55 this.last_msg_id = null;
63 this.last_msg_id = null;
56 this.last_msg_callbacks = {};
64 this.last_msg_callbacks = {};
57 };
65 };
58
66
59
67 /**
68 * @function _get_msg
69 */
60 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
70 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
61 var msg = {
71 var msg = {
62 header : {
72 header : {
63 msg_id : utils.uuid(),
73 msg_id : utils.uuid(),
64 username : this.username,
74 username : this.username,
65 session : this.session_id,
75 session : this.session_id,
66 msg_type : msg_type,
76 msg_type : msg_type,
67 version : "5.0"
77 version : "5.0"
68 },
78 },
69 metadata : metadata || {},
79 metadata : metadata || {},
70 content : content,
80 content : content,
71 parent_header : {}
81 parent_header : {}
72 };
82 };
73 return msg;
83 return msg;
74 };
84 };
75
85
86 /**
87 * @function bind_events
88 */
76 Kernel.prototype.bind_events = function () {
89 Kernel.prototype.bind_events = function () {
77 var that = this;
90 var that = this;
78 this.events.on('send_input_reply.Kernel', function(evt, data) {
91 this.events.on('send_input_reply.Kernel', function(evt, data) {
79 that.send_input_reply(data);
92 that.send_input_reply(data);
80 });
93 });
81 };
94 };
82
95
83 // Initialize the iopub handlers
96 /**
84
97 * Initialize the iopub handlers.
98 *
99 * @function init_iopub_handlers
100 */
85 Kernel.prototype.init_iopub_handlers = function () {
101 Kernel.prototype.init_iopub_handlers = function () {
86 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
102 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
87 this._iopub_handlers = {};
103 this._iopub_handlers = {};
88 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
104 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
89 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
105 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
90
106
91 for (var i=0; i < output_msg_types.length; i++) {
107 for (var i=0; i < output_msg_types.length; i++) {
92 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
108 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
93 }
109 }
94 };
110 };
95
111
96 /**
112 /**
97 * GET /api/kernels
113 * GET /api/kernels
114 *
115 * Get the list of running kernels.
116 *
117 * @function list
118 * @param {function} [success] - function executed on ajax success
119 * @param {function} [error] - functon executed on ajax error
98 */
120 */
99 Kernel.prototype.list = function (success, error) {
121 Kernel.prototype.list = function (success, error) {
100 $.ajax(this.kernel_service_url, {
122 $.ajax(this.kernel_service_url, {
101 processData: false,
123 processData: false,
102 cache: false,
124 cache: false,
103 type: "GET",
125 type: "GET",
104 dataType: "json",
126 dataType: "json",
105 success: success,
127 success: success,
106 error: this._on_error(error)
128 error: this._on_error(error)
107 });
129 });
108 };
130 };
109
131
110 /**
132 /**
111 * POST /api/kernels
133 * POST /api/kernels
112 *
134 *
135 * Start a new kernel.
136 *
113 * In general this shouldn't be used -- the kernel should be
137 * In general this shouldn't be used -- the kernel should be
114 * started through the session API. If you use this function and
138 * started through the session API. If you use this function and
115 * are also using the session API then your session and kernel
139 * are also using the session API then your session and kernel
116 * WILL be out of sync!
140 * WILL be out of sync!
141 *
142 * @function start
143 * @param {params} [Object] - parameters to include in the query string
144 * @param {function} [success] - function executed on ajax success
145 * @param {function} [error] - functon executed on ajax error
117 */
146 */
118 Kernel.prototype.start = function (params, success, error) {
147 Kernel.prototype.start = function (params, success, error) {
119 var url = this.kernel_service_url;
148 var url = this.kernel_service_url;
120 var qs = $.param(params || {}); // query string for sage math stuff
149 var qs = $.param(params || {}); // query string for sage math stuff
121 if (qs !== "") {
150 if (qs !== "") {
122 url = url + "?" + qs;
151 url = url + "?" + qs;
123 }
152 }
124
153
125 var that = this;
154 var that = this;
126 var on_success = function (data, status, xhr) {
155 var on_success = function (data, status, xhr) {
127 that.id = data.id;
156 that.id = data.id;
128 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
157 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
129 that._kernel_started(data);
158 that._kernel_started();
130 if (success) {
159 if (success) {
131 success(data, status, xhr);
160 success(data, status, xhr);
132 }
161 }
133 };
162 };
134
163
135 $.ajax(url, {
164 $.ajax(url, {
136 processData: false,
165 processData: false,
137 cache: false,
166 cache: false,
138 type: "POST",
167 type: "POST",
139 data: JSON.stringify({name: this.name}),
168 data: JSON.stringify({name: this.name}),
140 dataType: "json",
169 dataType: "json",
141 success: this._on_success(on_success),
170 success: this._on_success(on_success),
142 error: this._on_error(error)
171 error: this._on_error(error)
143 });
172 });
144
173
145 return url;
174 return url;
146 };
175 };
147
176
148 /**
177 /**
149 * GET /api/kernels/[:kernel_id]
178 * GET /api/kernels/[:kernel_id]
179 *
180 * Get information about the kernel.
181 *
182 * @function get_info
183 * @param {function} [success] - function executed on ajax success
184 * @param {function} [error] - functon executed on ajax error
150 */
185 */
151 Kernel.prototype.get_info = function (success, error) {
186 Kernel.prototype.get_info = function (success, error) {
152 $.ajax(this.kernel_url, {
187 $.ajax(this.kernel_url, {
153 processData: false,
188 processData: false,
154 cache: false,
189 cache: false,
155 type: "GET",
190 type: "GET",
156 dataType: "json",
191 dataType: "json",
157 success: this._on_success(success),
192 success: this._on_success(success),
158 error: this._on_error(error)
193 error: this._on_error(error)
159 });
194 });
160 };
195 };
161
196
162 /**
197 /**
163 * DELETE /api/kernels/[:kernel_id]
198 * DELETE /api/kernels/[:kernel_id]
199 *
200 * Shutdown the kernel.
201 *
202 * If you are also using sessions, then this function shoul NOT be
203 * used. Instead, use Session.delete. Otherwise, the session and
204 * kernel WILL be out of sync.
205 *
206 * @function kill
207 * @param {function} [success] - function executed on ajax success
208 * @param {function} [error] - functon executed on ajax error
164 */
209 */
165 Kernel.prototype.kill = function (success, error) {
210 Kernel.prototype.kill = function (success, error) {
166 this._kernel_dead();
211 this._kernel_dead();
167 $.ajax(this.kernel_url, {
212 $.ajax(this.kernel_url, {
168 processData: false,
213 processData: false,
169 cache: false,
214 cache: false,
170 type: "DELETE",
215 type: "DELETE",
171 dataType: "json",
216 dataType: "json",
172 success: this._on_success(success),
217 success: this._on_success(success),
173 error: this._on_error(error)
218 error: this._on_error(error)
174 });
219 });
175 };
220 };
176
221
177 /**
222 /**
178 * POST /api/kernels/[:kernel_id]/interrupt
223 * POST /api/kernels/[:kernel_id]/interrupt
224 *
225 * Interrupt the kernel.
226 *
227 * @function interrupt
228 * @param {function} [success] - function executed on ajax success
229 * @param {function} [error] - functon executed on ajax error
179 */
230 */
180 Kernel.prototype.interrupt = function (success, error) {
231 Kernel.prototype.interrupt = function (success, error) {
181 this.events.trigger('status_interrupting.Kernel', {kernel: this});
232 this.events.trigger('status_interrupting.Kernel', {kernel: this});
182 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
233 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
183 $.ajax(url, {
234 $.ajax(url, {
184 processData: false,
235 processData: false,
185 cache: false,
236 cache: false,
186 type: "POST",
237 type: "POST",
187 dataType: "json",
238 dataType: "json",
188 success: this._on_success(success),
239 success: this._on_success(success),
189 error: this._on_error(error)
240 error: this._on_error(error)
190 });
241 });
191 };
242 };
192
243
193 /**
244 /**
194 * POST /api/kernels/[:kernel_id]/restart
245 * POST /api/kernels/[:kernel_id]/restart
246 *
247 * Restart the kernel.
248 *
249 * @function interrupt
250 * @param {function} [success] - function executed on ajax success
251 * @param {function} [error] - functon executed on ajax error
195 */
252 */
196 Kernel.prototype.restart = function (success, error) {
253 Kernel.prototype.restart = function (success, error) {
197 this.events.trigger('status_restarting.Kernel', {kernel: this});
254 this.events.trigger('status_restarting.Kernel', {kernel: this});
198 this.stop_channels();
255 this.stop_channels();
199
256
200 var that = this;
257 var that = this;
201 var on_success = function (data, status, xhr) {
258 var on_success = function (data, status, xhr) {
202 that._kernel_started(data, status, xhr);
259 that._kernel_started();
203 if (success) {
260 if (success) {
204 success(data, status, xhr);
261 success(data, status, xhr);
205 }
262 }
206 };
263 };
207
264
208 var url = utils.url_join_encode(this.kernel_url, 'restart');
265 var url = utils.url_join_encode(this.kernel_url, 'restart');
209 $.ajax(url, {
266 $.ajax(url, {
210 processData: false,
267 processData: false,
211 cache: false,
268 cache: false,
212 type: "POST",
269 type: "POST",
213 dataType: "json",
270 dataType: "json",
214 success: this._on_success(on_success),
271 success: this._on_success(on_success),
215 error: this._on_error(error)
272 error: this._on_error(error)
216 });
273 });
217 };
274 };
218
275
219 /**
276 /**
220 * Not actually a HTTP request, but useful function nonetheless
277 * Reconnect to a disconnected kernel. This is not actually a
221 * for reconnecting to the kernel if the connection is somehow lost
278 * standard HTTP request, but useful function nonetheless for
279 * reconnecting to the kernel if the connection is somehow lost.
280 *
281 * @function reconnect
222 */
282 */
223 Kernel.prototype.reconnect = function () {
283 Kernel.prototype.reconnect = function () {
224 this.events.trigger('status_reconnecting.Kernel');
284 this.events.trigger('status_reconnecting.Kernel');
225 var that = this;
285 var that = this;
226 setTimeout(function () {
286 setTimeout(function () {
227 that.start_channels();
287 that.start_channels();
228 }, 5000);
288 }, 5000);
229 };
289 };
230
290
291 /**
292 * Handle a successful AJAX request by updating the kernel id and
293 * name from the response, and then optionally calling a provided
294 * callback.
295 *
296 * @function _on_success
297 * @param {function} success - callback
298 */
231 Kernel.prototype._on_success = function (success) {
299 Kernel.prototype._on_success = function (success) {
232 var that = this;
300 var that = this;
233 return function (data, status, xhr) {
301 return function (data, status, xhr) {
234 if (data) {
302 if (data) {
235 that.id = data.id;
303 that.id = data.id;
236 that.name = data.name;
304 that.name = data.name;
237 }
305 }
238 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
306 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
239 if (success) {
307 if (success) {
240 success(data, status, xhr);
308 success(data, status, xhr);
241 }
309 }
242 };
310 };
243 };
311 };
244
312
313 /**
314 * Handle a failed AJAX request by logging the error message, and
315 * then optionally calling a provided callback.
316 *
317 * @function _on_error
318 * @param {function} error - callback
319 */
245 Kernel.prototype._on_error = function (error) {
320 Kernel.prototype._on_error = function (error) {
246 return function (xhr, status, err) {
321 return function (xhr, status, err) {
247 utils.log_ajax_error(xhr, status, err);
322 utils.log_ajax_error(xhr, status, err);
248 if (error) {
323 if (error) {
249 error(xhr, status, err);
324 error(xhr, status, err);
250 }
325 }
251 };
326 };
252 };
327 };
253
328
254 Kernel.prototype._kernel_started = function (json) {
329 /**
255 console.log("Kernel started: ", json.id);
330 * Perform necessary tasks once the kernel has been started. This
331 * includes triggering the 'status_started.Kernel' event and
332 * then actually connecting to the kernel.
333 *
334 * @function _kernel_started
335 */
336 Kernel.prototype._kernel_started = function () {
337 console.log("Kernel started: ", this.id);
256 this.events.trigger('status_started.Kernel', {kernel: this});
338 this.events.trigger('status_started.Kernel', {kernel: this});
257 this.start_channels();
339 this.start_channels();
258 };
340 };
259
341
342 /**
343 * Perform necessary tasks once the connection to the kernel has
344 * been established. This includes triggering the
345 * 'status_connected.Kernel' event and then requesting information
346 * about the kernel.
347 *
348 * @function _kernel_connected
349 */
260 Kernel.prototype._kernel_connected = function () {
350 Kernel.prototype._kernel_connected = function () {
261 var that = this;
351 var that = this;
262 console.log('Connected to kernel: ', this.id);
352 console.log('Connected to kernel: ', this.id);
263 this.events.trigger('status_connected.Kernel');
353 this.events.trigger('status_connected.Kernel');
264 this.kernel_info(function () {
354 this.kernel_info(function () {
265 that.events.trigger('status_idle.Kernel');
355 that.events.trigger('status_idle.Kernel');
266 });
356 });
267 };
357 };
268
358
359 /**
360 * Perform necessary tasks after the kernel has died. This
361 * includes triggering both 'status_dead.Kernel' and
362 * 'no_kernel.Kernel', and then closing communication channels to
363 * the kernel if they are still somehow open.
364 *
365 * @function _kernel_dead
366 */
269 Kernel.prototype._kernel_dead = function () {
367 Kernel.prototype._kernel_dead = function () {
270 this.events.trigger('status_dead.Kernel');
368 this.events.trigger('status_dead.Kernel');
271 this.events.trigger('no_kernel.Kernel');
369 this.events.trigger('no_kernel.Kernel');
272 this.stop_channels();
370 this.stop_channels();
273 };
371 };
274
372
275
276 /**
373 /**
277 * Start the `shell`and `iopub` channels.
374 * Start the `shell`and `iopub` channels.
278 * Will stop and restart them if they already exist.
375 * Will stop and restart them if they already exist.
279 *
376 *
280 * @method start_channels
377 * @function start_channels
281 */
378 */
282 Kernel.prototype.start_channels = function () {
379 Kernel.prototype.start_channels = function () {
283 var that = this;
380 var that = this;
284 this.stop_channels();
381 this.stop_channels();
285 var ws_host_url = this.ws_url + this.kernel_url;
382 var ws_host_url = this.ws_url + this.kernel_url;
286 console.log("Starting WebSockets:", ws_host_url);
383 console.log("Starting WebSockets:", ws_host_url);
287 this.channels.shell = new this.WebSocket(
384 this.channels.shell = new this.WebSocket(
288 this.ws_url + utils.url_join_encode(this.kernel_url, "shell")
385 this.ws_url + utils.url_join_encode(this.kernel_url, "shell")
289 );
386 );
290 this.channels.stdin = new this.WebSocket(
387 this.channels.stdin = new this.WebSocket(
291 this.ws_url + utils.url_join_encode(this.kernel_url, "stdin")
388 this.ws_url + utils.url_join_encode(this.kernel_url, "stdin")
292 );
389 );
293 this.channels.iopub = new this.WebSocket(
390 this.channels.iopub = new this.WebSocket(
294 this.ws_url + utils.url_join_encode(this.kernel_url, "iopub")
391 this.ws_url + utils.url_join_encode(this.kernel_url, "iopub")
295 );
392 );
296
393
297 var already_called_onclose = false; // only alert once
394 var already_called_onclose = false; // only alert once
298 var ws_closed_early = function(evt){
395 var ws_closed_early = function(evt){
299 if (already_called_onclose){
396 if (already_called_onclose){
300 return;
397 return;
301 }
398 }
302 already_called_onclose = true;
399 already_called_onclose = true;
303 if ( ! evt.wasClean ){
400 if ( ! evt.wasClean ){
304 that._ws_closed(ws_host_url, true);
401 that._ws_closed(ws_host_url, true);
305 }
402 }
306 };
403 };
307 var ws_closed_late = function(evt){
404 var ws_closed_late = function(evt){
308 if (already_called_onclose){
405 if (already_called_onclose){
309 return;
406 return;
310 }
407 }
311 already_called_onclose = true;
408 already_called_onclose = true;
312 if ( ! evt.wasClean ){
409 if ( ! evt.wasClean ){
313 that._ws_closed(ws_host_url, false);
410 that._ws_closed(ws_host_url, false);
314 }
411 }
315 };
412 };
316 var ws_error = function(evt){
413 var ws_error = function(evt){
317 if (already_called_onclose){
414 if (already_called_onclose){
318 return;
415 return;
319 }
416 }
320 already_called_onclose = true;
417 already_called_onclose = true;
321 that._ws_closed(ws_host_url, false);
418 that._ws_closed(ws_host_url, false);
322 };
419 };
323
420
324 for (var c in this.channels) {
421 for (var c in this.channels) {
325 this.channels[c].onopen = $.proxy(this._ws_opened, this);
422 this.channels[c].onopen = $.proxy(this._ws_opened, this);
326 this.channels[c].onclose = ws_closed_early;
423 this.channels[c].onclose = ws_closed_early;
327 this.channels[c].onerror = ws_error;
424 this.channels[c].onerror = ws_error;
328 }
425 }
329 // switch from early-close to late-close message after 1s
426 // switch from early-close to late-close message after 1s
330 setTimeout(function() {
427 setTimeout(function() {
331 for (var c in that.channels) {
428 for (var c in that.channels) {
332 if (that.channels[c] !== null) {
429 if (that.channels[c] !== null) {
333 that.channels[c].onclose = ws_closed_late;
430 that.channels[c].onclose = ws_closed_late;
334 }
431 }
335 }
432 }
336 }, 1000);
433 }, 1000);
337 this.channels.shell.onmessage = $.proxy(this._handle_shell_reply, this);
434 this.channels.shell.onmessage = $.proxy(this._handle_shell_reply, this);
338 this.channels.iopub.onmessage = $.proxy(this._handle_iopub_message, this);
435 this.channels.iopub.onmessage = $.proxy(this._handle_iopub_message, this);
339 this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this);
436 this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this);
340 };
437 };
341
438
342 /**
439 /**
343 * Handle a websocket entering the open state
440 * Handle a websocket entering the open state sends session and
344 * sends session and cookie authentication info as first message.
441 * cookie authentication info as first message.
345 * Once all sockets are open, signal the Kernel.status_started event.
442 *
346 * @method _ws_opened
443 * @function _ws_opened
347 */
444 */
348 Kernel.prototype._ws_opened = function (evt) {
445 Kernel.prototype._ws_opened = function (evt) {
349 // send the session id so the Session object Python-side
446 // send the session id so the Session object Python-side
350 // has the same identity
447 // has the same identity
351 evt.target.send(this.session_id + ':' + document.cookie);
448 evt.target.send(this.session_id + ':' + document.cookie);
352
449
353 if (this.is_connected()) {
450 if (this.is_connected()) {
354 // all events ready, trigger started event.
451 // all events ready, trigger started event.
355 this._kernel_connected();
452 this._kernel_connected();
356 }
453 }
357 };
454 };
358
455
456 /**
457 * Handle a websocket entering the closed state. This closes the
458 * other communication channels if they are open, and triggers the
459 * 'status_disconnected.Kernel' event. If the websocket was closed
460 * early, then also trigger 'early_disconnect.Kernel'. Otherwise,
461 * try to reconnect to the kernel.
462 *
463 * @function _ws_closed
464 * @param {string} ws_url - the websocket url
465 * @param {bool} early - whether the connection was closed early or not
466 */
359 Kernel.prototype._ws_closed = function(ws_url, early) {
467 Kernel.prototype._ws_closed = function(ws_url, early) {
360 this.stop_channels();
468 this.stop_channels();
361 this.events.trigger('status_disconnected.Kernel');
469 this.events.trigger('status_disconnected.Kernel');
362 if (!early) {
470 if (!early) {
363 this.reconnect();
471 this.reconnect();
364 } else {
472 } else {
365 console.log('WebSocket connection failed: ', ws_url);
473 console.log('WebSocket connection failed: ', ws_url);
366 this.events.trigger('early_disconnect.Kernel', ws_url);
474 this.events.trigger('early_disconnect.Kernel', ws_url);
367 }
475 }
368 };
476 };
369
477
370 /**
478 /**
371 * Stop the websocket channels.
479 * Close the websocket channels. After successful close, the value
372 * @method stop_channels
480 * in `this.channels[channel_name]` will be null.
481 *
482 * @function stop_channels
373 */
483 */
374 Kernel.prototype.stop_channels = function () {
484 Kernel.prototype.stop_channels = function () {
375 var that = this;
485 var that = this;
376 var close = function (c) {
486 var close = function (c) {
377 return function () {
487 return function () {
378 if (that.channels[c].readyState === WebSocket.CLOSED) {
488 if (that.channels[c].readyState === WebSocket.CLOSED) {
379 that.channels[c] = null;
489 that.channels[c] = null;
380 }
490 }
381 };
491 };
382 };
492 };
383 for (var c in this.channels) {
493 for (var c in this.channels) {
384 if ( this.channels[c] !== null ) {
494 if ( this.channels[c] !== null ) {
385 this.channels[c].onclose = close(c);
495 this.channels[c].onclose = close(c);
386 this.channels[c].close();
496 this.channels[c].close();
387 }
497 }
388 }
498 }
389 };
499 };
390
500
391 // Main public methods.
501 /**
392
502 * Check whether there is a connection to the kernel. This
503 * function only returns true if all channel objects have been
504 * created and have a state of WebSocket.OPEN.
505 *
506 * @function is_connected
507 * @returns {bool} - whether there is a connection
508 */
393 Kernel.prototype.is_connected = function () {
509 Kernel.prototype.is_connected = function () {
394 for (var c in this.channels) {
510 for (var c in this.channels) {
395 // if any channel is not ready, then we're not connected
511 // if any channel is not ready, then we're not connected
396 if (this.channels[c] === null) {
512 if (this.channels[c] === null) {
397 return false;
513 return false;
398 }
514 }
399 if (this.channels[c].readyState !== WebSocket.OPEN) {
515 if (this.channels[c].readyState !== WebSocket.OPEN) {
400 return false;
516 return false;
401 }
517 }
402 }
518 }
403 return true;
519 return true;
404 };
520 };
405
521
522 /**
523 * Check whether the connection to the kernel has been completely
524 * severed. This function only returns true if all channel objects
525 * are null.
526 *
527 * @function is_fully_disconnected
528 * @returns {bool} - whether the kernel is fully disconnected
529 */
406 Kernel.prototype.is_fully_disconnected = function () {
530 Kernel.prototype.is_fully_disconnected = function () {
407 for (var c in this.channels) {
531 for (var c in this.channels) {
408 if (this.channels[c] === null) {
532 if (this.channels[c] === null) {
409 return true;
533 return true;
410 }
534 }
411 }
535 }
412 return false;
536 return false;
413 };
537 };
414
538
415 // send a message on the Kernel's shell channel
539 /**
540 * Send a message on the Kernel's shell channel
541 *
542 * @function send_shell_message
543 */
416 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
544 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
417 if (!this.is_connected()) {
545 if (!this.is_connected()) {
418 throw new Error("kernel is not connected");
546 throw new Error("kernel is not connected");
419 }
547 }
420 var msg = this._get_msg(msg_type, content, metadata);
548 var msg = this._get_msg(msg_type, content, metadata);
421 this.channels.shell.send(JSON.stringify(msg));
549 this.channels.shell.send(JSON.stringify(msg));
422 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
550 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
423 return msg.header.msg_id;
551 return msg.header.msg_id;
424 };
552 };
425
553
426 /**
554 /**
427 * Get kernel info
555 * Get kernel info
428 *
556 *
557 * @function kernel_info
429 * @param callback {function}
558 * @param callback {function}
430 * @method kernel_info
431 *
559 *
432 * When calling this method, pass a callback function that expects one argument.
560 * When calling this method, pass a callback function that expects one argument.
433 * The callback will be passed the complete `kernel_info_reply` message documented
561 * The callback will be passed the complete `kernel_info_reply` message documented
434 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
562 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
435 */
563 */
436 Kernel.prototype.kernel_info = function (callback) {
564 Kernel.prototype.kernel_info = function (callback) {
437 var callbacks;
565 var callbacks;
438 if (callback) {
566 if (callback) {
439 callbacks = { shell : { reply : callback } };
567 callbacks = { shell : { reply : callback } };
440 }
568 }
441 return this.send_shell_message("kernel_info_request", {}, callbacks);
569 return this.send_shell_message("kernel_info_request", {}, callbacks);
442 };
570 };
443
571
444 /**
572 /**
445 * Get info on an object
573 * Get info on an object
446 *
574 *
447 * @param code {string}
448 * @param cursor_pos {integer}
449 * @param callback {function}
450 * @method inspect
451 *
452 * When calling this method, pass a callback function that expects one argument.
575 * When calling this method, pass a callback function that expects one argument.
453 * The callback will be passed the complete `inspect_reply` message documented
576 * The callback will be passed the complete `inspect_reply` message documented
454 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
577 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
578 *
579 * @function inspect
580 * @param code {string}
581 * @param cursor_pos {integer}
582 * @param callback {function}
455 */
583 */
456 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
584 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
457 var callbacks;
585 var callbacks;
458 if (callback) {
586 if (callback) {
459 callbacks = { shell : { reply : callback } };
587 callbacks = { shell : { reply : callback } };
460 }
588 }
461
589
462 var content = {
590 var content = {
463 code : code,
591 code : code,
464 cursor_pos : cursor_pos,
592 cursor_pos : cursor_pos,
465 detail_level : 0
593 detail_level : 0
466 };
594 };
467 return this.send_shell_message("inspect_request", content, callbacks);
595 return this.send_shell_message("inspect_request", content, callbacks);
468 };
596 };
469
597
470 /**
598 /**
471 * Execute given code into kernel, and pass result to callback.
599 * Execute given code into kernel, and pass result to callback.
472 *
600 *
473 * @async
601 * @async
474 * @method execute
602 * @function execute
475 * @param {string} code
603 * @param {string} code
476 * @param [callbacks] {Object} With the following keys (all optional)
604 * @param [callbacks] {Object} With the following keys (all optional)
477 * @param callbacks.shell.reply {function}
605 * @param callbacks.shell.reply {function}
478 * @param callbacks.shell.payload.[payload_name] {function}
606 * @param callbacks.shell.payload.[payload_name] {function}
479 * @param callbacks.iopub.output {function}
607 * @param callbacks.iopub.output {function}
480 * @param callbacks.iopub.clear_output {function}
608 * @param callbacks.iopub.clear_output {function}
481 * @param callbacks.input {function}
609 * @param callbacks.input {function}
482 * @param {object} [options]
610 * @param {object} [options]
483 * @param [options.silent=false] {Boolean}
611 * @param [options.silent=false] {Boolean}
484 * @param [options.user_expressions=empty_dict] {Dict}
612 * @param [options.user_expressions=empty_dict] {Dict}
485 * @param [options.allow_stdin=false] {Boolean} true|false
613 * @param [options.allow_stdin=false] {Boolean} true|false
486 *
614 *
487 * @example
615 * @example
488 *
616 *
489 * The options object should contain the options for the execute call. Its default
617 * The options object should contain the options for the execute
490 * values are:
618 * call. Its default values are:
491 *
619 *
492 * options = {
620 * options = {
493 * silent : true,
621 * silent : true,
494 * user_expressions : {},
622 * user_expressions : {},
495 * allow_stdin : false
623 * allow_stdin : false
496 * }
624 * }
497 *
625 *
498 * When calling this method pass a callbacks structure of the form:
626 * When calling this method pass a callbacks structure of the
627 * form:
499 *
628 *
500 * callbacks = {
629 * callbacks = {
501 * shell : {
630 * shell : {
502 * reply : execute_reply_callback,
631 * reply : execute_reply_callback,
503 * payload : {
632 * payload : {
504 * set_next_input : set_next_input_callback,
633 * set_next_input : set_next_input_callback,
505 * }
634 * }
506 * },
635 * },
507 * iopub : {
636 * iopub : {
508 * output : output_callback,
637 * output : output_callback,
509 * clear_output : clear_output_callback,
638 * clear_output : clear_output_callback,
510 * },
639 * },
511 * input : raw_input_callback
640 * input : raw_input_callback
512 * }
641 * }
513 *
642 *
514 * Each callback will be passed the entire message as a single arugment.
643 * Each callback will be passed the entire message as a single
515 * Payload handlers will be passed the corresponding payload and the execute_reply message.
644 * arugment. Payload handlers will be passed the corresponding
645 * payload and the execute_reply message.
516 */
646 */
517 Kernel.prototype.execute = function (code, callbacks, options) {
647 Kernel.prototype.execute = function (code, callbacks, options) {
518 var content = {
648 var content = {
519 code : code,
649 code : code,
520 silent : true,
650 silent : true,
521 store_history : false,
651 store_history : false,
522 user_expressions : {},
652 user_expressions : {},
523 allow_stdin : false
653 allow_stdin : false
524 };
654 };
525 callbacks = callbacks || {};
655 callbacks = callbacks || {};
526 if (callbacks.input !== undefined) {
656 if (callbacks.input !== undefined) {
527 content.allow_stdin = true;
657 content.allow_stdin = true;
528 }
658 }
529 $.extend(true, content, options);
659 $.extend(true, content, options);
530 this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
660 this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
531 return this.send_shell_message("execute_request", content, callbacks);
661 return this.send_shell_message("execute_request", content, callbacks);
532 };
662 };
533
663
534 /**
664 /**
535 * When calling this method, pass a function to be called with the `complete_reply` message
665 * When calling this method, pass a function to be called with the
536 * as its only argument when it arrives.
666 * `complete_reply` message as its only argument when it arrives.
537 *
667 *
538 * `complete_reply` is documented
668 * `complete_reply` is documented
539 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
669 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
540 *
670 *
541 * @method complete
671 * @function complete
542 * @param code {string}
672 * @param code {string}
543 * @param cursor_pos {integer}
673 * @param cursor_pos {integer}
544 * @param callback {function}
674 * @param callback {function}
545 *
546 */
675 */
547 Kernel.prototype.complete = function (code, cursor_pos, callback) {
676 Kernel.prototype.complete = function (code, cursor_pos, callback) {
548 var callbacks;
677 var callbacks;
549 if (callback) {
678 if (callback) {
550 callbacks = { shell : { reply : callback } };
679 callbacks = { shell : { reply : callback } };
551 }
680 }
552 var content = {
681 var content = {
553 code : code,
682 code : code,
554 cursor_pos : cursor_pos
683 cursor_pos : cursor_pos
555 };
684 };
556 return this.send_shell_message("complete_request", content, callbacks);
685 return this.send_shell_message("complete_request", content, callbacks);
557 };
686 };
558
687
688 /**
689 * @function send_input_reply
690 */
559 Kernel.prototype.send_input_reply = function (input) {
691 Kernel.prototype.send_input_reply = function (input) {
560 if (!this.is_connected()) {
692 if (!this.is_connected()) {
561 throw new Error("kernel is not connected");
693 throw new Error("kernel is not connected");
562 }
694 }
563 var content = {
695 var content = {
564 value : input
696 value : input
565 };
697 };
566 this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
698 this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
567 var msg = this._get_msg("input_reply", content);
699 var msg = this._get_msg("input_reply", content);
568 this.channels.stdin.send(JSON.stringify(msg));
700 this.channels.stdin.send(JSON.stringify(msg));
569 return msg.header.msg_id;
701 return msg.header.msg_id;
570 };
702 };
571
703
572
704 /**
573 // Reply handlers
705 * @function register_iopub_handler
574
706 */
575 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
707 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
576 this._iopub_handlers[msg_type] = callback;
708 this._iopub_handlers[msg_type] = callback;
577 };
709 };
578
710
711 /**
712 * Get the iopub handler for a specific message type.
713 *
714 * @function get_iopub_handler
715 */
579 Kernel.prototype.get_iopub_handler = function (msg_type) {
716 Kernel.prototype.get_iopub_handler = function (msg_type) {
580 // get iopub handler for a specific message type
581 return this._iopub_handlers[msg_type];
717 return this._iopub_handlers[msg_type];
582 };
718 };
583
719
584
720 /**
721 * Get callbacks for a specific message.
722 *
723 * @function get_callbacks_for_msg
724 */
585 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
725 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
586 // get callbacks for a specific message
587 if (msg_id == this.last_msg_id) {
726 if (msg_id == this.last_msg_id) {
588 return this.last_msg_callbacks;
727 return this.last_msg_callbacks;
589 } else {
728 } else {
590 return this._msg_callbacks[msg_id];
729 return this._msg_callbacks[msg_id];
591 }
730 }
592 };
731 };
593
732
594
733 /**
734 * Clear callbacks for a specific message.
735 *
736 * @function clear_callbacks_for_msg
737 */
595 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
738 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
596 if (this._msg_callbacks[msg_id] !== undefined ) {
739 if (this._msg_callbacks[msg_id] !== undefined ) {
597 delete this._msg_callbacks[msg_id];
740 delete this._msg_callbacks[msg_id];
598 }
741 }
599 };
742 };
600
743
744 /**
745 * @function _finish_shell
746 */
601 Kernel.prototype._finish_shell = function (msg_id) {
747 Kernel.prototype._finish_shell = function (msg_id) {
602 var callbacks = this._msg_callbacks[msg_id];
748 var callbacks = this._msg_callbacks[msg_id];
603 if (callbacks !== undefined) {
749 if (callbacks !== undefined) {
604 callbacks.shell_done = true;
750 callbacks.shell_done = true;
605 if (callbacks.iopub_done) {
751 if (callbacks.iopub_done) {
606 this.clear_callbacks_for_msg(msg_id);
752 this.clear_callbacks_for_msg(msg_id);
607 }
753 }
608 }
754 }
609 };
755 };
610
756
757 /**
758 * @function _finish_iopub
759 */
611 Kernel.prototype._finish_iopub = function (msg_id) {
760 Kernel.prototype._finish_iopub = function (msg_id) {
612 var callbacks = this._msg_callbacks[msg_id];
761 var callbacks = this._msg_callbacks[msg_id];
613 if (callbacks !== undefined) {
762 if (callbacks !== undefined) {
614 callbacks.iopub_done = true;
763 callbacks.iopub_done = true;
615 if (callbacks.shell_done) {
764 if (callbacks.shell_done) {
616 this.clear_callbacks_for_msg(msg_id);
765 this.clear_callbacks_for_msg(msg_id);
617 }
766 }
618 }
767 }
619 };
768 };
620
769
621 /* Set callbacks for a particular message.
770 /**
771 * Set callbacks for a particular message.
622 * Callbacks should be a struct of the following form:
772 * Callbacks should be a struct of the following form:
623 * shell : {
773 * shell : {
624 *
774 *
625 * }
775 * }
626
776 *
777 * @function set_callbacks_for_msg
627 */
778 */
628 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
779 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
629 this.last_msg_id = msg_id;
780 this.last_msg_id = msg_id;
630 if (callbacks) {
781 if (callbacks) {
631 // shallow-copy mapping, because we will modify it at the top level
782 // shallow-copy mapping, because we will modify it at the top level
632 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
783 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
633 cbcopy.shell = callbacks.shell;
784 cbcopy.shell = callbacks.shell;
634 cbcopy.iopub = callbacks.iopub;
785 cbcopy.iopub = callbacks.iopub;
635 cbcopy.input = callbacks.input;
786 cbcopy.input = callbacks.input;
636 cbcopy.shell_done = (!callbacks.shell);
787 cbcopy.shell_done = (!callbacks.shell);
637 cbcopy.iopub_done = (!callbacks.iopub);
788 cbcopy.iopub_done = (!callbacks.iopub);
638 } else {
789 } else {
639 this.last_msg_callbacks = {};
790 this.last_msg_callbacks = {};
640 }
791 }
641 };
792 };
642
793
643
794 /**
795 * @function _handle_shell_reply
796 */
644 Kernel.prototype._handle_shell_reply = function (e) {
797 Kernel.prototype._handle_shell_reply = function (e) {
645 var reply = $.parseJSON(e.data);
798 var reply = $.parseJSON(e.data);
646 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
799 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
647 var content = reply.content;
800 var content = reply.content;
648 var metadata = reply.metadata;
801 var metadata = reply.metadata;
649 var parent_id = reply.parent_header.msg_id;
802 var parent_id = reply.parent_header.msg_id;
650 var callbacks = this.get_callbacks_for_msg(parent_id);
803 var callbacks = this.get_callbacks_for_msg(parent_id);
651 if (!callbacks || !callbacks.shell) {
804 if (!callbacks || !callbacks.shell) {
652 return;
805 return;
653 }
806 }
654 var shell_callbacks = callbacks.shell;
807 var shell_callbacks = callbacks.shell;
655
808
656 // signal that shell callbacks are done
809 // signal that shell callbacks are done
657 this._finish_shell(parent_id);
810 this._finish_shell(parent_id);
658
811
659 if (shell_callbacks.reply !== undefined) {
812 if (shell_callbacks.reply !== undefined) {
660 shell_callbacks.reply(reply);
813 shell_callbacks.reply(reply);
661 }
814 }
662 if (content.payload && shell_callbacks.payload) {
815 if (content.payload && shell_callbacks.payload) {
663 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
816 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
664 }
817 }
665 };
818 };
666
819
667
820 /**
821 * @function _handle_payloads
822 */
668 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
823 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
669 var l = payloads.length;
824 var l = payloads.length;
670 // Payloads are handled by triggering events because we don't want the Kernel
825 // Payloads are handled by triggering events because we don't want the Kernel
671 // to depend on the Notebook or Pager classes.
826 // to depend on the Notebook or Pager classes.
672 for (var i=0; i<l; i++) {
827 for (var i=0; i<l; i++) {
673 var payload = payloads[i];
828 var payload = payloads[i];
674 var callback = payload_callbacks[payload.source];
829 var callback = payload_callbacks[payload.source];
675 if (callback) {
830 if (callback) {
676 callback(payload, msg);
831 callback(payload, msg);
677 }
832 }
678 }
833 }
679 };
834 };
680
835
836 /**
837 * @function _handle_status_message
838 */
681 Kernel.prototype._handle_status_message = function (msg) {
839 Kernel.prototype._handle_status_message = function (msg) {
682 var execution_state = msg.content.execution_state;
840 var execution_state = msg.content.execution_state;
683 var parent_id = msg.parent_header.msg_id;
841 var parent_id = msg.parent_header.msg_id;
684
842
685 // dispatch status msg callbacks, if any
843 // dispatch status msg callbacks, if any
686 var callbacks = this.get_callbacks_for_msg(parent_id);
844 var callbacks = this.get_callbacks_for_msg(parent_id);
687 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
845 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
688 try {
846 try {
689 callbacks.iopub.status(msg);
847 callbacks.iopub.status(msg);
690 } catch (e) {
848 } catch (e) {
691 console.log("Exception in status msg handler", e, e.stack);
849 console.log("Exception in status msg handler", e, e.stack);
692 }
850 }
693 }
851 }
694
852
695 if (execution_state === 'busy') {
853 if (execution_state === 'busy') {
696 this.events.trigger('status_busy.Kernel', {kernel: this});
854 this.events.trigger('status_busy.Kernel', {kernel: this});
697
855
698 } else if (execution_state === 'idle') {
856 } else if (execution_state === 'idle') {
699 // signal that iopub callbacks are (probably) done
857 // signal that iopub callbacks are (probably) done
700 // async output may still arrive,
858 // async output may still arrive,
701 // but only for the most recent request
859 // but only for the most recent request
702 this._finish_iopub(parent_id);
860 this._finish_iopub(parent_id);
703
861
704 // trigger status_idle event
862 // trigger status_idle event
705 this.events.trigger('status_idle.Kernel', {kernel: this});
863 this.events.trigger('status_idle.Kernel', {kernel: this});
706
864
707 } else if (execution_state === 'restarting') {
865 } else if (execution_state === 'restarting') {
708 // autorestarting is distinct from restarting,
866 // autorestarting is distinct from restarting,
709 // in that it means the kernel died and the server is restarting it.
867 // in that it means the kernel died and the server is restarting it.
710 // status_restarting sets the notification widget,
868 // status_restarting sets the notification widget,
711 // autorestart shows the more prominent dialog.
869 // autorestart shows the more prominent dialog.
712 this.events.trigger('status_autorestarting.Kernel', {kernel: this});
870 this.events.trigger('status_autorestarting.Kernel', {kernel: this});
713 this.events.trigger('status_restarting.Kernel', {kernel: this});
871 this.events.trigger('status_restarting.Kernel', {kernel: this});
714
872
715 } else if (execution_state === 'dead') {
873 } else if (execution_state === 'dead') {
716 this._kernel_dead();
874 this._kernel_dead();
717 }
875 }
718 };
876 };
719
877
720
878 /**
721 // handle clear_output message
879 * Handle clear_output message
880 *
881 * @function _handle_clear_output
882 */
722 Kernel.prototype._handle_clear_output = function (msg) {
883 Kernel.prototype._handle_clear_output = function (msg) {
723 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
884 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
724 if (!callbacks || !callbacks.iopub) {
885 if (!callbacks || !callbacks.iopub) {
725 return;
886 return;
726 }
887 }
727 var callback = callbacks.iopub.clear_output;
888 var callback = callbacks.iopub.clear_output;
728 if (callback) {
889 if (callback) {
729 callback(msg);
890 callback(msg);
730 }
891 }
731 };
892 };
732
893
733
894 /**
734 // handle an output message (execute_result, display_data, etc.)
895 * handle an output message (execute_result, display_data, etc.)
896 *
897 * @function _handle_output_message
898 */
735 Kernel.prototype._handle_output_message = function (msg) {
899 Kernel.prototype._handle_output_message = function (msg) {
736 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
900 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
737 if (!callbacks || !callbacks.iopub) {
901 if (!callbacks || !callbacks.iopub) {
738 return;
902 return;
739 }
903 }
740 var callback = callbacks.iopub.output;
904 var callback = callbacks.iopub.output;
741 if (callback) {
905 if (callback) {
742 callback(msg);
906 callback(msg);
743 }
907 }
744 };
908 };
745
909
746 // dispatch IOPub messages to respective handlers.
910 /**
747 // each message type should have a handler.
911 * Dispatch IOPub messages to respective handlers. Each message
912 * type should have a handler.
913 *
914 * @function _handle_iopub_message
915 */
748 Kernel.prototype._handle_iopub_message = function (e) {
916 Kernel.prototype._handle_iopub_message = function (e) {
749 var msg = $.parseJSON(e.data);
917 var msg = $.parseJSON(e.data);
750
918
751 var handler = this.get_iopub_handler(msg.header.msg_type);
919 var handler = this.get_iopub_handler(msg.header.msg_type);
752 if (handler !== undefined) {
920 if (handler !== undefined) {
753 handler(msg);
921 handler(msg);
754 }
922 }
755 };
923 };
756
924
757
925 /**
926 * @function _handle_input_request
927 */
758 Kernel.prototype._handle_input_request = function (e) {
928 Kernel.prototype._handle_input_request = function (e) {
759 var request = $.parseJSON(e.data);
929 var request = $.parseJSON(e.data);
760 var header = request.header;
930 var header = request.header;
761 var content = request.content;
931 var content = request.content;
762 var metadata = request.metadata;
932 var metadata = request.metadata;
763 var msg_type = header.msg_type;
933 var msg_type = header.msg_type;
764 if (msg_type !== 'input_request') {
934 if (msg_type !== 'input_request') {
765 console.log("Invalid input request!", request);
935 console.log("Invalid input request!", request);
766 return;
936 return;
767 }
937 }
768 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
938 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
769 if (callbacks) {
939 if (callbacks) {
770 if (callbacks.input) {
940 if (callbacks.input) {
771 callbacks.input(request);
941 callbacks.input(request);
772 }
942 }
773 }
943 }
774 };
944 };
775
945
776 // Backwards compatability.
946 // Backwards compatability.
777 IPython.Kernel = Kernel;
947 IPython.Kernel = Kernel;
778
948
779 return {'Kernel': Kernel};
949 return {'Kernel': Kernel};
780 });
950 });
@@ -1,279 +1,279
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'services/kernels/js/kernel',
8 'services/kernels/js/kernel',
9 ], function(IPython, $, utils, kernel) {
9 ], function(IPython, $, utils, kernel) {
10 "use strict";
10 "use strict";
11
11
12 /**
12 /**
13 * Session object for accessing the session REST api. The session
13 * Session object for accessing the session REST api. The session
14 * should be used to start kernels and then shut them down -- for
14 * should be used to start kernels and then shut them down -- for
15 * all other operations, the kernel object should be used.
15 * all other operations, the kernel object should be used.
16 *
16 *
17 * Options should include:
17 * Options should include:
18 * - notebook_name: the notebook name
18 * - notebook_name: the notebook name
19 * - notebook_path: the path (not including name) to the notebook
19 * - notebook_path: the path (not including name) to the notebook
20 * - kernel_name: the type of kernel (e.g. python3)
20 * - kernel_name: the type of kernel (e.g. python3)
21 * - base_url: the root url of the notebook server
21 * - base_url: the root url of the notebook server
22 * - ws_url: the url to access websockets
22 * - ws_url: the url to access websockets
23 * - notebook: Notebook object
23 * - notebook: Notebook object
24 *
24 *
25 * @class Session
25 * @class Session
26 * @param {Object} options
26 * @param {Object} options
27 */
27 */
28 var Session = function (options) {
28 var Session = function (options) {
29 this.id = null;
29 this.id = null;
30 this.notebook_model = {
30 this.notebook_model = {
31 name: options.notebook_name,
31 name: options.notebook_name,
32 path: options.notebook_path
32 path: options.notebook_path
33 };
33 };
34 this.kernel_model = {
34 this.kernel_model = {
35 id: null,
35 id: null,
36 name: options.kernel_name
36 name: options.kernel_name
37 };
37 };
38
38
39 this.base_url = options.base_url;
39 this.base_url = options.base_url;
40 this.ws_url = options.ws_url;
40 this.ws_url = options.ws_url;
41 this.session_service_url = utils.url_join_encode(this.base_url, 'api/sessions');
41 this.session_service_url = utils.url_join_encode(this.base_url, 'api/sessions');
42 this.session_url = null;
42 this.session_url = null;
43
43
44 this.notebook = options.notebook;
44 this.notebook = options.notebook;
45 this.kernel = null;
45 this.kernel = null;
46 this.events = options.notebook.events;
46 this.events = options.notebook.events;
47 };
47 };
48
48
49 // Public REST api functions
49 // Public REST api functions
50
50
51 /**
51 /**
52 * GET /api/sessions
52 * GET /api/sessions
53 *
53 *
54 * Get a list of the current sessions.
54 * Get a list of the current sessions.
55 *
55 *
56 * @function list
56 * @function list
57 * @param {function} [success] - function executed on ajax success
57 * @param {function} [success] - function executed on ajax success
58 * @param {function} [error] - functon executed on ajax error
58 * @param {function} [error] - functon executed on ajax error
59 */
59 */
60 Session.prototype.list = function (success, error) {
60 Session.prototype.list = function (success, error) {
61 $.ajax(this.session_service_url, {
61 $.ajax(this.session_service_url, {
62 processData: false,
62 processData: false,
63 cache: false,
63 cache: false,
64 type: "GET",
64 type: "GET",
65 dataType: "json",
65 dataType: "json",
66 success: success,
66 success: success,
67 error: this._on_error(error)
67 error: this._on_error(error)
68 });
68 });
69 };
69 };
70
70
71 /**
71 /**
72 * POST /api/sessions
72 * POST /api/sessions
73 *
73 *
74 * Start a new session. This function can only executed once.
74 * Start a new session. This function can only executed once.
75 *
75 *
76 * @function start
76 * @function start
77 * @param {function} [success] - function executed on ajax success
77 * @param {function} [success] - function executed on ajax success
78 * @param {function} [error] - functon executed on ajax error
78 * @param {function} [error] - functon executed on ajax error
79 */
79 */
80 Session.prototype.start = function (success, error) {
80 Session.prototype.start = function (success, error) {
81 if (this.kernel !== null) {
81 if (this.kernel !== null) {
82 throw new Error("session has already been started");
82 throw new Error("session has already been started");
83 };
83 };
84
84
85 var that = this;
85 var that = this;
86 var on_success = function (data, status, xhr) {
86 var on_success = function (data, status, xhr) {
87 var kernel_service_url = utils.url_path_join(that.base_url, "api/kernels");
87 var kernel_service_url = utils.url_path_join(that.base_url, "api/kernels");
88 that.kernel = new kernel.Kernel(
88 that.kernel = new kernel.Kernel(
89 kernel_service_url, that.ws_url, that.notebook,
89 kernel_service_url, that.ws_url, that.notebook,
90 that.kernel_model.id, that.kernel_model.name);
90 that.kernel_model.id, that.kernel_model.name);
91 that.kernel._kernel_started(data.kernel);
91 that.kernel._kernel_started();
92 if (success) {
92 if (success) {
93 success(data, status, xhr);
93 success(data, status, xhr);
94 }
94 }
95 };
95 };
96 var on_error = function (xhr, status, err) {
96 var on_error = function (xhr, status, err) {
97 that.events.trigger('no_kernel.Kernel');
97 that.events.trigger('no_kernel.Kernel');
98 if (error) {
98 if (error) {
99 error(xhr, status, err);
99 error(xhr, status, err);
100 }
100 }
101 };
101 };
102
102
103 $.ajax(this.session_service_url, {
103 $.ajax(this.session_service_url, {
104 processData: false,
104 processData: false,
105 cache: false,
105 cache: false,
106 type: "POST",
106 type: "POST",
107 data: JSON.stringify(this._get_model()),
107 data: JSON.stringify(this._get_model()),
108 dataType: "json",
108 dataType: "json",
109 success: this._on_success(on_success),
109 success: this._on_success(on_success),
110 error: this._on_error(on_error)
110 error: this._on_error(on_error)
111 });
111 });
112 };
112 };
113
113
114 /**
114 /**
115 * GET /api/sessions/[:session_id]
115 * GET /api/sessions/[:session_id]
116 *
116 *
117 * Get information about a session.
117 * Get information about a session.
118 *
118 *
119 * @function get_info
119 * @function get_info
120 * @param {function} [success] - function executed on ajax success
120 * @param {function} [success] - function executed on ajax success
121 * @param {function} [error] - functon executed on ajax error
121 * @param {function} [error] - functon executed on ajax error
122 */
122 */
123 Session.prototype.get_info = function (success, error) {
123 Session.prototype.get_info = function (success, error) {
124 $.ajax(this.session_url, {
124 $.ajax(this.session_url, {
125 processData: false,
125 processData: false,
126 cache: false,
126 cache: false,
127 type: "GET",
127 type: "GET",
128 dataType: "json",
128 dataType: "json",
129 success: this._on_success(success),
129 success: this._on_success(success),
130 error: this._on_error(error)
130 error: this._on_error(error)
131 });
131 });
132 };
132 };
133
133
134 /**
134 /**
135 * PATCH /api/sessions/[:session_id]
135 * PATCH /api/sessions/[:session_id]
136 *
136 *
137 * Rename or move a notebook. If the given name or path are
137 * Rename or move a notebook. If the given name or path are
138 * undefined, then they will not be changed.
138 * undefined, then they will not be changed.
139 *
139 *
140 * @function rename_notebook
140 * @function rename_notebook
141 * @param {string} [name] - new notebook name
141 * @param {string} [name] - new notebook name
142 * @param {string} [path] - new path to notebook
142 * @param {string} [path] - new path to notebook
143 * @param {function} [success] - function executed on ajax success
143 * @param {function} [success] - function executed on ajax success
144 * @param {function} [error] - functon executed on ajax error
144 * @param {function} [error] - functon executed on ajax error
145 */
145 */
146 Session.prototype.rename_notebook = function (name, path, success, error) {
146 Session.prototype.rename_notebook = function (name, path, success, error) {
147 if (name !== undefined) {
147 if (name !== undefined) {
148 this.notebook_model.name = name;
148 this.notebook_model.name = name;
149 }
149 }
150 if (path !== undefined) {
150 if (path !== undefined) {
151 this.notebook_model.path = path;
151 this.notebook_model.path = path;
152 }
152 }
153
153
154 $.ajax(this.session_url, {
154 $.ajax(this.session_url, {
155 processData: false,
155 processData: false,
156 cache: false,
156 cache: false,
157 type: "PATCH",
157 type: "PATCH",
158 data: JSON.stringify(this._get_model()),
158 data: JSON.stringify(this._get_model()),
159 dataType: "json",
159 dataType: "json",
160 success: this._on_success(success),
160 success: this._on_success(success),
161 error: this._on_error(error)
161 error: this._on_error(error)
162 });
162 });
163 };
163 };
164
164
165 /**
165 /**
166 * DELETE /api/sessions/[:session_id]
166 * DELETE /api/sessions/[:session_id]
167 *
167 *
168 * Kill the kernel and shutdown the session.
168 * Kill the kernel and shutdown the session.
169 *
169 *
170 * @function delete
170 * @function delete
171 * @param {function} [success] - function executed on ajax success
171 * @param {function} [success] - function executed on ajax success
172 * @param {function} [error] - functon executed on ajax error
172 * @param {function} [error] - functon executed on ajax error
173 */
173 */
174 Session.prototype.delete = function (success, error) {
174 Session.prototype.delete = function (success, error) {
175 if (this.kernel) {
175 if (this.kernel) {
176 this.kernel._kernel_dead();
176 this.kernel._kernel_dead();
177 }
177 }
178
178
179 $.ajax(this.session_url, {
179 $.ajax(this.session_url, {
180 processData: false,
180 processData: false,
181 cache: false,
181 cache: false,
182 type: "DELETE",
182 type: "DELETE",
183 dataType: "json",
183 dataType: "json",
184 success: this._on_success(success),
184 success: this._on_success(success),
185 error: this._on_error(error)
185 error: this._on_error(error)
186 });
186 });
187 };
187 };
188
188
189 // Helper functions
189 // Helper functions
190
190
191 /**
191 /**
192 * Get the data model for the session, which includes the notebook
192 * Get the data model for the session, which includes the notebook
193 * (name and path) and kernel (name and id).
193 * (name and path) and kernel (name and id).
194 *
194 *
195 * @function _get_model
195 * @function _get_model
196 * @returns {Object} - the data model
196 * @returns {Object} - the data model
197 */
197 */
198 Session.prototype._get_model = function () {
198 Session.prototype._get_model = function () {
199 return {
199 return {
200 notebook: this.notebook_model,
200 notebook: this.notebook_model,
201 kernel: this.kernel_model
201 kernel: this.kernel_model
202 };
202 };
203 };
203 };
204
204
205 /**
205 /**
206 * Update the data model from the given JSON object, which should
206 * Update the data model from the given JSON object, which should
207 * have attributes of `id`, `notebook`, and/or `kernel`. If
207 * have attributes of `id`, `notebook`, and/or `kernel`. If
208 * provided, the notebook data must include name and path, and the
208 * provided, the notebook data must include name and path, and the
209 * kernel data must include name and id.
209 * kernel data must include name and id.
210 *
210 *
211 * @function _update_model
211 * @function _update_model
212 * @param {Object} data - updated data model
212 * @param {Object} data - updated data model
213 */
213 */
214 Session.prototype._update_model = function (data) {
214 Session.prototype._update_model = function (data) {
215 if (data && data.id) {
215 if (data && data.id) {
216 this.id = data.id;
216 this.id = data.id;
217 this.session_url = utils.url_join_encode(this.session_service_url, this.id);
217 this.session_url = utils.url_join_encode(this.session_service_url, this.id);
218 }
218 }
219 if (data && data.notebook) {
219 if (data && data.notebook) {
220 this.notebook_model.name = data.notebook.name;
220 this.notebook_model.name = data.notebook.name;
221 this.notebook_model.path = data.notebook.path;
221 this.notebook_model.path = data.notebook.path;
222 }
222 }
223 if (data && data.kernel) {
223 if (data && data.kernel) {
224 this.kernel_model.name = data.kernel.name;
224 this.kernel_model.name = data.kernel.name;
225 this.kernel_model.id = data.kernel.id;
225 this.kernel_model.id = data.kernel.id;
226 }
226 }
227 };
227 };
228
228
229 /**
229 /**
230 * Handle a successful AJAX request by updating the session data
230 * Handle a successful AJAX request by updating the session data
231 * model with the response, and then optionally calling a provided
231 * model with the response, and then optionally calling a provided
232 * callback.
232 * callback.
233 *
233 *
234 * @function _on_success
234 * @function _on_success
235 * @param {function} success - callback
235 * @param {function} success - callback
236 */
236 */
237 Session.prototype._on_success = function (success) {
237 Session.prototype._on_success = function (success) {
238 var that = this;
238 var that = this;
239 return function (data, status, xhr) {
239 return function (data, status, xhr) {
240 that._update_model(data);
240 that._update_model(data);
241 if (success) {
241 if (success) {
242 success(data, status, xhr);
242 success(data, status, xhr);
243 }
243 }
244 };
244 };
245 };
245 };
246
246
247 /**
247 /**
248 * Handle a failed AJAX request by logging the error message, and
248 * Handle a failed AJAX request by logging the error message, and
249 * then optionally calling a provided callback.
249 * then optionally calling a provided callback.
250 *
250 *
251 * @function _on_error
251 * @function _on_error
252 * @param {function} error - callback
252 * @param {function} error - callback
253 */
253 */
254 Session.prototype._on_error = function (error) {
254 Session.prototype._on_error = function (error) {
255 return function (xhr, status, err) {
255 return function (xhr, status, err) {
256 utils.log_ajax_error(xhr, status, err);
256 utils.log_ajax_error(xhr, status, err);
257 if (error) {
257 if (error) {
258 error(xhr, status, err);
258 error(xhr, status, err);
259 }
259 }
260 };
260 };
261 };
261 };
262
262
263 /**
263 /**
264 * Error type indicating that the session is already starting.
264 * Error type indicating that the session is already starting.
265 */
265 */
266 var SessionAlreadyStarting = function (message) {
266 var SessionAlreadyStarting = function (message) {
267 this.name = "SessionAlreadyStarting";
267 this.name = "SessionAlreadyStarting";
268 this.message = (message || "");
268 this.message = (message || "");
269 };
269 };
270 SessionAlreadyStarting.prototype = Error.prototype;
270 SessionAlreadyStarting.prototype = Error.prototype;
271
271
272 // For backwards compatability.
272 // For backwards compatability.
273 IPython.Session = Session;
273 IPython.Session = Session;
274
274
275 return {
275 return {
276 Session: Session,
276 Session: Session,
277 SessionAlreadyStarting: SessionAlreadyStarting
277 SessionAlreadyStarting: SessionAlreadyStarting
278 };
278 };
279 });
279 });
General Comments 0
You need to be logged in to leave comments. Login now