##// 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
@@ -46,17 +46,16 b' class MappingKernelManager(MultiKernelManager):'
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)
@@ -28,7 +28,6 b' from ...base.handlers import IPythonHandler'
28 28 #-----------------------------------------------------------------------------
29 29
30 30
31
32 31 class SessionRootHandler(IPythonHandler):
33 32
34 33 @web.authenticated
@@ -45,12 +44,19 b' class SessionRootHandler(IPythonHandler):'
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):
@@ -60,20 +66,19 b' class SessionHandler(IPythonHandler):'
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
@@ -81,9 +86,9 b' class SessionHandler(IPythonHandler):'
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
@@ -99,6 +104,3 b' default_handlers = ['
99 104 (r"api/sessions", SessionRootHandler)
100 105 ]
101 106
102
103
104
@@ -18,6 +18,7 b' Authors:'
18 18
19 19 import os
20 20 import uuid
21 import sqlite3
21 22
22 23 from tornado import web
23 24
@@ -31,67 +32,139 b' from IPython.utils.traitlets import List, Dict, Unicode, TraitError'
31 32
32 33 class SessionManager(LoggingConfigurable):
33 34
34 # Use session_ids to map notebook names to kernel_ids
35 sessions = List()
36
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
35 # Session database initialized below
36 _cursor = None
37 _connection = None
38
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
66 else:
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.
77
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)
46 118 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)
119 model = None
59 120 return model
60 121
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
71
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
80
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']
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.
125
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
@@ -24,16 +24,15 b' var IPython = (function (IPython) {'
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 34 this.username = "username";
36 this.base_session_id = utils.uuid();
35 this.session_id = utils.uuid();
37 36 this._msg_callbacks = {};
38 37
39 38 if (typeof(WebSocket) !== 'undefined') {
@@ -52,7 +51,7 b' var IPython = (function (IPython) {'
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 : {},
@@ -76,7 +75,6 b' var IPython = (function (IPython) {'
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;
@@ -66,7 +66,6 b' var IPython = (function (IPython) {'
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
General Comments 0
You need to be logged in to leave comments. Login now