kernel.js
502 lines
| 17.2 KiB
| application/javascript
|
JavascriptLexer
|
r4609 | //---------------------------------------------------------------------------- | ||
// Copyright (C) 2008-2011 The IPython Development Team | ||||
// | ||||
// Distributed under the terms of the BSD License. The full license is in | ||||
// the file COPYING, distributed as part of this software. | ||||
//---------------------------------------------------------------------------- | ||||
|
r4349 | |||
//============================================================================ | ||||
// Kernel | ||||
//============================================================================ | ||||
|
r8768 | /** | ||
* @module IPython | ||||
* @namespace IPython | ||||
* @submodule Kernel | ||||
*/ | ||||
|
r4352 | var IPython = (function (IPython) { | ||
|
r4349 | |||
|
r4352 | var utils = IPython.utils; | ||
|
r7168 | // Initialization and connection. | ||
|
r8768 | /** | ||
* A Kernel Class to communicate with the Python kernel | ||||
* @Class Kernel | ||||
*/ | ||||
|
r7168 | var Kernel = function (base_url) { | ||
|
r4352 | this.kernel_id = null; | ||
|
r4545 | this.shell_channel = null; | ||
this.iopub_channel = null; | ||||
|
r10366 | this.stdin_channel = null; | ||
|
r7168 | this.base_url = base_url; | ||
|
r4545 | this.running = false; | ||
|
r4694 | this.username = "username"; | ||
this.session_id = utils.uuid(); | ||||
|
r7168 | this._msg_callbacks = {}; | ||
|
r4612 | if (typeof(WebSocket) !== 'undefined') { | ||
|
r5479 | this.WebSocket = WebSocket; | ||
|
r4612 | } else if (typeof(MozWebSocket) !== 'undefined') { | ||
|
r5479 | this.WebSocket = MozWebSocket; | ||
|
r4611 | } else { | ||
|
r5253 | 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.'); | ||
|
r4611 | }; | ||
|
r11611 | this.bind_events(); | ||
|
r4352 | }; | ||
|
r7168 | Kernel.prototype._get_msg = function (msg_type, content) { | ||
|
r4352 | var msg = { | ||
header : { | ||||
msg_id : utils.uuid(), | ||||
|
r4694 | username : this.username, | ||
session : this.session_id, | ||||
|
r4352 | msg_type : msg_type | ||
}, | ||||
|
r7958 | metadata : {}, | ||
|
r4352 | content : content, | ||
parent_header : {} | ||||
}; | ||||
return msg; | ||||
|
r5479 | }; | ||
|
r11611 | |||
Kernel.prototype.bind_events = function() { | ||||
var that = this; | ||||
$([IPython.events]).on('send_input_reply.Kernel', function(evt, data) { | ||||
that.send_input_reply(data); | ||||
}); | ||||
} | ||||
|
r4352 | |||
|
r8768 | /** | ||
* Start the Python kernel | ||||
* @method start | ||||
*/ | ||||
|
r7168 | Kernel.prototype.start = function (notebook_id) { | ||
|
r4352 | var that = this; | ||
|
r4545 | if (!this.running) { | ||
var qs = $.param({notebook:notebook_id}); | ||||
|
r5479 | var url = this.base_url + '?' + qs; | ||
|
r5106 | $.post(url, | ||
|
r7168 | $.proxy(that._kernel_started,that), | ||
|
r4545 | 'json' | ||
); | ||||
}; | ||||
}; | ||||
|
r8768 | /** | ||
* Restart the python kernel. | ||||
* | ||||
* Emit a 'status_restarting.Kernel' event with | ||||
* the current object as parameter | ||||
* | ||||
* @method restart | ||||
*/ | ||||
|
r7168 | Kernel.prototype.restart = function () { | ||
|
r8677 | $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this}); | ||
|
r4545 | var that = this; | ||
if (this.running) { | ||||
this.stop_channels(); | ||||
|
r7168 | var url = this.kernel_url + "/restart"; | ||
|
r4545 | $.post(url, | ||
|
r7168 | $.proxy(that._kernel_started, that), | ||
|
r4545 | 'json' | ||
); | ||||
}; | ||||
|
r4352 | }; | ||
|
r7168 | Kernel.prototype._kernel_started = function (json) { | ||
console.log("Kernel started: ", json.kernel_id); | ||||
|
r4545 | this.running = true; | ||
|
r4572 | this.kernel_id = json.kernel_id; | ||
|
r10807 | var ws_url = json.ws_url; | ||
if (ws_url.match(/wss?:\/\//) == null) { | ||||
|
r10979 | // trailing 's' in https will become wss for secure web sockets | ||
prot = location.protocol.replace('http', 'ws') + "//"; | ||||
ws_url = prot + location.host + ws_url; | ||||
|
r10807 | }; | ||
this.ws_url = ws_url; | ||||
|
r4352 | this.kernel_url = this.base_url + "/" + this.kernel_id; | ||
|
r4545 | this.start_channels(); | ||
|
r8690 | $([IPython.events]).trigger('status_started.Kernel', {kernel: this}); | ||
|
r4352 | }; | ||
|
r7168 | |||
|
r9222 | Kernel.prototype._websocket_closed = function(ws_url, early) { | ||
this.stop_channels(); | ||||
$([IPython.events]).trigger('websocket_closed.Kernel', | ||||
{ws_url: ws_url, kernel: this, early: early} | ||||
); | ||||
|
r5479 | }; | ||
|
r4352 | |||
|
r8768 | /** | ||
* Start the `shell`and `iopub` channels. | ||||
* Will stop and restart them if they already exist. | ||||
* | ||||
* @method start_channels | ||||
*/ | ||||
|
r4545 | Kernel.prototype.start_channels = function () { | ||
|
r5253 | var that = this; | ||
|
r4545 | this.stop_channels(); | ||
|
r4572 | var ws_url = this.ws_url + this.kernel_url; | ||
|
r9222 | console.log("Starting WebSockets:", ws_url); | ||
|
r4611 | this.shell_channel = new this.WebSocket(ws_url + "/shell"); | ||
|
r10366 | this.stdin_channel = new this.WebSocket(ws_url + "/stdin"); | ||
|
r4611 | this.iopub_channel = new this.WebSocket(ws_url + "/iopub"); | ||
|
r4707 | send_cookie = function(){ | ||
|
r10379 | // send the session id so the Session object Python-side | ||
// has the same identity | ||||
|
r10365 | this.send(that.session_id + ':' + document.cookie); | ||
|
r5479 | }; | ||
|
r5253 | var already_called_onclose = false; // only alert once | ||
|
r8839 | var ws_closed_early = function(evt){ | ||
|
r5253 | if (already_called_onclose){ | ||
return; | ||||
} | ||||
already_called_onclose = true; | ||||
if ( ! evt.wasClean ){ | ||||
|
r5255 | that._websocket_closed(ws_url, true); | ||
|
r5253 | } | ||
|
r5479 | }; | ||
|
r8839 | var ws_closed_late = function(evt){ | ||
|
r5253 | if (already_called_onclose){ | ||
return; | ||||
} | ||||
already_called_onclose = true; | ||||
if ( ! evt.wasClean ){ | ||||
|
r5255 | that._websocket_closed(ws_url, false); | ||
|
r5253 | } | ||
|
r5479 | }; | ||
|
r10366 | var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel]; | ||
for (var i=0; i < channels.length; i++) { | ||||
channels[i].onopen = send_cookie; | ||||
channels[i].onclose = ws_closed_early; | ||||
} | ||||
|
r5253 | // switch from early-close to late-close message after 1s | ||
|
r9222 | setTimeout(function() { | ||
|
r10366 | for (var i=0; i < channels.length; i++) { | ||
if (channels[i] !== null) { | ||||
channels[i].onclose = ws_closed_late; | ||||
} | ||||
|
r9222 | } | ||
|
r5253 | }, 1000); | ||
|
r10366 | this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this); | ||
this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this); | ||||
this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this); | ||||
|
r4545 | }; | ||
|
r4352 | |||
|
r8768 | /** | ||
|
r11978 | * Stop the `shell`and `iopub` channels. | ||
|
r8768 | * @method stop_channels | ||
*/ | ||||
|
r4545 | Kernel.prototype.stop_channels = function () { | ||
|
r10366 | var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel]; | ||
for (var i=0; i < channels.length; i++) { | ||||
if ( channels[i] !== null ) { | ||||
channels[i].onclose = function (evt) {}; | ||||
channels[i].close(); | ||||
} | ||||
|
r4545 | }; | ||
|
r10366 | this.shell_channel = this.iopub_channel = this.stdin_channel = null; | ||
|
r4545 | }; | ||
|
r7168 | // Main public methods. | ||
|
r8768 | /** | ||
* Get info on object asynchronoulsy | ||||
* | ||||
* @async | ||||
* @param objname {string} | ||||
* @param callback {dict} | ||||
* @method object_info_request | ||||
* | ||||
* @example | ||||
* | ||||
* When calling this method pass a callbacks structure of the form: | ||||
* | ||||
* callbacks = { | ||||
* 'object_info_reply': object_info_reply_callback | ||||
* } | ||||
* | ||||
* The `object_info_reply_callback` will be passed the content object of the | ||||
* | ||||
* `object_into_reply` message documented in | ||||
* [IPython dev documentation](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information) | ||||
*/ | ||||
|
r7168 | Kernel.prototype.object_info_request = function (objname, callbacks) { | ||
|
r5522 | if(typeof(objname)!=null && objname!=null) | ||
|
r5409 | { | ||
var content = { | ||||
oname : objname.toString(), | ||||
|
r11694 | detail_level : 0, | ||
|
r5409 | }; | ||
|
r7168 | var msg = this._get_msg("object_info_request", content); | ||
|
r5409 | this.shell_channel.send(JSON.stringify(msg)); | ||
|
r7168 | this.set_callbacks_for_msg(msg.header.msg_id, callbacks); | ||
|
r5409 | return msg.header.msg_id; | ||
} | ||||
return; | ||||
|
r5397 | } | ||
|
r8768 | /** | ||
* Execute given code into kernel, and pass result to callback. | ||||
* | ||||
|
r10594 | * TODO: document input_request in callbacks | ||
* | ||||
|
r8768 | * @async | ||
* @method execute | ||||
* @param {string} code | ||||
|
r10615 | * @param [callbacks] {Object} With the optional following keys | ||
|
r10594 | * @param callbacks.'execute_reply' {function} | ||
* @param callbacks.'output' {function} | ||||
* @param callbacks.'clear_output' {function} | ||||
* @param callbacks.'set_next_input' {function} | ||||
|
r8768 | * @param {object} [options] | ||
* @param [options.silent=false] {Boolean} | ||||
* @param [options.user_expressions=empty_dict] {Dict} | ||||
* @param [options.user_variables=empty_list] {List od Strings} | ||||
* @param [options.allow_stdin=false] {Boolean} true|false | ||||
* | ||||
* @example | ||||
* | ||||
* The options object should contain the options for the execute call. Its default | ||||
* values are: | ||||
* | ||||
* options = { | ||||
* silent : true, | ||||
* user_variables : [], | ||||
* user_expressions : {}, | ||||
* allow_stdin : false | ||||
* } | ||||
* | ||||
* When calling this method pass a callbacks structure of the form: | ||||
* | ||||
* callbacks = { | ||||
* 'execute_reply': execute_reply_callback, | ||||
* 'output': output_callback, | ||||
* 'clear_output': clear_output_callback, | ||||
* 'set_next_input': set_next_input_callback | ||||
* } | ||||
* | ||||
* The `execute_reply_callback` will be passed the content and metadata | ||||
* objects of the `execute_reply` message documented | ||||
* [here](http://ipython.org/ipython-doc/dev/development/messaging.html#execute) | ||||
* | ||||
* The `output_callback` will be passed `msg_type` ('stream','display_data','pyout','pyerr') | ||||
* of the output and the content and metadata objects of the PUB/SUB channel that contains the | ||||
* output: | ||||
* | ||||
* http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket | ||||
* | ||||
* The `clear_output_callback` will be passed a content object that contains | ||||
* stdout, stderr and other fields that are booleans, as well as the metadata object. | ||||
* | ||||
* The `set_next_input_callback` will be passed the text that should become the next | ||||
* input cell. | ||||
*/ | ||||
|
r7176 | Kernel.prototype.execute = function (code, callbacks, options) { | ||
|
r4352 | var content = { | ||
code : code, | ||||
|
r7176 | silent : true, | ||
|
r11857 | store_history : false, | ||
|
r4352 | user_variables : [], | ||
|
r4975 | user_expressions : {}, | ||
|
r10368 | allow_stdin : false | ||
|
r4352 | }; | ||
|
r10594 | callbacks = callbacks || {}; | ||
|
r10368 | if (callbacks.input_request !== undefined) { | ||
content.allow_stdin = true; | ||||
} | ||||
|
r8605 | $.extend(true, content, options) | ||
|
r8677 | $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content}); | ||
|
r7168 | var msg = this._get_msg("execute_request", content); | ||
|
r4352 | this.shell_channel.send(JSON.stringify(msg)); | ||
|
r7168 | this.set_callbacks_for_msg(msg.header.msg_id, callbacks); | ||
|
r4352 | return msg.header.msg_id; | ||
|
r5479 | }; | ||
|
r4352 | |||
|
r8768 | /** | ||
* When calling this method pass a callbacks structure of the form: | ||||
* | ||||
* callbacks = { | ||||
* 'complete_reply': complete_reply_callback | ||||
* } | ||||
* | ||||
* The `complete_reply_callback` will be passed the content object of the | ||||
* `complete_reply` message documented | ||||
* [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete) | ||||
* | ||||
* @method complete | ||||
* @param line {integer} | ||||
* @param cursor_pos {integer} | ||||
* @param {dict} callbacks | ||||
* @param callbacks.complete_reply {function} `complete_reply_callback` | ||||
* | ||||
*/ | ||||
|
r7168 | Kernel.prototype.complete = function (line, cursor_pos, callbacks) { | ||
|
r7212 | callbacks = callbacks || {}; | ||
|
r4388 | var content = { | ||
text : '', | ||||
line : line, | ||||
|
r11695 | block : null, | ||
|
r4388 | cursor_pos : cursor_pos | ||
}; | ||||
|
r7168 | var msg = this._get_msg("complete_request", content); | ||
|
r4388 | this.shell_channel.send(JSON.stringify(msg)); | ||
|
r7168 | this.set_callbacks_for_msg(msg.header.msg_id, callbacks); | ||
|
r4388 | return msg.header.msg_id; | ||
|
r5479 | }; | ||
|
r4388 | |||
|
r4352 | Kernel.prototype.interrupt = function () { | ||
|
r4545 | if (this.running) { | ||
|
r8677 | $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this}); | ||
|
r4545 | $.post(this.kernel_url + "/interrupt"); | ||
}; | ||||
|
r4349 | }; | ||
|
r4496 | Kernel.prototype.kill = function () { | ||
|
r4545 | if (this.running) { | ||
this.running = false; | ||||
var settings = { | ||||
cache : false, | ||||
|
r5479 | type : "DELETE" | ||
|
r4545 | }; | ||
$.ajax(this.kernel_url, settings); | ||||
|
r4496 | }; | ||
}; | ||||
|
r10368 | Kernel.prototype.send_input_reply = function (input) { | ||
|
r10366 | var content = { | ||
value : input, | ||||
}; | ||||
$([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content}); | ||||
var msg = this._get_msg("input_reply", content); | ||||
this.stdin_channel.send(JSON.stringify(msg)); | ||||
return msg.header.msg_id; | ||||
}; | ||||
// Reply handlers | ||||
|
r7168 | |||
Kernel.prototype.get_callbacks_for_msg = function (msg_id) { | ||||
var callbacks = this._msg_callbacks[msg_id]; | ||||
return callbacks; | ||||
}; | ||||
Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) { | ||||
|
r7212 | this._msg_callbacks[msg_id] = callbacks || {}; | ||
|
r7168 | } | ||
Kernel.prototype._handle_shell_reply = function (e) { | ||||
|
r8839 | var reply = $.parseJSON(e.data); | ||
|
r8677 | $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply}); | ||
|
r7168 | var header = reply.header; | ||
var content = reply.content; | ||||
|
r7952 | var metadata = reply.metadata; | ||
|
r7168 | var msg_type = header.msg_type; | ||
var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id); | ||||
if (callbacks !== undefined) { | ||||
var cb = callbacks[msg_type]; | ||||
if (cb !== undefined) { | ||||
|
r7952 | cb(content, metadata); | ||
|
r7168 | } | ||
}; | ||||
|
r7223 | if (content.payload !== undefined) { | ||
|
r7168 | var payload = content.payload || []; | ||
|
r7223 | this._handle_payload(callbacks, payload); | ||
|
r7168 | } | ||
}; | ||||
|
r7223 | Kernel.prototype._handle_payload = function (callbacks, payload) { | ||
|
r7168 | var l = payload.length; | ||
// Payloads are handled by triggering events because we don't want the Kernel | ||||
// to depend on the Notebook or Pager classes. | ||||
for (var i=0; i<l; i++) { | ||||
|
r11839 | if (payload[i].source === 'page') { | ||
|
r7168 | var data = {'text':payload[i].text} | ||
$([IPython.events]).trigger('open_with_text.Pager', data); | ||||
|
r11839 | } else if (payload[i].source === 'set_next_input') { | ||
|
r7223 | if (callbacks.set_next_input !== undefined) { | ||
callbacks.set_next_input(payload[i].text) | ||||
} | ||||
|
r7168 | } | ||
}; | ||||
}; | ||||
Kernel.prototype._handle_iopub_reply = function (e) { | ||||
|
r7952 | var reply = $.parseJSON(e.data); | ||
|
r7168 | var content = reply.content; | ||
var msg_type = reply.header.msg_type; | ||||
|
r7955 | var metadata = reply.metadata; | ||
|
r7168 | var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id); | ||
if (msg_type !== 'status' && callbacks === undefined) { | ||||
// Message not from one of this notebook's cells and there are no | ||||
// callbacks to handle it. | ||||
return; | ||||
} | ||||
var output_types = ['stream','display_data','pyout','pyerr']; | ||||
if (output_types.indexOf(msg_type) >= 0) { | ||||
var cb = callbacks['output']; | ||||
if (cb !== undefined) { | ||||
|
r7952 | cb(msg_type, content, metadata); | ||
|
r7168 | } | ||
} else if (msg_type === 'status') { | ||||
if (content.execution_state === 'busy') { | ||||
|
r8677 | $([IPython.events]).trigger('status_busy.Kernel', {kernel: this}); | ||
|
r7168 | } else if (content.execution_state === 'idle') { | ||
|
r8677 | $([IPython.events]).trigger('status_idle.Kernel', {kernel: this}); | ||
|
r10316 | } else if (content.execution_state === 'restarting') { | ||
|
r11076 | // autorestarting is distinct from restarting, | ||
// in that it means the kernel died and the server is restarting it. | ||||
// status_restarting sets the notification widget, | ||||
// autorestart shows the more prominent dialog. | ||||
$([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this}); | ||||
|
r10316 | $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this}); | ||
|
r7168 | } else if (content.execution_state === 'dead') { | ||
this.stop_channels(); | ||||
|
r8677 | $([IPython.events]).trigger('status_dead.Kernel', {kernel: this}); | ||
|
r7168 | }; | ||
} else if (msg_type === 'clear_output') { | ||||
var cb = callbacks['clear_output']; | ||||
if (cb !== undefined) { | ||||
|
r7952 | cb(content, metadata); | ||
|
r7168 | } | ||
}; | ||||
}; | ||||
|
r10366 | Kernel.prototype._handle_input_request = function (e) { | ||
var request = $.parseJSON(e.data); | ||||
var header = request.header; | ||||
var content = request.content; | ||||
var metadata = request.metadata; | ||||
var msg_type = header.msg_type; | ||||
if (msg_type !== 'input_request') { | ||||
console.log("Invalid input request!", request); | ||||
return; | ||||
} | ||||
|
r10368 | var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id); | ||
if (callbacks !== undefined) { | ||||
var cb = callbacks[msg_type]; | ||||
if (cb !== undefined) { | ||||
cb(content, metadata); | ||||
} | ||||
}; | ||||
|
r10366 | }; | ||
|
r4352 | IPython.Kernel = Kernel; | ||
return IPython; | ||||
}(IPython)); | ||||