##// END OF EJS Templates
session manager restructuring...
Zachary Sailer -
Show More
1 NO CONTENT: new file 100644
@@ -0,0 +1,12 b''
1 """Tests for the content manager."""
2
3 import os
4 from unittest import TestCase
5 from tempfile import NamedTemporaryFile
6
7 from IPython.utils.tempdir import TemporaryDirectory
8 from IPython.utils.traitlets import TraitError
9
10 from ..contentmanager import ContentManager
11
12 #class TestContentManager(TestCase):
1 NO CONTENT: new file 100644
@@ -0,0 +1,86 b''
1 """Tests for the session manager."""
2
3 import os
4
5 from unittest import TestCase
6 from tempfile import NamedTemporaryFile
7
8 from IPython.utils.tempdir import TemporaryDirectory
9 from IPython.utils.traitlets import TraitError
10
11 from ..sessionmanager import SessionManager
12
13 class TestSessionManager(TestCase):
14
15 def test_get_session(self):
16 sm = SessionManager()
17 session_id = sm.get_session_id()
18 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
19 model = sm.get_session(id=session_id)
20 expected = {'id':session_id, 'name':u'test.ipynb', 'path': u'/path/to/', 'kernel':{'id':u'5678', 'ws_url': u''}}
21 self.assertEqual(model, expected)
22
23 def test_bad_get_session(self):
24 # Should raise error if a bad key is passed to the database.
25 sm = SessionManager()
26 session_id = sm.get_session_id()
27 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
28 self.assertRaises(TraitError, sm.get_session, bad_id=session_id) # Bad keyword
29
30 def test_list_sessions(self):
31 sm = SessionManager()
32 session_id1 = sm.get_session_id()
33 session_id2 = sm.get_session_id()
34 session_id3 = sm.get_session_id()
35 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel='5678')
36 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel='5678')
37 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel='5678')
38 sessions = sm.list_sessions()
39 expected = [{'id':session_id1, 'name':u'test1.ipynb',
40 'path': u'/path/to/1/', 'kernel':{'id':u'5678', 'ws_url': u''}},
41 {'id':session_id2, 'name':u'test2.ipynb',
42 'path': u'/path/to/2/', 'kernel':{'id':u'5678', 'ws_url': u''}},
43 {'id':session_id3, 'name':u'test3.ipynb',
44 'path': u'/path/to/3/', 'kernel':{'id':u'5678', 'ws_url': u''}}]
45 self.assertEqual(sessions, expected)
46
47 def test_update_session(self):
48 sm = SessionManager()
49 session_id = sm.get_session_id()
50 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel=None)
51 sm.update_session(session_id, kernel='5678')
52 sm.update_session(session_id, name='new_name.ipynb')
53 model = sm.get_session(id=session_id)
54 expected = {'id':session_id, 'name':u'new_name.ipynb', 'path': u'/path/to/', 'kernel':{'id':u'5678', 'ws_url': u''}}
55 self.assertEqual(model, expected)
56
57 def test_bad_update_session(self):
58 # try to update a session with a bad keyword ~ raise error
59 sm = SessionManager()
60 session_id = sm.get_session_id()
61 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
62 self.assertRaises(TraitError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
63
64 def test_delete_session(self):
65 sm = SessionManager()
66 session_id1 = sm.get_session_id()
67 session_id2 = sm.get_session_id()
68 session_id3 = sm.get_session_id()
69 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel='5678')
70 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel='5678')
71 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel='5678')
72 sm.delete_session(session_id2)
73 sessions = sm.list_sessions()
74 expected = [{'id':session_id1, 'name':u'test1.ipynb',
75 'path': u'/path/to/1/', 'kernel':{'id':u'5678', 'ws_url': u''}},
76 {'id':session_id3, 'name':u'test3.ipynb',
77 'path': u'/path/to/3/', 'kernel':{'id':u'5678', 'ws_url': u''}}]
78 self.assertEqual(sessions, expected)
79
80 def test_bad_delete_session(self):
81 # try to delete a session that doesn't exist ~ raise error
82 sm = SessionManager()
83 session_id = sm.get_session_id()
84 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
85 self.assertRaises(TraitError, sm.delete_session, session_id='23424') # Bad keyword
86
@@ -1,96 +1,95 b''
1 1 """A kernel manager relating notebooks and kernels
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from tornado import web
20 20
21 21 from IPython.kernel.multikernelmanager import MultiKernelManager
22 22 from IPython.utils.traitlets import (
23 23 Dict, List, Unicode,
24 24 )
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Classes
28 28 #-----------------------------------------------------------------------------
29 29
30 30
31 31 class MappingKernelManager(MultiKernelManager):
32 32 """A KernelManager that handles notebook mapping and HTTP error handling"""
33 33
34 34 def _kernel_manager_class_default(self):
35 35 return "IPython.kernel.ioloop.IOLoopKernelManager"
36 36
37 37 kernel_argv = List(Unicode)
38 38 kernels = []
39 39
40 40 #-------------------------------------------------------------------------
41 41 # Methods for managing kernels and sessions
42 42 #-------------------------------------------------------------------------
43 43
44 44 def _handle_kernel_died(self, kernel_id):
45 45 """notice that a kernel died"""
46 46 self.log.warn("Kernel %s died, removing from map.", kernel_id)
47 47 self.remove_kernel(kernel_id)
48 48
49 def start_kernel(self, **kwargs):
49 def start_kernel(self, kernel_id=None, **kwargs):
50 50 """Start a kernel for a session an return its kernel_id.
51 51
52 52 Parameters
53 53 ----------
54 session_id : uuid
55 The uuid of the session to associate the new kernel with. If this
56 is not None, this kernel will be persistent whenever the session
57 requests a kernel.
54 kernel_id : uuid
55 The uuid to associate the new kernel with. If this
56 is not None, this kernel will be persistent whenever it is
57 requested.
58 58 """
59 kernel_id = None
60 59 if kernel_id is None:
61 60 kwargs['extra_arguments'] = self.kernel_argv
62 61 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
63 62 self.log.info("Kernel started: %s" % kernel_id)
64 63 self.log.debug("Kernel args: %r" % kwargs)
65 64 # register callback for failed auto-restart
66 65 self.add_restart_callback(kernel_id,
67 66 lambda : self._handle_kernel_died(kernel_id),
68 67 'dead',
69 68 )
70 69 else:
71 70 self.log.info("Using existing kernel: %s" % kernel_id)
72 71
73 72 return kernel_id
74 73
75 74 def shutdown_kernel(self, kernel_id, now=False):
76 75 """Shutdown a kernel by kernel_id"""
77 76 i = 0
78 77 for kernel in self.kernels:
79 78 if kernel['id'] == kernel_id:
80 79 del self.kernels[i]
81 80 i = i+1
82 81 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
83 82
84 83 def kernel_model(self, kernel_id, ws_url):
85 84 model = {"id":kernel_id, "ws_url": ws_url}
86 85 self.kernels.append(model)
87 86 return model
88 87
89 88 def list_kernels(self):
90 89 return self.kernels
91 90
92 91 # override _check_kernel_id to raise 404 instead of KeyError
93 92 def _check_kernel_id(self, kernel_id):
94 93 """Check a that a kernel_id exists and raise 404 if not."""
95 94 if kernel_id not in self:
96 95 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
@@ -1,104 +1,106 b''
1 1 """Tornado handlers for the sessions web service.
2 2
3 3 Authors:
4 4
5 5 * Zach Sailer
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from tornado import web
20 20
21 21 from zmq.utils import jsonapi
22 22
23 23 from IPython.utils.jsonutil import date_default
24 24 from ...base.handlers import IPythonHandler
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Session web service handlers
28 28 #-----------------------------------------------------------------------------
29 29
30 30
31
32 31 class SessionRootHandler(IPythonHandler):
33 32
34 33 @web.authenticated
35 34 def get(self):
36 35 sm = self.session_manager
37 36 nbm = self.notebook_manager
38 37 km = self.kernel_manager
39 38 sessions = sm.list_sessions()
40 39 self.finish(jsonapi.dumps(sessions))
41 40
42 41 @web.authenticated
43 42 def post(self):
44 43 sm = self.session_manager
45 44 nbm = self.notebook_manager
46 45 km = self.kernel_manager
47 46 notebook_path = self.get_argument('notebook_path', default=None)
48 notebook_name, path = nbm.named_notebook_path(notebook_path)
49 session_id, model = sm.get_session(notebook_name, path)
50 if model == None:
51 kernel_id = km.start_kernel()
47 name, path = nbm.named_notebook_path(notebook_path)
48 if sm.session_exists(name=name, path=path):
49 model = sm.get_session(name=name, path=path)
50 kernel_id = model['kernel']['id']
51 km.start_kernel(kernel_id, cwd=nbm.notebook_dir)
52 else:
53 session_id = sm.get_session_id()
54 sm.save_session(session_id=session_id, name=name, path=path)
55 kernel_id = km.start_kernel(cwd=nbm.notebook_dir)
52 56 kernel = km.kernel_model(kernel_id, self.ws_url)
53 model = sm.session_model(session_id, notebook_name, path, kernel)
57 sm.update_session(session_id, kernel=kernel_id)
58 model = sm.get_session(id=session_id)
59 self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
54 60 self.finish(jsonapi.dumps(model))
55 61
56 62 class SessionHandler(IPythonHandler):
57 63
58 64 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
59 65
60 66 @web.authenticated
61 67 def get(self, session_id):
62 68 sm = self.session_manager
63 model = sm.get_session_from_id(session_id)
69 model = sm.get_session(id=session_id)
64 70 self.finish(jsonapi.dumps(model))
65 71
66 72 @web.authenticated
67 73 def patch(self, session_id):
74 # Currently, this handler is strictly for renaming notebooks
68 75 sm = self.session_manager
69 76 nbm = self.notebook_manager
70 77 km = self.kernel_manager
71 78 notebook_path = self.request.body
72 notebook_name, path = nbm.named_notebook_path(notebook_path)
73 kernel_id = sm.get_kernel_from_session(session_id)
74 kernel = km.kernel_model(kernel_id, self.ws_url)
75 sm.delete_mapping_for_session(session_id)
76 model = sm.session_model(session_id, notebook_name, path, kernel)
79 name, path = nbm.named_notebook_path(notebook_path)
80 sm.update_session(id=session_id, name=name)
81 model = sm.get_session(id=session_id)
77 82 self.finish(jsonapi.dumps(model))
78 83
79 84 @web.authenticated
80 85 def delete(self, session_id):
81 86 sm = self.session_manager
82 87 nbm = self.notebook_manager
83 88 km = self.kernel_manager
84 kernel_id = sm.get_kernel_from_session(session_id)
85 km.shutdown_kernel(kernel_id)
86 sm.delete_mapping_for_session(session_id)
89 session = sm.get_session(id=session_id)
90 sm.delete_session(session_id)
91 km.shutdown_kernel(session['kernel']['id'])
87 92 self.set_status(204)
88 93 self.finish()
89 94
90 95
91 96 #-----------------------------------------------------------------------------
92 97 # URL to handler mappings
93 98 #-----------------------------------------------------------------------------
94 99
95 100 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
96 101
97 102 default_handlers = [
98 103 (r"api/sessions/%s" % _session_id_regex, SessionHandler),
99 104 (r"api/sessions", SessionRootHandler)
100 105 ]
101 106
102
103
104
@@ -1,97 +1,170 b''
1 1 """A base class session manager.
2 2
3 3 Authors:
4 4
5 5 * Zach Sailer
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import os
20 20 import uuid
21 import sqlite3
21 22
22 23 from tornado import web
23 24
24 25 from IPython.config.configurable import LoggingConfigurable
25 26 from IPython.nbformat import current
26 27 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
27 28
28 29 #-----------------------------------------------------------------------------
29 30 # Classes
30 31 #-----------------------------------------------------------------------------
31 32
32 33 class SessionManager(LoggingConfigurable):
33 34
34 # Use session_ids to map notebook names to kernel_ids
35 sessions = List()
35 # Session database initialized below
36 _cursor = None
37 _connection = None
36 38
37 def get_session(self, nb_name, nb_path=None):
38 """Get an existing session or create a new one"""
39 model = None
40 for session in self.sessions:
41 if session['name'] == nb_name and session['path'] == nb_path:
42 session_id = session['id']
43 model = session
44 if model != None:
45 return session_id, model
39 @property
40 def cursor(self):
41 """Start a cursor and create a database called 'session'"""
42 if self._cursor is None:
43 self._cursor = self.connection.cursor()
44 self._cursor.execute("""CREATE TABLE session
45 (id, name, path, kernel)""")
46 return self._cursor
47
48 @property
49 def connection(self):
50 """Start a database connection"""
51 if self._connection is None:
52 self._connection = sqlite3.connect(':memory:')
53 self._connection.row_factory = sqlite3.Row
54 return self._connection
55
56 def __del__(self):
57 """Close connection once SessionManager closes"""
58 self.cursor.close()
59
60 def session_exists(self, name, path):
61 """Check to see if the session for the given notebook exists"""
62 self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name,path))
63 reply = self.cursor.fetchone()
64 if reply is None:
65 return False
46 66 else:
47 session_id = unicode(uuid.uuid4())
48 return session_id, model
49
50 def session_model(self, session_id, notebook_name=None, notebook_path=None, kernel=None):
51 """ Create a session that links notebooks with kernels """
52 model = dict(id=session_id,
53 name=notebook_name,
54 path=notebook_path,
55 kernel=kernel)
56 if notebook_path == None:
57 model['path']=""
58 self.sessions.append(model)
59 return model
60
61 def list_sessions(self):
62 """List all sessions and their information"""
63 return self.sessions
64
65 def set_kernel_for_sessions(self, session_id, kernel_id):
66 """Maps the kernel_ids to the session_id in session_mapping"""
67 for session in self.sessions:
68 if session['id'] == session_id:
69 session['kernel']['id'] = kernel_id
70 return self.sessions
67 return True
68
69 def get_session_id(self):
70 "Create a uuid for a new session"
71 return unicode(uuid.uuid4())
72
73 def save_session(self, session_id, name=None, path=None, kernel=None):
74 """ Given a session_id (and any other of the arguments), this method
75 creates a row in the sqlite session database that holds the information
76 for a session.
71 77
72 def delete_mapping_for_session(self, session_id):
73 """Delete the session from session_mapping with the given session_id"""
74 i = 0
75 for session in self.sessions:
76 if session['id'] == session_id:
77 del self.sessions[i]
78 i = i + 1
79 return self.sessions
78 Parameters
79 ----------
80 session_id : str
81 uuid for the session; this method must be given a session_id
82 name : str
83 the .ipynb notebook name that started the session
84 path : str
85 the path to the named notebook
86 kernel : str
87 a uuid for the kernel associated with this session
88 """
89 self.cursor.execute("""INSERT INTO session VALUES
90 (?,?,?,?)""", (session_id, name, path, kernel))
91 self.connection.commit()
92
93 def get_session(self, **kwargs):
94 """ Takes a keyword argument and searches for the value in the session
95 database, then returns the rest of the session's info.
96
97 Parameters
98 ----------
99 **kwargs : keyword argument
100 must be given one of the keywords and values from the session database
101 (i.e. session_id, name, path, kernel)
102
103 Returns
104 -------
105 model : dict
106 returns a dictionary that includes all the information from the
107 session described by the kwarg.
108 """
109 column = kwargs.keys()[0] # uses only the first kwarg that is entered
110 value = kwargs.values()[0]
111 try:
112 self.cursor.execute("SELECT * FROM session WHERE %s=?" %column, (value,))
113 except sqlite3.OperationalError:
114 raise TraitError("The session database has no column: %s" %column)
115 reply = self.cursor.fetchone()
116 if reply is not None:
117 model = self.reply_to_dictionary_model(reply)
118 else:
119 model = None
120 return model
121
122 def update_session(self, session_id, **kwargs):
123 """Updates the values in the session with the given session_id
124 with the values from the keyword arguments.
80 125
81 def get_session_from_id(self, session_id):
82 for session in self.sessions:
83 if session['id'] == session_id:
84 return session
85
86 def get_notebook_from_session(self, session_id):
87 """Returns the notebook_path for the given session_id"""
88 for session in self.sessions:
89 if session['id'] == session_id:
90 return session['name']
91
92 def get_kernel_from_session(self, session_id):
93 """Returns the kernel_id for the given session_id"""
94 for session in self.sessions:
95 if session['id'] == session_id:
96 return session['kernel']['id']
126 Parameters
127 ----------
128 session_id : str
129 a uuid that identifies a session in the sqlite3 database
130 **kwargs : str
131 the key must correspond to a column title in session database,
132 and the value replaces the current value in the session
133 with session_id.
134 """
135 column = kwargs.keys()[0] # uses only the first kwarg that is entered
136 value = kwargs.values()[0]
137 try:
138 self.cursor.execute("UPDATE session SET %s=? WHERE id=?" %column, (value, session_id))
139 self.connection.commit()
140 except sqlite3.OperationalError:
141 raise TraitError("No session exists with ID: %s" %session_id)
142
143 def reply_to_dictionary_model(self, reply):
144 """Takes sqlite database session row and turns it into a dictionary"""
145 model = {'id': reply['id'],
146 'name' : reply['name'],
147 'path' : reply['path'],
148 'kernel' : {'id':reply['kernel'], 'ws_url': ''}}
149 return model
97 150
151 def list_sessions(self):
152 """Returns a list of dictionaries containing all the information from
153 the session database"""
154 session_list=[]
155 self.cursor.execute("SELECT * FROM session")
156 sessions = self.cursor.fetchall()
157 for session in sessions:
158 model = self.reply_to_dictionary_model(session)
159 session_list.append(model)
160 return session_list
161
162 def delete_session(self, session_id):
163 """Deletes the row in the session database with given session_id"""
164 # Check that session exists before deleting
165 model = self.get_session(id=session_id)
166 if model is None:
167 raise TraitError("The session does not exist: %s" %session_id)
168 else:
169 self.cursor.execute("DELETE FROM session WHERE id=?", (session_id,))
170 self.connection.commit() No newline at end of file
@@ -1,527 +1,525 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Kernel
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule Kernel
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19
20 20 var utils = IPython.utils;
21 21
22 22 // Initialization and connection.
23 23 /**
24 24 * A Kernel Class to communicate with the Python kernel
25 25 * @Class Kernel
26 26 */
27 var Kernel = function (base_url, session_id) {
27 var Kernel = function (base_url) {
28 28 this.kernel_id = null;
29 this.session_id = session_id
30 29 this.shell_channel = null;
31 30 this.iopub_channel = null;
32 31 this.stdin_channel = null;
33 32 this.base_url = base_url;
34 33 this.running = false;
35 this.username = "username";
36 this.base_session_id = utils.uuid();
34 this.username= "username";
35 this.session_id = utils.uuid();
37 36 this._msg_callbacks = {};
38 37
39 38 if (typeof(WebSocket) !== 'undefined') {
40 39 this.WebSocket = WebSocket;
41 40 } else if (typeof(MozWebSocket) !== 'undefined') {
42 41 this.WebSocket = MozWebSocket;
43 42 } else {
44 43 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.');
45 44 };
46 45 this.bind_events();
47 46 };
48 47
49 48
50 49 Kernel.prototype._get_msg = function (msg_type, content) {
51 50 var msg = {
52 51 header : {
53 52 msg_id : utils.uuid(),
54 53 username : this.username,
55 session : this.base_session_id,
54 session : this.session_id,
56 55 msg_type : msg_type
57 56 },
58 57 metadata : {},
59 58 content : content,
60 59 parent_header : {}
61 60 };
62 61 return msg;
63 62 };
64 63
65 64 Kernel.prototype.bind_events = function() {
66 65 var that = this;
67 66 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
68 67 that.send_input_reply(data);
69 68 });
70 69 }
71 70
72 71 /**
73 72 * Start the Python kernel
74 73 * @method start
75 74 */
76 75 Kernel.prototype.start = function (params) {
77 76 var that = this;
78 77 params = params || {};
79 params.session = this.session_id;
80 78 if (!this.running) {
81 79 var qs = $.param(params);
82 80 var url = this.base_url + '?' + qs;
83 81 $.post(url,
84 82 $.proxy(that._kernel_started,that),
85 83 'json'
86 84 );
87 85 };
88 86 };
89 87
90 88 /**
91 89 * Restart the python kernel.
92 90 *
93 91 * Emit a 'status_restarting.Kernel' event with
94 92 * the current object as parameter
95 93 *
96 94 * @method restart
97 95 */
98 96 Kernel.prototype.restart = function () {
99 97 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
100 98 var that = this;
101 99 if (this.running) {
102 100 this.stop_channels();
103 101 var url = this.kernel_url + "/restart";
104 102 $.post(url,
105 103 $.proxy(that._kernel_started, that),
106 104 'json'
107 105 );
108 106 };
109 107 };
110 108
111 109
112 110 Kernel.prototype._kernel_started = function (json) {
113 111 console.log("Kernel started: ", json.id);
114 112 this.running = true;
115 113 this.kernel_id = json.id;
116 114 var ws_url = json.ws_url;
117 115 if (ws_url.match(/wss?:\/\//) == null) {
118 116 // trailing 's' in https will become wss for secure web sockets
119 117 prot = location.protocol.replace('http', 'ws') + "//";
120 118 ws_url = prot + location.host + ws_url;
121 119 };
122 120 this.ws_url = ws_url;
123 121 this.kernel_url = this.base_url + "/" + this.kernel_id;
124 122 this.start_channels();
125 123 };
126 124
127 125
128 126 Kernel.prototype._websocket_closed = function(ws_url, early) {
129 127 this.stop_channels();
130 128 $([IPython.events]).trigger('websocket_closed.Kernel',
131 129 {ws_url: ws_url, kernel: this, early: early}
132 130 );
133 131 };
134 132
135 133 /**
136 134 * Start the `shell`and `iopub` channels.
137 135 * Will stop and restart them if they already exist.
138 136 *
139 137 * @method start_channels
140 138 */
141 139 Kernel.prototype.start_channels = function () {
142 140 var that = this;
143 141 this.stop_channels();
144 142 var ws_url = this.ws_url + this.kernel_url;
145 143 console.log("Starting WebSockets:", ws_url);
146 144 this.shell_channel = new this.WebSocket(ws_url + "/shell");
147 145 this.stdin_channel = new this.WebSocket(ws_url + "/stdin");
148 146 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
149 147
150 148 var already_called_onclose = false; // only alert once
151 149 var ws_closed_early = function(evt){
152 150 if (already_called_onclose){
153 151 return;
154 152 }
155 153 already_called_onclose = true;
156 154 if ( ! evt.wasClean ){
157 155 that._websocket_closed(ws_url, true);
158 156 }
159 157 };
160 158 var ws_closed_late = function(evt){
161 159 if (already_called_onclose){
162 160 return;
163 161 }
164 162 already_called_onclose = true;
165 163 if ( ! evt.wasClean ){
166 164 that._websocket_closed(ws_url, false);
167 165 }
168 166 };
169 167 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
170 168 for (var i=0; i < channels.length; i++) {
171 169 channels[i].onopen = $.proxy(this._ws_opened, this);
172 170 channels[i].onclose = ws_closed_early;
173 171 }
174 172 // switch from early-close to late-close message after 1s
175 173 setTimeout(function() {
176 174 for (var i=0; i < channels.length; i++) {
177 175 if (channels[i] !== null) {
178 176 channels[i].onclose = ws_closed_late;
179 177 }
180 178 }
181 179 }, 1000);
182 180 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
183 181 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this);
184 182 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
185 183 };
186 184
187 185 /**
188 186 * Handle a websocket entering the open state
189 187 * sends session and cookie authentication info as first message.
190 188 * Once all sockets are open, signal the Kernel.status_started event.
191 189 * @method _ws_opened
192 190 */
193 191 Kernel.prototype._ws_opened = function (evt) {
194 192 // send the session id so the Session object Python-side
195 193 // has the same identity
196 194 evt.target.send(this.session_id + ':' + document.cookie);
197 195
198 196 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
199 197 for (var i=0; i < channels.length; i++) {
200 198 // if any channel is not ready, don't trigger event.
201 199 if ( !channels[i].readyState ) return;
202 200 }
203 201 // all events ready, trigger started event.
204 202 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
205 203 };
206 204
207 205 /**
208 206 * Stop the websocket channels.
209 207 * @method stop_channels
210 208 */
211 209 Kernel.prototype.stop_channels = function () {
212 210 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
213 211 for (var i=0; i < channels.length; i++) {
214 212 if ( channels[i] !== null ) {
215 213 channels[i].onclose = function (evt) {};
216 214 channels[i].close();
217 215 }
218 216 };
219 217 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
220 218 };
221 219
222 220 // Main public methods.
223 221
224 222 /**
225 223 * Get info on object asynchronoulsy
226 224 *
227 225 * @async
228 226 * @param objname {string}
229 227 * @param callback {dict}
230 228 * @method object_info_request
231 229 *
232 230 * @example
233 231 *
234 232 * When calling this method pass a callbacks structure of the form:
235 233 *
236 234 * callbacks = {
237 235 * 'object_info_reply': object_info_reply_callback
238 236 * }
239 237 *
240 238 * The `object_info_reply_callback` will be passed the content object of the
241 239 *
242 240 * `object_into_reply` message documented in
243 241 * [IPython dev documentation](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
244 242 */
245 243 Kernel.prototype.object_info_request = function (objname, callbacks) {
246 244 if(typeof(objname)!=null && objname!=null)
247 245 {
248 246 var content = {
249 247 oname : objname.toString(),
250 248 detail_level : 0,
251 249 };
252 250 var msg = this._get_msg("object_info_request", content);
253 251 this.shell_channel.send(JSON.stringify(msg));
254 252 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
255 253 return msg.header.msg_id;
256 254 }
257 255 return;
258 256 }
259 257
260 258 /**
261 259 * Execute given code into kernel, and pass result to callback.
262 260 *
263 261 * TODO: document input_request in callbacks
264 262 *
265 263 * @async
266 264 * @method execute
267 265 * @param {string} code
268 266 * @param [callbacks] {Object} With the optional following keys
269 267 * @param callbacks.'execute_reply' {function}
270 268 * @param callbacks.'output' {function}
271 269 * @param callbacks.'clear_output' {function}
272 270 * @param callbacks.'set_next_input' {function}
273 271 * @param {object} [options]
274 272 * @param [options.silent=false] {Boolean}
275 273 * @param [options.user_expressions=empty_dict] {Dict}
276 274 * @param [options.user_variables=empty_list] {List od Strings}
277 275 * @param [options.allow_stdin=false] {Boolean} true|false
278 276 *
279 277 * @example
280 278 *
281 279 * The options object should contain the options for the execute call. Its default
282 280 * values are:
283 281 *
284 282 * options = {
285 283 * silent : true,
286 284 * user_variables : [],
287 285 * user_expressions : {},
288 286 * allow_stdin : false
289 287 * }
290 288 *
291 289 * When calling this method pass a callbacks structure of the form:
292 290 *
293 291 * callbacks = {
294 292 * 'execute_reply': execute_reply_callback,
295 293 * 'output': output_callback,
296 294 * 'clear_output': clear_output_callback,
297 295 * 'set_next_input': set_next_input_callback
298 296 * }
299 297 *
300 298 * The `execute_reply_callback` will be passed the content and metadata
301 299 * objects of the `execute_reply` message documented
302 300 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#execute)
303 301 *
304 302 * The `output_callback` will be passed `msg_type` ('stream','display_data','pyout','pyerr')
305 303 * of the output and the content and metadata objects of the PUB/SUB channel that contains the
306 304 * output:
307 305 *
308 306 * http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
309 307 *
310 308 * The `clear_output_callback` will be passed a content object that contains
311 309 * stdout, stderr and other fields that are booleans, as well as the metadata object.
312 310 *
313 311 * The `set_next_input_callback` will be passed the text that should become the next
314 312 * input cell.
315 313 */
316 314 Kernel.prototype.execute = function (code, callbacks, options) {
317 315
318 316 var content = {
319 317 code : code,
320 318 silent : true,
321 319 store_history : false,
322 320 user_variables : [],
323 321 user_expressions : {},
324 322 allow_stdin : false
325 323 };
326 324 callbacks = callbacks || {};
327 325 if (callbacks.input_request !== undefined) {
328 326 content.allow_stdin = true;
329 327 }
330 328 $.extend(true, content, options)
331 329 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
332 330 var msg = this._get_msg("execute_request", content);
333 331 this.shell_channel.send(JSON.stringify(msg));
334 332 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
335 333 return msg.header.msg_id;
336 334 };
337 335
338 336 /**
339 337 * When calling this method pass a callbacks structure of the form:
340 338 *
341 339 * callbacks = {
342 340 * 'complete_reply': complete_reply_callback
343 341 * }
344 342 *
345 343 * The `complete_reply_callback` will be passed the content object of the
346 344 * `complete_reply` message documented
347 345 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
348 346 *
349 347 * @method complete
350 348 * @param line {integer}
351 349 * @param cursor_pos {integer}
352 350 * @param {dict} callbacks
353 351 * @param callbacks.complete_reply {function} `complete_reply_callback`
354 352 *
355 353 */
356 354 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
357 355 callbacks = callbacks || {};
358 356 var content = {
359 357 text : '',
360 358 line : line,
361 359 block : null,
362 360 cursor_pos : cursor_pos
363 361 };
364 362 var msg = this._get_msg("complete_request", content);
365 363 this.shell_channel.send(JSON.stringify(msg));
366 364 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
367 365 return msg.header.msg_id;
368 366 };
369 367
370 368
371 369 Kernel.prototype.interrupt = function () {
372 370 if (this.running) {
373 371 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
374 372 $.post(this.kernel_url + "/interrupt");
375 373 };
376 374 };
377 375
378 376
379 377 Kernel.prototype.kill = function () {
380 378 if (this.running) {
381 379 this.running = false;
382 380 var settings = {
383 381 cache : false,
384 382 type : "DELETE"
385 383 };
386 384 $.ajax(this.kernel_url, settings);
387 385 };
388 386 };
389 387
390 388 Kernel.prototype.send_input_reply = function (input) {
391 389 var content = {
392 390 value : input,
393 391 };
394 392 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
395 393 var msg = this._get_msg("input_reply", content);
396 394 this.stdin_channel.send(JSON.stringify(msg));
397 395 return msg.header.msg_id;
398 396 };
399 397
400 398
401 399 // Reply handlers
402 400
403 401 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
404 402 var callbacks = this._msg_callbacks[msg_id];
405 403 return callbacks;
406 404 };
407 405
408 406
409 407 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
410 408 if (this._msg_callbacks[msg_id] !== undefined ) {
411 409 delete this._msg_callbacks[msg_id];
412 410 }
413 411 };
414 412
415 413
416 414 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
417 415 this._msg_callbacks[msg_id] = callbacks || {};
418 416 };
419 417
420 418
421 419 Kernel.prototype._handle_shell_reply = function (e) {
422 420 var reply = $.parseJSON(e.data);
423 421 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
424 422 var header = reply.header;
425 423 var content = reply.content;
426 424 var metadata = reply.metadata;
427 425 var msg_type = header.msg_type;
428 426 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
429 427 if (callbacks !== undefined) {
430 428 var cb = callbacks[msg_type];
431 429 if (cb !== undefined) {
432 430 cb(content, metadata);
433 431 }
434 432 };
435 433
436 434 if (content.payload !== undefined) {
437 435 var payload = content.payload || [];
438 436 this._handle_payload(callbacks, payload);
439 437 }
440 438 };
441 439
442 440
443 441 Kernel.prototype._handle_payload = function (callbacks, payload) {
444 442 var l = payload.length;
445 443 // Payloads are handled by triggering events because we don't want the Kernel
446 444 // to depend on the Notebook or Pager classes.
447 445 for (var i=0; i<l; i++) {
448 446 if (payload[i].source === 'page') {
449 447 var data = {'text':payload[i].text}
450 448 $([IPython.events]).trigger('open_with_text.Pager', data);
451 449 } else if (payload[i].source === 'set_next_input') {
452 450 if (callbacks.set_next_input !== undefined) {
453 451 callbacks.set_next_input(payload[i].text)
454 452 }
455 453 }
456 454 };
457 455 };
458 456
459 457
460 458 Kernel.prototype._handle_iopub_reply = function (e) {
461 459 var reply = $.parseJSON(e.data);
462 460 var content = reply.content;
463 461 var msg_type = reply.header.msg_type;
464 462 var metadata = reply.metadata;
465 463 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
466 464 if (msg_type !== 'status' && callbacks === undefined) {
467 465 // Message not from one of this notebook's cells and there are no
468 466 // callbacks to handle it.
469 467 return;
470 468 }
471 469 var output_types = ['stream','display_data','pyout','pyerr'];
472 470 if (output_types.indexOf(msg_type) >= 0) {
473 471 var cb = callbacks['output'];
474 472 if (cb !== undefined) {
475 473 cb(msg_type, content, metadata);
476 474 }
477 475 } else if (msg_type === 'status') {
478 476 if (content.execution_state === 'busy') {
479 477 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
480 478 } else if (content.execution_state === 'idle') {
481 479 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
482 480 } else if (content.execution_state === 'restarting') {
483 481 // autorestarting is distinct from restarting,
484 482 // in that it means the kernel died and the server is restarting it.
485 483 // status_restarting sets the notification widget,
486 484 // autorestart shows the more prominent dialog.
487 485 $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
488 486 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
489 487 } else if (content.execution_state === 'dead') {
490 488 this.stop_channels();
491 489 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
492 490 };
493 491 } else if (msg_type === 'clear_output') {
494 492 var cb = callbacks['clear_output'];
495 493 if (cb !== undefined) {
496 494 cb(content, metadata);
497 495 }
498 496 };
499 497 };
500 498
501 499
502 500 Kernel.prototype._handle_input_request = function (e) {
503 501 var request = $.parseJSON(e.data);
504 502 var header = request.header;
505 503 var content = request.content;
506 504 var metadata = request.metadata;
507 505 var msg_type = header.msg_type;
508 506 if (msg_type !== 'input_request') {
509 507 console.log("Invalid input request!", request);
510 508 return;
511 509 }
512 510 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
513 511 if (callbacks !== undefined) {
514 512 var cb = callbacks[msg_type];
515 513 if (cb !== undefined) {
516 514 cb(content, metadata);
517 515 }
518 516 };
519 517 };
520 518
521 519
522 520 IPython.Kernel = Kernel;
523 521
524 522 return IPython;
525 523
526 524 }(IPython));
527 525
@@ -1,96 +1,95 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var Session = function(notebook_path, Notebook){
15 15 this.kernel = null;
16 16 this.kernel_id = null;
17 17 this.session_id = null;
18 18 this.notebook_path = notebook_path;
19 19 this.notebook = Notebook;
20 20 this._baseProjectUrl = Notebook.baseProjectUrl()
21 21 };
22 22
23 23 Session.prototype.start = function(){
24 24 var that = this
25 25 var qs = $.param({notebook_path:this.notebook_path});
26 26 var url = '/api/sessions' + '?' + qs;
27 27 $.post(url,
28 28 $.proxy(this.start_kernel, that),
29 29 'json'
30 30 );
31 31 };
32 32
33 33 Session.prototype.notebook_rename = function (notebook_path) {
34 34 this.notebook_path = notebook_path;
35 35 var settings = {
36 36 processData : false,
37 37 cache : false,
38 38 type : "PATCH",
39 39 data: notebook_path,
40 40 dataType : "json",
41 41 };
42 42 var url = this._baseProjectUrl + 'api/sessions/' + this.session_id;
43 43 $.ajax(url, settings);
44 44 }
45 45
46 46
47 47 Session.prototype.delete_session = function() {
48 48 var settings = {
49 49 processData : false,
50 50 cache : false,
51 51 type : "DELETE",
52 52 dataType : "json",
53 53 };
54 54 var url = this._baseProjectUrl + 'api/sessions/' + this.session_id;
55 55 $.ajax(url, settings);
56 56 };
57 57
58 58 // Kernel related things
59 59 /**
60 60 * Start a new kernel and set it on each code cell.
61 61 *
62 62 * @method start_kernel
63 63 */
64 64 Session.prototype.start_kernel = function (json) {
65 65 this.session_id = json.id;
66 66 this.kernel_content = json.kernel;
67 67 var base_url = $('body').data('baseKernelUrl') + "api/kernels";
68 68 this.kernel = new IPython.Kernel(base_url, this.session_id);
69 // Now that the kernel has been created, tell the CodeCells about it.
70 69 this.kernel._kernel_started(this.kernel_content);
71 70 };
72 71
73 72 /**
74 73 * Prompt the user to restart the IPython kernel.
75 74 *
76 75 * @method restart_kernel
77 76 */
78 77 Session.prototype.restart_kernel = function () {
79 78 this.kernel.restart();
80 79 };
81 80
82 81 Session.prototype.interrupt_kernel = function() {
83 82 this.kernel.interrupt();
84 83 };
85 84
86 85
87 86 Session.prototype.kill_kernel = function() {
88 87 this.kernel.kill();
89 88 };
90 89
91 90 IPython.Session = Session;
92 91
93 92
94 93 return IPython;
95 94
96 95 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now