##// END OF EJS Templates
Fix some HTTP status codes in sessions API
Thomas Kluyver -
Show More
@@ -1,125 +1,126 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 self.set_status(201)
66 67 self.finish(json.dumps(model, default=date_default))
67 68
68 69 class SessionHandler(IPythonHandler):
69 70
70 71 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
71 72
72 73 @web.authenticated
73 74 @json_errors
74 75 def get(self, session_id):
75 76 # Returns the JSON model for a single session
76 77 sm = self.session_manager
77 78 model = sm.get_session(id=session_id)
78 79 self.finish(json.dumps(model, default=date_default))
79 80
80 81 @web.authenticated
81 82 @json_errors
82 83 def patch(self, session_id):
83 84 # Currently, this handler is strictly for renaming notebooks
84 85 sm = self.session_manager
85 86 nbm = self.notebook_manager
86 87 km = self.kernel_manager
87 88 model = self.get_json_body()
88 89 if model is None:
89 90 raise HTTPError(400, "No JSON data provided")
90 91 changes = {}
91 92 if 'notebook' in model:
92 93 notebook = model['notebook']
93 94 if 'name' in notebook:
94 95 changes['name'] = notebook['name']
95 96 if 'path' in notebook:
96 97 changes['path'] = notebook['path']
97 98 sm.update_session(session_id, **changes)
98 99 model = sm.get_session(id=session_id)
99 100 self.finish(json.dumps(model, default=date_default))
100 101
101 102 @web.authenticated
102 103 @json_errors
103 104 def delete(self, session_id):
104 105 # Deletes the session with given session_id
105 106 sm = self.session_manager
106 107 nbm = self.notebook_manager
107 108 km = self.kernel_manager
108 109 session = sm.get_session(id=session_id)
109 110 sm.delete_session(session_id)
110 111 km.shutdown_kernel(session['kernel']['id'])
111 112 self.set_status(204)
112 113 self.finish()
113 114
114 115
115 116 #-----------------------------------------------------------------------------
116 117 # URL to handler mappings
117 118 #-----------------------------------------------------------------------------
118 119
119 120 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
120 121
121 122 default_handlers = [
122 123 (r"/api/sessions/%s" % _session_id_regex, SessionHandler),
123 124 (r"/api/sessions", SessionRootHandler)
124 125 ]
125 126
@@ -1,189 +1,187 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 21 import sqlite3
22 22
23 23 from tornado import web
24 24
25 25 from IPython.config.configurable import LoggingConfigurable
26 26 from IPython.nbformat import current
27 27 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Classes
31 31 #-----------------------------------------------------------------------------
32 32
33 33 class SessionManager(LoggingConfigurable):
34 34
35 35 # Session database initialized below
36 36 _cursor = None
37 37 _connection = None
38 38
39 39 @property
40 40 def cursor(self):
41 41 """Start a cursor and create a database called 'session'"""
42 42 if self._cursor is None:
43 43 self._cursor = self.connection.cursor()
44 44 self._cursor.execute("""CREATE TABLE session
45 45 (id, name, path, kernel_id, ws_url)""")
46 46 return self._cursor
47 47
48 48 @property
49 49 def connection(self):
50 50 """Start a database connection"""
51 51 if self._connection is None:
52 52 self._connection = sqlite3.connect(':memory:')
53 53 self._connection.row_factory = sqlite3.Row
54 54 return self._connection
55 55
56 56 def __del__(self):
57 57 """Close connection once SessionManager closes"""
58 58 self.cursor.close()
59 59
60 60 def session_exists(self, name, path):
61 61 """Check to see if the session for the given notebook exists"""
62 62 self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name,path))
63 63 reply = self.cursor.fetchone()
64 64 if reply is None:
65 65 return False
66 66 else:
67 67 return True
68 68
69 69 def get_session_id(self):
70 70 "Create a uuid for a new session"
71 71 return unicode(uuid.uuid4())
72 72
73 73 def create_session(self, name=None, path=None, kernel_id=None, ws_url=None):
74 74 """Creates a session and returns its model"""
75 75 session_id = self.get_session_id()
76 76 return self.save_session(session_id, name=name, path=path, kernel_id=kernel_id, ws_url=ws_url)
77 77
78 78 def save_session(self, session_id, name=None, path=None, kernel_id=None, ws_url=None):
79 79 """Saves the items for the session with the given session_id
80 80
81 81 Given a session_id (and any other of the arguments), this method
82 82 creates a row in the sqlite session database that holds the information
83 83 for a session.
84 84
85 85 Parameters
86 86 ----------
87 87 session_id : str
88 88 uuid for the session; this method must be given a session_id
89 89 name : str
90 90 the .ipynb notebook name that started the session
91 91 path : str
92 92 the path to the named notebook
93 93 kernel_id : str
94 94 a uuid for the kernel associated with this session
95 95 ws_url : str
96 96 the websocket url
97 97
98 98 Returns
99 99 -------
100 100 model : dict
101 101 a dictionary of the session model
102 102 """
103 103 self.cursor.execute("""INSERT INTO session VALUES
104 104 (?,?,?,?,?)""", (session_id, name, path, kernel_id, ws_url))
105 105 self.connection.commit()
106 106 return self.get_session(id=session_id)
107 107
108 108 def get_session(self, **kwargs):
109 109 """Returns the model for a particular session.
110 110
111 111 Takes a keyword argument and searches for the value in the session
112 112 database, then returns the rest of the session's info.
113 113
114 114 Parameters
115 115 ----------
116 116 **kwargs : keyword argument
117 117 must be given one of the keywords and values from the session database
118 118 (i.e. session_id, name, path, kernel_id, ws_url)
119 119
120 120 Returns
121 121 -------
122 122 model : dict
123 123 returns a dictionary that includes all the information from the
124 124 session described by the kwarg.
125 125 """
126 126 column = kwargs.keys()[0] # uses only the first kwarg that is entered
127 127 value = kwargs.values()[0]
128 128 try:
129 129 self.cursor.execute("SELECT * FROM session WHERE %s=?" %column, (value,))
130 130 except sqlite3.OperationalError:
131 131 raise TraitError("The session database has no column: %s" %column)
132 132 reply = self.cursor.fetchone()
133 133 if reply is not None:
134 134 model = self.reply_to_dictionary_model(reply)
135 135 else:
136 model = None
136 raise web.HTTPError(404, u'Session not found: %s=%r' % (column, value))
137 137 return model
138 138
139 139 def update_session(self, session_id, **kwargs):
140 140 """Updates the values in the session database.
141 141
142 142 Changes the values of the session with the given session_id
143 143 with the values from the keyword arguments.
144 144
145 145 Parameters
146 146 ----------
147 147 session_id : str
148 148 a uuid that identifies a session in the sqlite3 database
149 149 **kwargs : str
150 150 the key must correspond to a column title in session database,
151 151 and the value replaces the current value in the session
152 152 with session_id.
153 153 """
154 column = kwargs.keys() # uses only the first kwarg that is entered
155 value = kwargs.values()
156 154 for kwarg in kwargs:
157 155 try:
158 156 self.cursor.execute("UPDATE session SET %s=? WHERE id=?" %kwarg, (kwargs[kwarg], session_id))
159 157 self.connection.commit()
160 158 except sqlite3.OperationalError:
161 159 raise TraitError("No session exists with ID: %s" %session_id)
162 160
163 161 def reply_to_dictionary_model(self, reply):
164 162 """Takes sqlite database session row and turns it into a dictionary"""
165 163 model = {'id': reply['id'],
166 164 'notebook': {'name': reply['name'], 'path': reply['path']},
167 165 'kernel': {'id': reply['kernel_id'], 'ws_url': reply['ws_url']}}
168 166 return model
169 167
170 168 def list_sessions(self):
171 169 """Returns a list of dictionaries containing all the information from
172 170 the session database"""
173 171 session_list=[]
174 172 self.cursor.execute("SELECT * FROM session")
175 173 sessions = self.cursor.fetchall()
176 174 for session in sessions:
177 175 model = self.reply_to_dictionary_model(session)
178 176 session_list.append(model)
179 177 return session_list
180 178
181 179 def delete_session(self, session_id):
182 180 """Deletes the row in the session database with given session_id"""
183 181 # Check that session exists before deleting
184 182 model = self.get_session(id=session_id)
185 183 if model is None:
186 184 raise TraitError("The session does not exist: %s" %session_id)
187 185 else:
188 186 self.cursor.execute("DELETE FROM session WHERE id=?", (session_id,))
189 187 self.connection.commit() No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now