//---------------------------------------------------------------------------- // 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. //---------------------------------------------------------------------------- //============================================================================ // Kernel //============================================================================ /** * @module IPython * @namespace IPython * @submodule Kernel */ var IPython = (function (IPython) { var utils = IPython.utils; // Initialization and connection. /** * A Kernel Class to communicate with the Python kernel * @Class Kernel */ var Kernel = function (base_url) { this.kernel_id = null; this.shell_channel = null; this.iopub_channel = null; this.stdin_channel = null; this.base_url = base_url; this.running = false; this.username = "username"; this.session_id = utils.uuid(); this._msg_callbacks = {}; if (typeof(WebSocket) !== 'undefined') { this.WebSocket = WebSocket; } else if (typeof(MozWebSocket) !== 'undefined') { this.WebSocket = MozWebSocket; } else { 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.'); }; this.bind_events(); }; Kernel.prototype._get_msg = function (msg_type, content) { var msg = { header : { msg_id : utils.uuid(), username : this.username, session : this.session_id, msg_type : msg_type }, metadata : {}, content : content, parent_header : {} }; return msg; }; Kernel.prototype.bind_events = function() { var that = this; $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) { that.send_input_reply(data); }); } /** * Start the Python kernel * @method start */ Kernel.prototype.start = function (params) { var that = this; if (!this.running) { var qs = $.param(params); var url = this.base_url + '?' + qs; $.post(url, $.proxy(that._kernel_started,that), 'json' ); }; }; /** * Restart the python kernel. * * Emit a 'status_restarting.Kernel' event with * the current object as parameter * * @method restart */ Kernel.prototype.restart = function () { $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this}); var that = this; if (this.running) { this.stop_channels(); var url = this.kernel_url + "/restart"; $.post(url, $.proxy(that._kernel_started, that), 'json' ); }; }; Kernel.prototype._kernel_started = function (json) { console.log("Kernel started: ", json.kernel_id); this.running = true; this.kernel_id = json.kernel_id; var ws_url = json.ws_url; if (ws_url.match(/wss?:\/\//) == null) { // trailing 's' in https will become wss for secure web sockets prot = location.protocol.replace('http', 'ws') + "//"; ws_url = prot + location.host + ws_url; }; this.ws_url = ws_url; this.kernel_url = this.base_url + "/" + this.kernel_id; this.start_channels(); }; 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} ); }; /** * Start the `shell`and `iopub` channels. * Will stop and restart them if they already exist. * * @method start_channels */ Kernel.prototype.start_channels = function () { var that = this; this.stop_channels(); var ws_url = this.ws_url + this.kernel_url; console.log("Starting WebSockets:", ws_url); this.shell_channel = new this.WebSocket(ws_url + "/shell"); this.stdin_channel = new this.WebSocket(ws_url + "/stdin"); this.iopub_channel = new this.WebSocket(ws_url + "/iopub"); var already_called_onclose = false; // only alert once var ws_closed_early = function(evt){ if (already_called_onclose){ return; } already_called_onclose = true; if ( ! evt.wasClean ){ that._websocket_closed(ws_url, true); } }; var ws_closed_late = function(evt){ if (already_called_onclose){ return; } already_called_onclose = true; if ( ! evt.wasClean ){ that._websocket_closed(ws_url, false); } }; var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel]; for (var i=0; i < channels.length; i++) { channels[i].onopen = $.proxy(this._ws_opened, this); channels[i].onclose = ws_closed_early; } // switch from early-close to late-close message after 1s setTimeout(function() { for (var i=0; i < channels.length; i++) { if (channels[i] !== null) { channels[i].onclose = ws_closed_late; } } }, 1000); 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); }; /** * Handle a websocket entering the open state * sends session and cookie authentication info as first message. * Once all sockets are open, signal the Kernel.status_started event. * @method _ws_opened */ Kernel.prototype._ws_opened = function (evt) { // send the session id so the Session object Python-side // has the same identity evt.target.send(this.session_id + ':' + document.cookie); var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel]; for (var i=0; i < channels.length; i++) { // if any channel is not ready, don't trigger event. if ( !channels[i].readyState ) return; } // all events ready, trigger started event. $([IPython.events]).trigger('status_started.Kernel', {kernel: this}); }; /** * Stop the websocket channels. * @method stop_channels */ Kernel.prototype.stop_channels = function () { 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(); } }; this.shell_channel = this.iopub_channel = this.stdin_channel = null; }; // Main public methods. /** * 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) */ Kernel.prototype.object_info_request = function (objname, callbacks) { if(typeof(objname)!=null && objname!=null) { var content = { oname : objname.toString(), detail_level : 0, }; var msg = this._get_msg("object_info_request", content); this.shell_channel.send(JSON.stringify(msg)); this.set_callbacks_for_msg(msg.header.msg_id, callbacks); return msg.header.msg_id; } return; } /** * Execute given code into kernel, and pass result to callback. * * TODO: document input_request in callbacks * * @async * @method execute * @param {string} code * @param [callbacks] {Object} With the optional following keys * @param callbacks.'execute_reply' {function} * @param callbacks.'output' {function} * @param callbacks.'clear_output' {function} * @param callbacks.'set_next_input' {function} * @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. */ Kernel.prototype.execute = function (code, callbacks, options) { var content = { code : code, silent : true, store_history : false, user_variables : [], user_expressions : {}, allow_stdin : false }; callbacks = callbacks || {}; if (callbacks.input_request !== undefined) { content.allow_stdin = true; } $.extend(true, content, options) $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content}); var msg = this._get_msg("execute_request", content); this.shell_channel.send(JSON.stringify(msg)); this.set_callbacks_for_msg(msg.header.msg_id, callbacks); return msg.header.msg_id; }; /** * 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` * */ Kernel.prototype.complete = function (line, cursor_pos, callbacks) { callbacks = callbacks || {}; var content = { text : '', line : line, block : null, cursor_pos : cursor_pos }; var msg = this._get_msg("complete_request", content); this.shell_channel.send(JSON.stringify(msg)); this.set_callbacks_for_msg(msg.header.msg_id, callbacks); return msg.header.msg_id; }; Kernel.prototype.interrupt = function () { if (this.running) { $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this}); $.post(this.kernel_url + "/interrupt"); }; }; Kernel.prototype.kill = function () { if (this.running) { this.running = false; var settings = { cache : false, type : "DELETE" }; $.ajax(this.kernel_url, settings); }; }; Kernel.prototype.send_input_reply = function (input) { 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 Kernel.prototype.get_callbacks_for_msg = function (msg_id) { var callbacks = this._msg_callbacks[msg_id]; return callbacks; }; Kernel.prototype.clear_callbacks_for_msg = function (msg_id) { if (this._msg_callbacks[msg_id] !== undefined ) { delete this._msg_callbacks[msg_id]; } }; Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) { this._msg_callbacks[msg_id] = callbacks || {}; }; Kernel.prototype._handle_shell_reply = function (e) { var reply = $.parseJSON(e.data); $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply}); var header = reply.header; var content = reply.content; var metadata = reply.metadata; 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) { cb(content, metadata); } }; if (content.payload !== undefined) { var payload = content.payload || []; this._handle_payload(callbacks, payload); } }; Kernel.prototype._handle_payload = function (callbacks, payload) { 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= 0) { var cb = callbacks['output']; if (cb !== undefined) { cb(msg_type, content, metadata); } } else if (msg_type === 'status') { if (content.execution_state === 'busy') { $([IPython.events]).trigger('status_busy.Kernel', {kernel: this}); } else if (content.execution_state === 'idle') { $([IPython.events]).trigger('status_idle.Kernel', {kernel: this}); } else if (content.execution_state === 'restarting') { // 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}); $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this}); } else if (content.execution_state === 'dead') { this.stop_channels(); $([IPython.events]).trigger('status_dead.Kernel', {kernel: this}); }; } else if (msg_type === 'clear_output') { var cb = callbacks['clear_output']; if (cb !== undefined) { cb(content, metadata); } }; }; 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; } 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); } }; }; IPython.Kernel = Kernel; return IPython; }(IPython));