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