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