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