Show More
@@ -658,7 +658,9 b' class NotebookApp(BaseIPythonApplication):' | |||||
658 | kls = import_item(self.notebook_manager_class) |
|
658 | kls = import_item(self.notebook_manager_class) | |
659 | self.notebook_manager = kls(parent=self, log=self.log) |
|
659 | self.notebook_manager = kls(parent=self, log=self.log) | |
660 | kls = import_item(self.session_manager_class) |
|
660 | kls = import_item(self.session_manager_class) | |
661 |
self.session_manager = kls(parent=self, log=self.log |
|
661 | self.session_manager = kls(parent=self, log=self.log, | |
|
662 | kernel_manager=self.kernel_manager, | |||
|
663 | notebook_manager=self.notebook_manager) | |||
662 | kls = import_item(self.cluster_manager_class) |
|
664 | kls = import_item(self.cluster_manager_class) | |
663 | self.cluster_manager = kls(parent=self, log=self.log) |
|
665 | self.cluster_manager = kls(parent=self, log=self.log) | |
664 | self.cluster_manager.update_profiles() |
|
666 | self.cluster_manager.update_profiles() |
@@ -45,27 +45,28 b' class SessionRootHandler(IPythonHandler):' | |||||
45 | # Creates a new session |
|
45 | # Creates a new session | |
46 | #(unless a session already exists for the named nb) |
|
46 | #(unless a session already exists for the named nb) | |
47 | sm = self.session_manager |
|
47 | sm = self.session_manager | |
48 | nbm = self.notebook_manager |
|
48 | ||
49 | km = self.kernel_manager |
|
|||
50 | model = self.get_json_body() |
|
49 | model = self.get_json_body() | |
51 | if model is None: |
|
50 | if model is None: | |
52 | raise web.HTTPError(400, "No JSON data provided") |
|
51 | raise web.HTTPError(400, "No JSON data provided") | |
53 | try: |
|
52 | try: | |
54 | name = model['notebook']['name'] |
|
53 | name = model['notebook']['name'] | |
55 | except KeyError: |
|
54 | except KeyError: | |
56 | raise web.HTTPError(400, "Missing field in JSON data: name") |
|
55 | raise web.HTTPError(400, "Missing field in JSON data: notebook.name") | |
57 | try: |
|
56 | try: | |
58 | path = model['notebook']['path'] |
|
57 | path = model['notebook']['path'] | |
59 | except KeyError: |
|
58 | except KeyError: | |
60 | raise web.HTTPError(400, "Missing field in JSON data: path") |
|
59 | raise web.HTTPError(400, "Missing field in JSON data: notebook.path") | |
|
60 | try: | |||
|
61 | kernel_name = model['kernel']['name'] | |||
|
62 | except KeyError: | |||
|
63 | raise web.HTTPError(400, "Missing field in JSON data: kernel.name") | |||
|
64 | ||||
61 | # Check to see if session exists |
|
65 | # Check to see if session exists | |
62 | if sm.session_exists(name=name, path=path): |
|
66 | if sm.session_exists(name=name, path=path): | |
63 | model = sm.get_session(name=name, path=path) |
|
67 | model = sm.get_session(name=name, path=path) | |
64 | else: |
|
68 | else: | |
65 | # allow nbm to specify kernels cwd |
|
69 | model = sm.create_session(name=name, path=path, kernel_name=kernel_name) | |
66 | kernel_path = nbm.get_kernel_path(name=name, path=path) |
|
|||
67 | kernel_id = km.start_kernel(path=kernel_path) |
|
|||
68 | model = sm.create_session(name=name, path=path, kernel_id=kernel_id) |
|
|||
69 | location = url_path_join(self.base_url, 'api', 'sessions', model['id']) |
|
70 | location = url_path_join(self.base_url, 'api', 'sessions', model['id']) | |
70 | self.set_header('Location', url_escape(location)) |
|
71 | self.set_header('Location', url_escape(location)) | |
71 | self.set_status(201) |
|
72 | self.set_status(201) | |
@@ -108,10 +109,7 b' class SessionHandler(IPythonHandler):' | |||||
108 | def delete(self, session_id): |
|
109 | def delete(self, session_id): | |
109 | # Deletes the session with given session_id |
|
110 | # Deletes the session with given session_id | |
110 | sm = self.session_manager |
|
111 | sm = self.session_manager | |
111 | km = self.kernel_manager |
|
|||
112 | session = sm.get_session(session_id=session_id) |
|
|||
113 | sm.delete_session(session_id) |
|
112 | sm.delete_session(session_id) | |
114 | km.shutdown_kernel(session['kernel']['id']) |
|
|||
115 | self.set_status(204) |
|
113 | self.set_status(204) | |
116 | self.finish() |
|
114 | self.finish() | |
117 |
|
115 |
@@ -23,12 +23,16 b' from tornado import web' | |||||
23 |
|
23 | |||
24 | from IPython.config.configurable import LoggingConfigurable |
|
24 | from IPython.config.configurable import LoggingConfigurable | |
25 | from IPython.utils.py3compat import unicode_type |
|
25 | from IPython.utils.py3compat import unicode_type | |
|
26 | from IPython.utils.traitlets import Instance | |||
26 |
|
27 | |||
27 | #----------------------------------------------------------------------------- |
|
28 | #----------------------------------------------------------------------------- | |
28 | # Classes |
|
29 | # Classes | |
29 | #----------------------------------------------------------------------------- |
|
30 | #----------------------------------------------------------------------------- | |
30 |
|
31 | |||
31 | class SessionManager(LoggingConfigurable): |
|
32 | class SessionManager(LoggingConfigurable): | |
|
33 | ||||
|
34 | kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager') | |||
|
35 | notebook_manager = Instance('IPython.html.services.notebooks.nbmanager.NotebookManager', args=()) | |||
32 |
|
36 | |||
33 | # Session database initialized below |
|
37 | # Session database initialized below | |
34 | _cursor = None |
|
38 | _cursor = None | |
@@ -69,10 +73,15 b' class SessionManager(LoggingConfigurable):' | |||||
69 | "Create a uuid for a new session" |
|
73 | "Create a uuid for a new session" | |
70 | return unicode_type(uuid.uuid4()) |
|
74 | return unicode_type(uuid.uuid4()) | |
71 |
|
75 | |||
72 |
def create_session(self, name=None, path=None, kernel_ |
|
76 | def create_session(self, name=None, path=None, kernel_name='python'): | |
73 | """Creates a session and returns its model""" |
|
77 | """Creates a session and returns its model""" | |
74 | session_id = self.new_session_id() |
|
78 | session_id = self.new_session_id() | |
75 | return self.save_session(session_id, name=name, path=path, kernel_id=kernel_id) |
|
79 | # allow nbm to specify kernels cwd | |
|
80 | kernel_path = self.notebook_manager.get_kernel_path(name=name, path=path) | |||
|
81 | kernel_id = self.kernel_manager.start_kernel(path=kernel_path, | |||
|
82 | kernel_name=kernel_name) | |||
|
83 | return self.save_session(session_id, name=name, path=path, | |||
|
84 | kernel_id=kernel_id) | |||
76 |
|
85 | |||
77 | def save_session(self, session_id, name=None, path=None, kernel_id=None): |
|
86 | def save_session(self, session_id, name=None, path=None, kernel_id=None): | |
78 | """Saves the items for the session with the given session_id |
|
87 | """Saves the items for the session with the given session_id | |
@@ -170,8 +179,7 b' class SessionManager(LoggingConfigurable):' | |||||
170 | query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets)) |
|
179 | query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets)) | |
171 | self.cursor.execute(query, list(kwargs.values()) + [session_id]) |
|
180 | self.cursor.execute(query, list(kwargs.values()) + [session_id]) | |
172 |
|
181 | |||
173 | @staticmethod |
|
182 | def row_factory(self, cursor, row): | |
174 | def row_factory(cursor, row): |
|
|||
175 | """Takes sqlite database session row and turns it into a dictionary""" |
|
183 | """Takes sqlite database session row and turns it into a dictionary""" | |
176 | row = sqlite3.Row(cursor, row) |
|
184 | row = sqlite3.Row(cursor, row) | |
177 | model = { |
|
185 | model = { | |
@@ -180,9 +188,7 b' class SessionManager(LoggingConfigurable):' | |||||
180 | 'name': row['name'], |
|
188 | 'name': row['name'], | |
181 | 'path': row['path'] |
|
189 | 'path': row['path'] | |
182 | }, |
|
190 | }, | |
183 | 'kernel': { |
|
191 | 'kernel': self.kernel_manager.kernel_model(row['kernel_id']) | |
184 | 'id': row['kernel_id'], |
|
|||
185 | } |
|
|||
186 | } |
|
192 | } | |
187 | return model |
|
193 | return model | |
188 |
|
194 | |||
@@ -195,5 +201,6 b' class SessionManager(LoggingConfigurable):' | |||||
195 | def delete_session(self, session_id): |
|
201 | def delete_session(self, session_id): | |
196 | """Deletes the row in the session database with given session_id""" |
|
202 | """Deletes the row in the session database with given session_id""" | |
197 | # Check that session exists before deleting |
|
203 | # Check that session exists before deleting | |
198 | self.get_session(session_id=session_id) |
|
204 | session = self.get_session(session_id=session_id) | |
|
205 | self.kernel_manager.shutdown_kernel(session['kernel']['id']) | |||
199 | self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,)) |
|
206 | self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,)) |
@@ -5,79 +5,101 b' from unittest import TestCase' | |||||
5 | from tornado import web |
|
5 | from tornado import web | |
6 |
|
6 | |||
7 | from ..sessionmanager import SessionManager |
|
7 | from ..sessionmanager import SessionManager | |
|
8 | from IPython.html.services.kernels.kernelmanager import MappingKernelManager | |||
|
9 | ||||
|
10 | class DummyKernel(object): | |||
|
11 | def __init__(self, kernel_name='python'): | |||
|
12 | self.kernel_name = kernel_name | |||
|
13 | ||||
|
14 | class DummyMKM(MappingKernelManager): | |||
|
15 | """MappingKernelManager interface that doesn't start kernels, for testing""" | |||
|
16 | def __init__(self, *args, **kwargs): | |||
|
17 | super(DummyMKM, self).__init__(*args, **kwargs) | |||
|
18 | self.id_letters = iter(u'ABCDEFGHIJK') | |||
|
19 | ||||
|
20 | def _new_id(self): | |||
|
21 | return next(self.id_letters) | |||
|
22 | ||||
|
23 | def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs): | |||
|
24 | kernel_id = kernel_id or self._new_id() | |||
|
25 | self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name) | |||
|
26 | return kernel_id | |||
|
27 | ||||
|
28 | def shutdown_kernel(self, kernel_id, now=False): | |||
|
29 | del self._kernels[kernel_id] | |||
8 |
|
30 | |||
9 | class TestSessionManager(TestCase): |
|
31 | class TestSessionManager(TestCase): | |
10 |
|
32 | |||
11 | def test_get_session(self): |
|
33 | def test_get_session(self): | |
12 | sm = SessionManager() |
|
34 | sm = SessionManager(kernel_manager=DummyMKM()) | |
13 | session_id = sm.new_session_id() |
|
35 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
14 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') |
|
36 | kernel_name='bar')['id'] | |
15 | model = sm.get_session(session_id=session_id) |
|
37 | model = sm.get_session(session_id=session_id) | |
16 | expected = {'id':session_id, 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}} |
|
38 | expected = {'id':session_id, | |
|
39 | 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, | |||
|
40 | 'kernel': {'id':u'A', 'name': 'bar'}} | |||
17 | self.assertEqual(model, expected) |
|
41 | self.assertEqual(model, expected) | |
18 |
|
42 | |||
19 | def test_bad_get_session(self): |
|
43 | def test_bad_get_session(self): | |
20 | # Should raise error if a bad key is passed to the database. |
|
44 | # Should raise error if a bad key is passed to the database. | |
21 | sm = SessionManager() |
|
45 | sm = SessionManager(kernel_manager=DummyMKM()) | |
22 | session_id = sm.new_session_id() |
|
46 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
23 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') |
|
47 | kernel_name='foo')['id'] | |
24 | self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword |
|
48 | self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword | |
25 |
|
49 | |||
26 | def test_list_sessions(self): |
|
50 | def test_list_sessions(self): | |
27 | sm = SessionManager() |
|
51 | sm = SessionManager(kernel_manager=DummyMKM()) | |
28 | session_id1 = sm.new_session_id() |
|
52 | sessions = [ | |
29 | session_id2 = sm.new_session_id() |
|
53 | sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'), | |
30 | session_id3 = sm.new_session_id() |
|
54 | sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'), | |
31 |
sm. |
|
55 | sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'), | |
32 | sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678') |
|
56 | ] | |
33 | sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678') |
|
|||
34 | sessions = sm.list_sessions() |
|
57 | sessions = sm.list_sessions() | |
35 |
expected = [{'id':session |
|
58 | expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb', | |
36 |
'path': u'/path/to/1/'}, 'kernel':{'id':u' |
|
59 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}}, | |
37 |
{'id':session |
|
60 | {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb', | |
38 |
'path': u'/path/to/2/'}, 'kernel':{'id':u' |
|
61 | 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}}, | |
39 |
{'id':session |
|
62 | {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb', | |
40 |
'path': u'/path/to/3/'}, 'kernel':{'id':u' |
|
63 | 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}] | |
41 | self.assertEqual(sessions, expected) |
|
64 | self.assertEqual(sessions, expected) | |
42 |
|
65 | |||
43 | def test_update_session(self): |
|
66 | def test_update_session(self): | |
44 | sm = SessionManager() |
|
67 | sm = SessionManager(kernel_manager=DummyMKM()) | |
45 | session_id = sm.new_session_id() |
|
68 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
46 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id=None) |
|
69 | kernel_name='julia')['id'] | |
47 | sm.update_session(session_id, kernel_id='5678') |
|
|||
48 | sm.update_session(session_id, name='new_name.ipynb') |
|
70 | sm.update_session(session_id, name='new_name.ipynb') | |
49 | model = sm.get_session(session_id=session_id) |
|
71 | model = sm.get_session(session_id=session_id) | |
50 | expected = {'id':session_id, 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678'}} |
|
72 | expected = {'id':session_id, | |
|
73 | 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, | |||
|
74 | 'kernel':{'id':u'A', 'name':'julia'}} | |||
51 | self.assertEqual(model, expected) |
|
75 | self.assertEqual(model, expected) | |
52 |
|
76 | |||
53 | def test_bad_update_session(self): |
|
77 | def test_bad_update_session(self): | |
54 | # try to update a session with a bad keyword ~ raise error |
|
78 | # try to update a session with a bad keyword ~ raise error | |
55 | sm = SessionManager() |
|
79 | sm = SessionManager(kernel_manager=DummyMKM()) | |
56 | session_id = sm.new_session_id() |
|
80 | session_id = sm.create_session(name='test.ipynb', path='/path/to/', | |
57 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') |
|
81 | kernel_name='ir')['id'] | |
58 | self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword |
|
82 | self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword | |
59 |
|
83 | |||
60 | def test_delete_session(self): |
|
84 | def test_delete_session(self): | |
61 | sm = SessionManager() |
|
85 | sm = SessionManager(kernel_manager=DummyMKM()) | |
62 | session_id1 = sm.new_session_id() |
|
86 | sessions = [ | |
63 | session_id2 = sm.new_session_id() |
|
87 | sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'), | |
64 | session_id3 = sm.new_session_id() |
|
88 | sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'), | |
65 |
sm. |
|
89 | sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'), | |
66 | sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678') |
|
90 | ] | |
67 | sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678') |
|
91 | sm.delete_session(sessions[1]['id']) | |
68 |
sm. |
|
92 | new_sessions = sm.list_sessions() | |
69 | sessions = sm.list_sessions() |
|
93 | expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb', | |
70 | expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb', |
|
94 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}}, | |
71 | 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678'}}, |
|
95 | {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb', | |
72 | {'id':session_id3, 'notebook':{'name':u'test3.ipynb', |
|
96 | 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}] | |
73 | 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678'}}] |
|
97 | self.assertEqual(new_sessions, expected) | |
74 | self.assertEqual(sessions, expected) |
|
|||
75 |
|
98 | |||
76 | def test_bad_delete_session(self): |
|
99 | def test_bad_delete_session(self): | |
77 | # try to delete a session that doesn't exist ~ raise error |
|
100 | # try to delete a session that doesn't exist ~ raise error | |
78 | sm = SessionManager() |
|
101 | sm = SessionManager(kernel_manager=DummyMKM()) | |
79 | session_id = sm.new_session_id() |
|
102 | sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python') | |
80 | sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678') |
|
|||
81 | self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword |
|
103 | self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword | |
82 | self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant |
|
104 | self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant | |
83 |
|
105 |
@@ -37,8 +37,9 b' class SessionAPI(object):' | |||||
37 | def get(self, id): |
|
37 | def get(self, id): | |
38 | return self._req('GET', id) |
|
38 | return self._req('GET', id) | |
39 |
|
39 | |||
40 | def create(self, name, path): |
|
40 | def create(self, name, path, kernel_name='python'): | |
41 |
body = json.dumps({'notebook': {'name':name, 'path':path} |
|
41 | body = json.dumps({'notebook': {'name':name, 'path':path}, | |
|
42 | 'kernel': {'name': kernel_name}}) | |||
42 | return self._req('POST', '', body) |
|
43 | return self._req('POST', '', body) | |
43 |
|
44 | |||
44 | def modify(self, id, name, path): |
|
45 | def modify(self, id, name, path): |
General Comments 0
You need to be logged in to leave comments.
Login now