##// END OF EJS Templates
Clean up sessions code from static analysis
Thomas Kluyver -
Show More
@@ -1,126 +1,123 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 import json
20 20
21 21 from tornado import web
22 22 from IPython.utils.jsonutil import date_default
23 23 from ...base.handlers import IPythonHandler, json_errors
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Session web service handlers
27 27 #-----------------------------------------------------------------------------
28 28
29 29
30 30 class SessionRootHandler(IPythonHandler):
31 31
32 32 @web.authenticated
33 33 @json_errors
34 34 def get(self):
35 35 # Return a list of running sessions
36 36 sm = self.session_manager
37 37 sessions = sm.list_sessions()
38 38 self.finish(json.dumps(sessions, default=date_default))
39 39
40 40 @web.authenticated
41 41 @json_errors
42 42 def post(self):
43 43 # Creates a new session
44 44 #(unless a session already exists for the named nb)
45 45 sm = self.session_manager
46 46 nbm = self.notebook_manager
47 47 km = self.kernel_manager
48 48 model = self.get_json_body()
49 49 if model is None:
50 50 raise web.HTTPError(400, "No JSON data provided")
51 51 try:
52 52 name = model['notebook']['name']
53 53 except KeyError:
54 54 raise web.HTTPError(400, "Missing field in JSON data: name")
55 55 try:
56 56 path = model['notebook']['path']
57 57 except KeyError:
58 58 raise web.HTTPError(400, "Missing field in JSON data: path")
59 59 # Check to see if session exists
60 60 if sm.session_exists(name=name, path=path):
61 61 model = sm.get_session(name=name, path=path)
62 62 else:
63 63 kernel_id = km.start_kernel(cwd=nbm.notebook_dir)
64 64 model = sm.create_session(name=name, path=path, kernel_id=kernel_id, ws_url=self.ws_url)
65 65 self.set_header('Location', '{0}/api/sessions/{1}'.format(self.base_project_url, model['id']))
66 66 self.set_status(201)
67 67 self.finish(json.dumps(model, default=date_default))
68 68
69 69 class SessionHandler(IPythonHandler):
70 70
71 71 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
72 72
73 73 @web.authenticated
74 74 @json_errors
75 75 def get(self, session_id):
76 76 # Returns the JSON model for a single session
77 77 sm = self.session_manager
78 78 model = sm.get_session(id=session_id)
79 79 self.finish(json.dumps(model, default=date_default))
80 80
81 81 @web.authenticated
82 82 @json_errors
83 83 def patch(self, session_id):
84 84 # Currently, this handler is strictly for renaming notebooks
85 85 sm = self.session_manager
86 nbm = self.notebook_manager
87 km = self.kernel_manager
88 86 model = self.get_json_body()
89 87 if model is None:
90 raise HTTPError(400, "No JSON data provided")
88 raise web.HTTPError(400, "No JSON data provided")
91 89 changes = {}
92 90 if 'notebook' in model:
93 91 notebook = model['notebook']
94 92 if 'name' in notebook:
95 93 changes['name'] = notebook['name']
96 94 if 'path' in notebook:
97 95 changes['path'] = notebook['path']
98 96 sm.update_session(session_id, **changes)
99 97 model = sm.get_session(id=session_id)
100 98 self.finish(json.dumps(model, default=date_default))
101 99
102 100 @web.authenticated
103 101 @json_errors
104 102 def delete(self, session_id):
105 103 # Deletes the session with given session_id
106 104 sm = self.session_manager
107 nbm = self.notebook_manager
108 105 km = self.kernel_manager
109 106 session = sm.get_session(id=session_id)
110 107 sm.delete_session(session_id)
111 108 km.shutdown_kernel(session['kernel']['id'])
112 109 self.set_status(204)
113 110 self.finish()
114 111
115 112
116 113 #-----------------------------------------------------------------------------
117 114 # URL to handler mappings
118 115 #-----------------------------------------------------------------------------
119 116
120 117 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
121 118
122 119 default_handlers = [
123 120 (r"/api/sessions/%s" % _session_id_regex, SessionHandler),
124 121 (r"/api/sessions", SessionRootHandler)
125 122 ]
126 123
@@ -1,187 +1,185 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 import os
20 19 import uuid
21 20 import sqlite3
22 21
23 22 from tornado import web
24 23
25 24 from IPython.config.configurable import LoggingConfigurable
26 from IPython.nbformat import current
27 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
25 from IPython.utils.traitlets import TraitError
28 26
29 27 #-----------------------------------------------------------------------------
30 28 # Classes
31 29 #-----------------------------------------------------------------------------
32 30
33 31 class SessionManager(LoggingConfigurable):
34 32
35 33 # Session database initialized below
36 34 _cursor = None
37 35 _connection = None
38 36
39 37 @property
40 38 def cursor(self):
41 39 """Start a cursor and create a database called 'session'"""
42 40 if self._cursor is None:
43 41 self._cursor = self.connection.cursor()
44 42 self._cursor.execute("""CREATE TABLE session
45 43 (id, name, path, kernel_id, ws_url)""")
46 44 return self._cursor
47 45
48 46 @property
49 47 def connection(self):
50 48 """Start a database connection"""
51 49 if self._connection is None:
52 50 self._connection = sqlite3.connect(':memory:')
53 51 self._connection.row_factory = sqlite3.Row
54 52 return self._connection
55 53
56 54 def __del__(self):
57 55 """Close connection once SessionManager closes"""
58 56 self.cursor.close()
59 57
60 58 def session_exists(self, name, path):
61 59 """Check to see if the session for the given notebook exists"""
62 60 self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name,path))
63 61 reply = self.cursor.fetchone()
64 62 if reply is None:
65 63 return False
66 64 else:
67 65 return True
68 66
69 67 def get_session_id(self):
70 68 "Create a uuid for a new session"
71 69 return unicode(uuid.uuid4())
72 70
73 71 def create_session(self, name=None, path=None, kernel_id=None, ws_url=None):
74 72 """Creates a session and returns its model"""
75 73 session_id = self.get_session_id()
76 74 return self.save_session(session_id, name=name, path=path, kernel_id=kernel_id, ws_url=ws_url)
77 75
78 76 def save_session(self, session_id, name=None, path=None, kernel_id=None, ws_url=None):
79 77 """Saves the items for the session with the given session_id
80 78
81 79 Given a session_id (and any other of the arguments), this method
82 80 creates a row in the sqlite session database that holds the information
83 81 for a session.
84 82
85 83 Parameters
86 84 ----------
87 85 session_id : str
88 86 uuid for the session; this method must be given a session_id
89 87 name : str
90 88 the .ipynb notebook name that started the session
91 89 path : str
92 90 the path to the named notebook
93 91 kernel_id : str
94 92 a uuid for the kernel associated with this session
95 93 ws_url : str
96 94 the websocket url
97 95
98 96 Returns
99 97 -------
100 98 model : dict
101 99 a dictionary of the session model
102 100 """
103 101 self.cursor.execute("""INSERT INTO session VALUES
104 102 (?,?,?,?,?)""", (session_id, name, path, kernel_id, ws_url))
105 103 self.connection.commit()
106 104 return self.get_session(id=session_id)
107 105
108 106 def get_session(self, **kwargs):
109 107 """Returns the model for a particular session.
110 108
111 109 Takes a keyword argument and searches for the value in the session
112 110 database, then returns the rest of the session's info.
113 111
114 112 Parameters
115 113 ----------
116 114 **kwargs : keyword argument
117 115 must be given one of the keywords and values from the session database
118 116 (i.e. session_id, name, path, kernel_id, ws_url)
119 117
120 118 Returns
121 119 -------
122 120 model : dict
123 121 returns a dictionary that includes all the information from the
124 122 session described by the kwarg.
125 123 """
126 124 column = kwargs.keys()[0] # uses only the first kwarg that is entered
127 125 value = kwargs.values()[0]
128 126 try:
129 127 self.cursor.execute("SELECT * FROM session WHERE %s=?" %column, (value,))
130 128 except sqlite3.OperationalError:
131 129 raise TraitError("The session database has no column: %s" %column)
132 130 reply = self.cursor.fetchone()
133 131 if reply is not None:
134 132 model = self.reply_to_dictionary_model(reply)
135 133 else:
136 134 raise web.HTTPError(404, u'Session not found: %s=%r' % (column, value))
137 135 return model
138 136
139 137 def update_session(self, session_id, **kwargs):
140 138 """Updates the values in the session database.
141 139
142 140 Changes the values of the session with the given session_id
143 141 with the values from the keyword arguments.
144 142
145 143 Parameters
146 144 ----------
147 145 session_id : str
148 146 a uuid that identifies a session in the sqlite3 database
149 147 **kwargs : str
150 148 the key must correspond to a column title in session database,
151 149 and the value replaces the current value in the session
152 150 with session_id.
153 151 """
154 152 for kwarg in kwargs:
155 153 try:
156 154 self.cursor.execute("UPDATE session SET %s=? WHERE id=?" %kwarg, (kwargs[kwarg], session_id))
157 155 self.connection.commit()
158 156 except sqlite3.OperationalError:
159 157 raise TraitError("No session exists with ID: %s" %session_id)
160 158
161 159 def reply_to_dictionary_model(self, reply):
162 160 """Takes sqlite database session row and turns it into a dictionary"""
163 161 model = {'id': reply['id'],
164 162 'notebook': {'name': reply['name'], 'path': reply['path']},
165 163 'kernel': {'id': reply['kernel_id'], 'ws_url': reply['ws_url']}}
166 164 return model
167 165
168 166 def list_sessions(self):
169 167 """Returns a list of dictionaries containing all the information from
170 168 the session database"""
171 169 session_list=[]
172 170 self.cursor.execute("SELECT * FROM session")
173 171 sessions = self.cursor.fetchall()
174 172 for session in sessions:
175 173 model = self.reply_to_dictionary_model(session)
176 174 session_list.append(model)
177 175 return session_list
178 176
179 177 def delete_session(self, session_id):
180 178 """Deletes the row in the session database with given session_id"""
181 179 # Check that session exists before deleting
182 180 model = self.get_session(id=session_id)
183 181 if model is None:
184 182 raise TraitError("The session does not exist: %s" %session_id)
185 183 else:
186 184 self.cursor.execute("DELETE FROM session WHERE id=?", (session_id,))
187 185 self.connection.commit() No newline at end of file
@@ -1,114 +1,113 b''
1 1 """Test the sessions web service API."""
2 2
3 3 import io
4 4 import os
5 5 import json
6 6 import requests
7 7 import shutil
8 8
9 9 pjoin = os.path.join
10 10
11 from IPython.utils.jsonutil import date_default
12 11 from IPython.html.utils import url_path_join
13 12 from IPython.html.tests.launchnotebook import NotebookTestBase
14 13 from IPython.nbformat.current import new_notebook, write
15 14
16 15 class SessionAPI(object):
17 16 """Wrapper for notebook API calls."""
18 17 def __init__(self, base_url):
19 18 self.base_url = base_url
20 19
21 20 def _req(self, verb, path, body=None):
22 21 response = requests.request(verb,
23 22 url_path_join(self.base_url, 'api/sessions', path), data=body)
24 23
25 24 if 400 <= response.status_code < 600:
26 25 try:
27 26 response.reason = response.json()['message']
28 27 except:
29 28 pass
30 29 response.raise_for_status()
31 30
32 31 return response
33 32
34 33 def list(self):
35 34 return self._req('GET', '')
36 35
37 36 def get(self, id):
38 37 return self._req('GET', id)
39 38
40 39 def create(self, name, path):
41 40 body = json.dumps({'notebook': {'name':name, 'path':path}})
42 41 return self._req('POST', '', body)
43 42
44 43 def modify(self, id, name, path):
45 44 body = json.dumps({'notebook': {'name':name, 'path':path}})
46 45 return self._req('PATCH', id, body)
47 46
48 47 def delete(self, id):
49 48 return self._req('DELETE', id)
50 49
51 50 class SessionAPITest(NotebookTestBase):
52 51 """Test the sessions web service API"""
53 52 def setUp(self):
54 53 nbdir = self.notebook_dir.name
55 54 os.mkdir(pjoin(nbdir, 'foo'))
56 55
57 56 with io.open(pjoin(nbdir, 'foo', 'nb1.ipynb'), 'w') as f:
58 57 nb = new_notebook(name='nb1')
59 58 write(nb, f, format='ipynb')
60 59
61 60 self.sess_api = SessionAPI(self.base_url())
62 61
63 62 def tearDown(self):
64 63 for session in self.sess_api.list().json():
65 64 self.sess_api.delete(session['id'])
66 65 shutil.rmtree(pjoin(self.notebook_dir.name, 'foo'))
67 66
68 67 def assert_404(self, id):
69 68 try:
70 69 self.sess_api.get(id)
71 70 except requests.HTTPError as e:
72 71 self.assertEqual(e.response.status_code, 404)
73 72 else:
74 73 assert False, "Getting nonexistent session didn't give HTTP error"
75 74
76 75 def test_create(self):
77 76 sessions = self.sess_api.list().json()
78 77 self.assertEqual(len(sessions), 0)
79 78
80 79 resp = self.sess_api.create('nb1.ipynb', 'foo')
81 80 self.assertEqual(resp.status_code, 201)
82 81 newsession = resp.json()
83 82 self.assertIn('id', newsession)
84 83 self.assertEqual(newsession['notebook']['name'], 'nb1.ipynb')
85 84 self.assertEqual(newsession['notebook']['path'], 'foo')
86 85
87 86 sessions = self.sess_api.list().json()
88 87 self.assertEqual(sessions, [newsession])
89 88
90 89 # Retrieve it
91 90 sid = newsession['id']
92 91 got = self.sess_api.get(sid).json()
93 92 self.assertEqual(got, newsession)
94 93
95 94 def test_delete(self):
96 95 newsession = self.sess_api.create('nb1.ipynb', 'foo').json()
97 96 sid = newsession['id']
98 97
99 98 resp = self.sess_api.delete(sid)
100 99 self.assertEqual(resp.status_code, 204)
101 100
102 101 sessions = self.sess_api.list().json()
103 102 self.assertEqual(sessions, [])
104 103
105 104 self.assert_404(sid)
106 105
107 106 def test_modify(self):
108 107 newsession = self.sess_api.create('nb1.ipynb', 'foo').json()
109 108 sid = newsession['id']
110 109
111 110 changed = self.sess_api.modify(sid, 'nb2.ipynb', '').json()
112 111 self.assertEqual(changed['id'], sid)
113 112 self.assertEqual(changed['notebook']['name'], 'nb2.ipynb')
114 113 self.assertEqual(changed['notebook']['path'], '')
General Comments 0
You need to be logged in to leave comments. Login now