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