##// END OF EJS Templates
changes after session manager code review
Zachary Sailer -
Show More
@@ -28,6 +28,7 b' import os'
28 28 import stat
29 29 import sys
30 30 import threading
31 import traceback
31 32
32 33 from tornado import web
33 34 from tornado import websocket
@@ -248,6 +249,17 b' class IPythonHandler(AuthenticatedHandler):'
248 249 use_less=self.use_less,
249 250 )
250 251
252 def get_json_body(self):
253 """Return the body of the request as JSON data."""
254 if not self.request.body:
255 return None
256 # Do we need to call body.decode('utf-8') here?
257 body = self.request.body.strip().decode(u'utf-8')
258 try:
259 model = json.loads(body)
260 except:
261 raise web.HTTPError(400, u'Invalid JSON in body of request')
262 return model
251 263
252 264 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
253 265 """static files should only be accessible when logged in"""
@@ -282,7 +294,8 b' def json_errors(method):'
282 294 status = 400
283 295 message = u"Unknown server error"
284 296 self.set_status(status)
285 reply = dict(message=message)
297 tb_text = ''.join(traceback.format_exception(t, value, tb))
298 reply = dict(message=message, traceback=tb_text)
286 299 self.finish(json.dumps(reply, default=date_default))
287 300 else:
288 301 return result
@@ -381,6 +394,12 b' class FileFindHandler(web.StaticFileHandler):'
381 394 if if_since >= modified:
382 395 self.set_status(304)
383 396 return
397
398 if os.path.splitext(path)[1] == '.ipynb':
399 raise HTTPError(404, 'HAHA')
400 name = os.path.splitext(os.path.split(path))[0]
401 self.set_header('Content-Type', 'application/json')
402 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
384 403
385 404 with open(abspath, "rb") as file:
386 405 data = file.read()
@@ -16,12 +16,11 b' Authors:'
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 from tornado import web
20
21 from zmq.utils import jsonapi
19 import json
22 20
21 from tornado import web
23 22 from IPython.utils.jsonutil import date_default
24 from ...base.handlers import IPythonHandler
23 from ...base.handlers import IPythonHandler, json_errors
25 24
26 25 #-----------------------------------------------------------------------------
27 26 # Session web service handlers
@@ -31,63 +30,76 b' from ...base.handlers import IPythonHandler'
31 30 class SessionRootHandler(IPythonHandler):
32 31
33 32 @web.authenticated
33 @json_errors
34 34 def get(self):
35 35 # Return a list of running sessions
36 36 sm = self.session_manager
37 nbm = self.notebook_manager
38 km = self.kernel_manager
39 37 sessions = sm.list_sessions()
40 self.finish(jsonapi.dumps(sessions))
38 self.finish(json.dumps(sessions, default=date_default))
41 39
42 40 @web.authenticated
41 @json_errors
43 42 def post(self):
44 43 # Creates a new session
45 44 #(unless a session already exists for the named nb)
46 45 sm = self.session_manager
47 46 nbm = self.notebook_manager
48 47 km = self.kernel_manager
49 notebook_path = self.get_argument('notebook_path', default=None)
50 name, path = nbm.named_notebook_path(notebook_path)
48 model = self.get_json_body()
49 if model is None:
50 raise HTTPError(400, "No JSON data provided")
51 try:
52 name = model['notebook']['name']
53 except KeyError:
54 raise HTTPError(400, "Missing field in JSON data: name")
55 try:
56 path = model['notebook']['path']
57 except KeyError:
58 raise HTTPError(400, "Missing field in JSON data: path")
51 59 # Check to see if session exists
52 60 if sm.session_exists(name=name, path=path):
53 61 model = sm.get_session(name=name, path=path)
54 kernel_id = model['kernel']['id']
55 km.start_kernel(kernel_id, cwd=nbm.notebook_dir)
56 62 else:
57 session_id = sm.get_session_id()
58 sm.save_session(session_id=session_id, name=name, path=path)
59 63 kernel_id = km.start_kernel(cwd=nbm.notebook_dir)
60 kernel = km.kernel_model(kernel_id, self.ws_url)
61 sm.update_session(session_id, kernel=kernel_id)
62 model = sm.get_session(id=session_id)
63 self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
64 self.finish(jsonapi.dumps(model))
64 model = sm.create_session(name=name, path=path, kernel_id=kernel_id, ws_url=self.ws_url)
65 self.set_header('Location', '{0}/api/sessions/{1}'.format(self.base_project_url, model['id']))
66 self.finish(json.dumps(model, default=date_default))
65 67
66 68 class SessionHandler(IPythonHandler):
67 69
68 70 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
69 71
70 72 @web.authenticated
73 @json_errors
71 74 def get(self, session_id):
72 75 # Returns the JSON model for a single session
73 76 sm = self.session_manager
74 77 model = sm.get_session(id=session_id)
75 self.finish(jsonapi.dumps(model))
78 self.finish(json.dumps(model, default=date_default))
76 79
77 80 @web.authenticated
81 @json_errors
78 82 def patch(self, session_id):
79 83 # Currently, this handler is strictly for renaming notebooks
80 84 sm = self.session_manager
81 85 nbm = self.notebook_manager
82 86 km = self.kernel_manager
83 data = self.request.body
84 data = jsonapi.loads(self.request.body)
85 name, path = nbm.named_notebook_path(data['notebook_path'])
86 sm.update_session(session_id, name=name)
87 model = self.get_json_body()
88 if model is None:
89 raise HTTPError(400, "No JSON data provided")
90 changes = {}
91 if 'notebook' in model:
92 notebook = model['notebook']
93 if 'name' in notebook:
94 changes['name'] = notebook['name']
95 if 'path' in notebook:
96 changes['path'] = notebook['path']
97 sm.update_session(session_id, **changes)
87 98 model = sm.get_session(id=session_id)
88 self.finish(jsonapi.dumps(model))
99 self.finish(json.dumps(model, default=date_default))
89 100
90 101 @web.authenticated
102 @json_errors
91 103 def delete(self, session_id):
92 104 # Deletes the session with given session_id
93 105 sm = self.session_manager
@@ -107,9 +119,7 b' class SessionHandler(IPythonHandler):'
107 119 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
108 120
109 121 default_handlers = [
110 (r"api/sessions/%s/" % _session_id_regex, SessionHandler),
111 122 (r"api/sessions/%s" % _session_id_regex, SessionHandler),
112 (r"api/sessions/", SessionRootHandler),
113 123 (r"api/sessions", SessionRootHandler)
114 124 ]
115 125
@@ -42,7 +42,7 b' class SessionManager(LoggingConfigurable):'
42 42 if self._cursor is None:
43 43 self._cursor = self.connection.cursor()
44 44 self._cursor.execute("""CREATE TABLE session
45 (id, name, path, kernel)""")
45 (id, name, path, kernel_id, ws_url)""")
46 46 return self._cursor
47 47
48 48 @property
@@ -70,8 +70,15 b' class SessionManager(LoggingConfigurable):'
70 70 "Create a uuid for a new session"
71 71 return unicode(uuid.uuid4())
72 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
73 def create_session(self, name=None, path=None, kernel_id=None, ws_url=None):
74 """Creates a session and returns its model"""
75 session_id = self.get_session_id()
76 return self.save_session(session_id, name=name, path=path, kernel_id=kernel_id, ws_url=ws_url)
77
78 def save_session(self, session_id, name=None, path=None, kernel_id=None, ws_url=None):
79 """Saves the items for the session with the given session_id
80
81 Given a session_id (and any other of the arguments), this method
75 82 creates a row in the sqlite session database that holds the information
76 83 for a session.
77 84
@@ -83,22 +90,32 b' class SessionManager(LoggingConfigurable):'
83 90 the .ipynb notebook name that started the session
84 91 path : str
85 92 the path to the named notebook
86 kernel : str
93 kernel_id : str
87 94 a uuid for the kernel associated with this session
95 ws_url : str
96 the websocket url
97
98 Returns
99 -------
100 model : dict
101 a dictionary of the session model
88 102 """
89 103 self.cursor.execute("""INSERT INTO session VALUES
90 (?,?,?,?)""", (session_id, name, path, kernel))
104 (?,?,?,?,?)""", (session_id, name, path, kernel_id, ws_url))
91 105 self.connection.commit()
106 return self.get_session(id=session_id)
92 107
93 108 def get_session(self, **kwargs):
94 """ Takes a keyword argument and searches for the value in the session
109 """Returns the model for a particular session.
110
111 Takes a keyword argument and searches for the value in the session
95 112 database, then returns the rest of the session's info.
96 113
97 114 Parameters
98 115 ----------
99 116 **kwargs : keyword argument
100 117 must be given one of the keywords and values from the session database
101 (i.e. session_id, name, path, kernel)
118 (i.e. session_id, name, path, kernel_id, ws_url)
102 119
103 120 Returns
104 121 -------
@@ -120,7 +137,9 b' class SessionManager(LoggingConfigurable):'
120 137 return model
121 138
122 139 def update_session(self, session_id, **kwargs):
123 """Updates the values in the session with the given session_id
140 """Updates the values in the session database.
141
142 Changes the values of the session with the given session_id
124 143 with the values from the keyword arguments.
125 144
126 145 Parameters
@@ -132,20 +151,20 b' class SessionManager(LoggingConfigurable):'
132 151 and the value replaces the current value in the session
133 152 with session_id.
134 153 """
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)
154 column = kwargs.keys() # uses only the first kwarg that is entered
155 value = kwargs.values()
156 for kwarg in kwargs:
157 try:
158 self.cursor.execute("UPDATE session SET %s=? WHERE id=?" %kwarg, (kwargs[kwarg], session_id))
159 self.connection.commit()
160 except sqlite3.OperationalError:
161 raise TraitError("No session exists with ID: %s" %session_id)
142 162
143 163 def reply_to_dictionary_model(self, reply):
144 164 """Takes sqlite database session row and turns it into a dictionary"""
145 165 model = {'id': reply['id'],
146 'name' : reply['name'],
147 'path' : reply['path'],
148 'kernel' : {'id':reply['kernel'], 'ws_url': ''}}
166 'notebook': {'name': reply['name'], 'path': reply['path']},
167 'kernel': {'id': reply['kernel_id'], 'ws_url': reply['ws_url']}}
149 168 return model
150 169
151 170 def list_sessions(self):
@@ -15,16 +15,16 b' class TestSessionManager(TestCase):'
15 15 def test_get_session(self):
16 16 sm = SessionManager()
17 17 session_id = sm.get_session_id()
18 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
18 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678', ws_url='ws_url')
19 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''}}
20 expected = {'id':session_id, 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678', 'ws_url':u'ws_url'}}
21 21 self.assertEqual(model, expected)
22 22
23 23 def test_bad_get_session(self):
24 24 # Should raise error if a bad key is passed to the database.
25 25 sm = SessionManager()
26 26 session_id = sm.get_session_id()
27 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
27 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678', ws_url='ws_url')
28 28 self.assertRaises(TraitError, sm.get_session, bad_id=session_id) # Bad keyword
29 29
30 30 def test_list_sessions(self):
@@ -32,33 +32,33 b' class TestSessionManager(TestCase):'
32 32 session_id1 = sm.get_session_id()
33 33 session_id2 = sm.get_session_id()
34 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')
35 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678', ws_url='ws_url')
36 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678', ws_url='ws_url')
37 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678', ws_url='ws_url')
38 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''}}]
39 expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
40 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678', 'ws_url': 'ws_url'}},
41 {'id':session_id2, 'notebook': {'name':u'test2.ipynb',
42 'path': u'/path/to/2/'}, 'kernel':{'id':u'5678', 'ws_url': 'ws_url'}},
43 {'id':session_id3, 'notebook':{'name':u'test3.ipynb',
44 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678', 'ws_url': 'ws_url'}}]
45 45 self.assertEqual(sessions, expected)
46 46
47 47 def test_update_session(self):
48 48 sm = SessionManager()
49 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')
50 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id=None, ws_url='ws_url')
51 sm.update_session(session_id, kernel_id='5678')
52 52 sm.update_session(session_id, name='new_name.ipynb')
53 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''}}
54 expected = {'id':session_id, 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, 'kernel':{'id':u'5678', 'ws_url': 'ws_url'}}
55 55 self.assertEqual(model, expected)
56 56
57 57 def test_bad_update_session(self):
58 58 # try to update a session with a bad keyword ~ raise error
59 59 sm = SessionManager()
60 60 session_id = sm.get_session_id()
61 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
61 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678', ws_url='ws_url')
62 62 self.assertRaises(TraitError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
63 63
64 64 def test_delete_session(self):
@@ -66,21 +66,21 b' class TestSessionManager(TestCase):'
66 66 session_id1 = sm.get_session_id()
67 67 session_id2 = sm.get_session_id()
68 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')
69 sm.save_session(session_id=session_id1, name='test1.ipynb', path='/path/to/1/', kernel_id='5678', ws_url='ws_url')
70 sm.save_session(session_id=session_id2, name='test2.ipynb', path='/path/to/2/', kernel_id='5678', ws_url='ws_url')
71 sm.save_session(session_id=session_id3, name='test3.ipynb', path='/path/to/3/', kernel_id='5678', ws_url='ws_url')
72 72 sm.delete_session(session_id2)
73 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''}}]
74 expected = [{'id':session_id1, 'notebook':{'name':u'test1.ipynb',
75 'path': u'/path/to/1/'}, 'kernel':{'id':u'5678', 'ws_url': 'ws_url'}},
76 {'id':session_id3, 'notebook':{'name':u'test3.ipynb',
77 'path': u'/path/to/3/'}, 'kernel':{'id':u'5678', 'ws_url': 'ws_url'}}]
78 78 self.assertEqual(sessions, expected)
79 79
80 80 def test_bad_delete_session(self):
81 81 # try to delete a session that doesn't exist ~ raise error
82 82 sm = SessionManager()
83 83 session_id = sm.get_session_id()
84 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel='5678')
84 sm.save_session(session_id=session_id, name='test.ipynb', path='/path/to/', kernel_id='5678', ws_url='ws_url')
85 85 self.assertRaises(TraitError, sm.delete_session, session_id='23424') # Bad keyword
86 86
@@ -4,10 +4,9 b''
4 4 import os
5 5 import sys
6 6 import json
7 from zmq.utils import jsonapi
8
9 7 import requests
10 8
9 from IPython.utils.jsonutil import date_default
11 10 from IPython.html.utils import url_path_join
12 11 from IPython.html.tests.launchnotebook import NotebookTestBase
13 12
@@ -39,12 +38,12 b' class SessionAPITest(NotebookTestBase):'
39 38 # POST a session
40 39 url, nb = self.mknb()
41 40 notebook = nb.json()
42 param = {'notebook_path': notebook['path'] + notebook['name']}
43 r = requests.post(self.session_url(), params=param)
41 model = {'notebook': {'name':notebook['name'], 'path': notebook['path']}}
42 r = requests.post(self.session_url(), data=json.dumps(model, default=date_default))
44 43 data = r.json()
45 44 assert isinstance(data, dict)
46 self.assertIn('name', data)
47 self.assertEqual(data['name'], notebook['name'])
45 self.assertIn('name', data['notebook'])
46 self.assertEqual(data['notebook']['name'], notebook['name'])
48 47
49 48 # GET sessions
50 49 r = requests.get(self.session_url())
@@ -62,8 +61,8 b' class SessionAPITest(NotebookTestBase):'
62 61 # Create a session
63 62 url, nb = self.mknb()
64 63 notebook = nb.json()
65 param = {'notebook_path': notebook['path'] + notebook['name']}
66 r = requests.post(self.session_url(), params=param)
64 model = {'notebook': {'name':notebook['name'], 'path': notebook['path']}}
65 r = requests.post(self.session_url(), data=json.dumps(model, default=date_default))
67 66 session = r.json()
68 67
69 68 # GET a session
@@ -73,15 +72,17 b' class SessionAPITest(NotebookTestBase):'
73 72 self.assertEqual(r.json(), session)
74 73
75 74 # PATCH a session
76 data = {'notebook_path': 'test.ipynb'}
77 r = requests.patch(sess_url, data=jsonapi.dumps(data))
75 model = {'notebook': {'name':'test.ipynb', 'path': '/'}}
76 r = requests.patch(sess_url, data=json.dumps(model, default=date_default))
77
78 78 # Patching the notebook webservice too (just for consistency)
79 79 requests.patch(self.notebook_url() + '/Untitled0.ipynb',
80 data=jsonapi.dumps({'name':'test.ipynb'}))
80 data=json.dumps({'name':'test.ipynb'}))
81 print r.json()
81 82 assert isinstance(r.json(), dict)
82 self.assertIn('name', r.json())
83 self.assertIn('name', r.json()['notebook'])
83 84 self.assertIn('id', r.json())
84 self.assertEqual(r.json()['name'], 'test.ipynb')
85 self.assertEqual(r.json()['notebook']['name'], 'test.ipynb')
85 86 self.assertEqual(r.json()['id'], session['id'])
86 87
87 88 # DELETE a session
@@ -11,33 +11,39 b''
11 11
12 12 var IPython = (function (IPython) {
13 13
14 var Session = function(notebook_path, Notebook){
14 var Session = function(notebook_name, notebook_path, Notebook){
15 15 this.kernel = null;
16 16 this.kernel_id = null;
17 17 this.session_id = null;
18 this.notebook_name = notebook_name;
18 19 this.notebook_path = notebook_path;
19 20 this.notebook = Notebook;
20 21 this._baseProjectUrl = Notebook.baseProjectUrl()
21 22 };
22 23
23 Session.prototype.start = function(){
24 Session.prototype.start = function() {
24 25 var that = this
25 var qs = $.param({notebook_path:this.notebook_path});
26 var url = '/api/sessions' + '?' + qs;
27 $.post(url,
28 $.proxy(this.start_kernel, that),
29 'json'
30 );
26 var notebook = {'notebook':{'name': this.notebook_name, 'path': this.notebook_path}}
27 var settings = {
28 processData : false,
29 cache : false,
30 type : "POST",
31 data: JSON.stringify(notebook),
32 dataType : "json",
33 };
34 var url = this._baseProjectUrl + 'api/sessions';
35 $.ajax(url, settings);
31 36 };
32 37
33 Session.prototype.notebook_rename = function (notebook_path) {
34 this.notebook_path = notebook_path;
35 var name = {'notebook_path': notebook_path}
38 Session.prototype.notebook_rename = function (name, path) {
39 this.notebook_name = name;
40 this.notebook_path = path;
41 var notebook = {'notebook':{'name':name, 'path': path}};
36 42 var settings = {
37 43 processData : false,
38 44 cache : false,
39 45 type : "PATCH",
40 data: JSON.stringify(name),
46 data: JSON.stringify(notebook),
41 47 dataType : "json",
42 48 };
43 49 var url = this._baseProjectUrl + 'api/sessions/' + this.session_id;
General Comments 0
You need to be logged in to leave comments. Login now