##// END OF EJS Templates
allow kernel_name to be undefined in requests...
MinRK -
Show More
@@ -1,112 +1,113 b''
1 """Tornado handlers for the sessions web service."""
1 """Tornado handlers for the sessions web service."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import json
6 import json
7
7
8 from tornado import web
8 from tornado import web
9
9
10 from ...base.handlers import IPythonHandler, json_errors
10 from ...base.handlers import IPythonHandler, json_errors
11 from IPython.utils.jsonutil import date_default
11 from IPython.utils.jsonutil import date_default
12 from IPython.html.utils import url_path_join, url_escape
12 from IPython.html.utils import url_path_join, url_escape
13
13
14
14
15 class SessionRootHandler(IPythonHandler):
15 class SessionRootHandler(IPythonHandler):
16
16
17 @web.authenticated
17 @web.authenticated
18 @json_errors
18 @json_errors
19 def get(self):
19 def get(self):
20 # Return a list of running sessions
20 # Return a list of running sessions
21 sm = self.session_manager
21 sm = self.session_manager
22 sessions = sm.list_sessions()
22 sessions = sm.list_sessions()
23 self.finish(json.dumps(sessions, default=date_default))
23 self.finish(json.dumps(sessions, default=date_default))
24
24
25 @web.authenticated
25 @web.authenticated
26 @json_errors
26 @json_errors
27 def post(self):
27 def post(self):
28 # Creates a new session
28 # Creates a new session
29 #(unless a session already exists for the named nb)
29 #(unless a session already exists for the named nb)
30 sm = self.session_manager
30 sm = self.session_manager
31 cm = self.contents_manager
31 cm = self.contents_manager
32 km = self.kernel_manager
32 km = self.kernel_manager
33
33
34 model = self.get_json_body()
34 model = self.get_json_body()
35 if model is None:
35 if model is None:
36 raise web.HTTPError(400, "No JSON data provided")
36 raise web.HTTPError(400, "No JSON data provided")
37 try:
37 try:
38 name = model['notebook']['name']
38 name = model['notebook']['name']
39 except KeyError:
39 except KeyError:
40 raise web.HTTPError(400, "Missing field in JSON data: notebook.name")
40 raise web.HTTPError(400, "Missing field in JSON data: notebook.name")
41 try:
41 try:
42 path = model['notebook']['path']
42 path = model['notebook']['path']
43 except KeyError:
43 except KeyError:
44 raise web.HTTPError(400, "Missing field in JSON data: notebook.path")
44 raise web.HTTPError(400, "Missing field in JSON data: notebook.path")
45 try:
45 try:
46 kernel_name = model['kernel']['name']
46 kernel_name = model['kernel']['name']
47 except KeyError:
47 except KeyError:
48 raise web.HTTPError(400, "Missing field in JSON data: kernel.name")
48 self.log.debug("No kernel name specified, using default kernel")
49 kernel_name = None
49
50
50 # Check to see if session exists
51 # Check to see if session exists
51 if sm.session_exists(name=name, path=path):
52 if sm.session_exists(name=name, path=path):
52 model = sm.get_session(name=name, path=path)
53 model = sm.get_session(name=name, path=path)
53 else:
54 else:
54 model = sm.create_session(name=name, path=path, kernel_name=kernel_name)
55 model = sm.create_session(name=name, path=path, kernel_name=kernel_name)
55 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
56 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
56 self.set_header('Location', url_escape(location))
57 self.set_header('Location', url_escape(location))
57 self.set_status(201)
58 self.set_status(201)
58 self.finish(json.dumps(model, default=date_default))
59 self.finish(json.dumps(model, default=date_default))
59
60
60 class SessionHandler(IPythonHandler):
61 class SessionHandler(IPythonHandler):
61
62
62 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
63 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
63
64
64 @web.authenticated
65 @web.authenticated
65 @json_errors
66 @json_errors
66 def get(self, session_id):
67 def get(self, session_id):
67 # Returns the JSON model for a single session
68 # Returns the JSON model for a single session
68 sm = self.session_manager
69 sm = self.session_manager
69 model = sm.get_session(session_id=session_id)
70 model = sm.get_session(session_id=session_id)
70 self.finish(json.dumps(model, default=date_default))
71 self.finish(json.dumps(model, default=date_default))
71
72
72 @web.authenticated
73 @web.authenticated
73 @json_errors
74 @json_errors
74 def patch(self, session_id):
75 def patch(self, session_id):
75 # Currently, this handler is strictly for renaming notebooks
76 # Currently, this handler is strictly for renaming notebooks
76 sm = self.session_manager
77 sm = self.session_manager
77 model = self.get_json_body()
78 model = self.get_json_body()
78 if model is None:
79 if model is None:
79 raise web.HTTPError(400, "No JSON data provided")
80 raise web.HTTPError(400, "No JSON data provided")
80 changes = {}
81 changes = {}
81 if 'notebook' in model:
82 if 'notebook' in model:
82 notebook = model['notebook']
83 notebook = model['notebook']
83 if 'name' in notebook:
84 if 'name' in notebook:
84 changes['name'] = notebook['name']
85 changes['name'] = notebook['name']
85 if 'path' in notebook:
86 if 'path' in notebook:
86 changes['path'] = notebook['path']
87 changes['path'] = notebook['path']
87
88
88 sm.update_session(session_id, **changes)
89 sm.update_session(session_id, **changes)
89 model = sm.get_session(session_id=session_id)
90 model = sm.get_session(session_id=session_id)
90 self.finish(json.dumps(model, default=date_default))
91 self.finish(json.dumps(model, default=date_default))
91
92
92 @web.authenticated
93 @web.authenticated
93 @json_errors
94 @json_errors
94 def delete(self, session_id):
95 def delete(self, session_id):
95 # Deletes the session with given session_id
96 # Deletes the session with given session_id
96 sm = self.session_manager
97 sm = self.session_manager
97 sm.delete_session(session_id)
98 sm.delete_session(session_id)
98 self.set_status(204)
99 self.set_status(204)
99 self.finish()
100 self.finish()
100
101
101
102
102 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
103 # URL to handler mappings
104 # URL to handler mappings
104 #-----------------------------------------------------------------------------
105 #-----------------------------------------------------------------------------
105
106
106 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
107 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
107
108
108 default_handlers = [
109 default_handlers = [
109 (r"/api/sessions/%s" % _session_id_regex, SessionHandler),
110 (r"/api/sessions/%s" % _session_id_regex, SessionHandler),
110 (r"/api/sessions", SessionRootHandler)
111 (r"/api/sessions", SessionRootHandler)
111 ]
112 ]
112
113
@@ -1,227 +1,211 b''
1 """A base class session manager.
1 """A base class session manager."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Zach Sailer
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
5
19 import uuid
6 import uuid
20 import sqlite3
7 import sqlite3
21
8
22 from tornado import web
9 from tornado import web
23
10
24 from IPython.config.configurable import LoggingConfigurable
11 from IPython.config.configurable import LoggingConfigurable
25 from IPython.utils.py3compat import unicode_type
12 from IPython.utils.py3compat import unicode_type
26 from IPython.utils.traitlets import Instance
13 from IPython.utils.traitlets import Instance
27
14
28 #-----------------------------------------------------------------------------
29 # Classes
30 #-----------------------------------------------------------------------------
31
15
32 class SessionManager(LoggingConfigurable):
16 class SessionManager(LoggingConfigurable):
33
17
34 kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
18 kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
35 contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
19 contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
36
20
37 # Session database initialized below
21 # Session database initialized below
38 _cursor = None
22 _cursor = None
39 _connection = None
23 _connection = None
40 _columns = {'session_id', 'name', 'path', 'kernel_id'}
24 _columns = {'session_id', 'name', 'path', 'kernel_id'}
41
25
42 @property
26 @property
43 def cursor(self):
27 def cursor(self):
44 """Start a cursor and create a database called 'session'"""
28 """Start a cursor and create a database called 'session'"""
45 if self._cursor is None:
29 if self._cursor is None:
46 self._cursor = self.connection.cursor()
30 self._cursor = self.connection.cursor()
47 self._cursor.execute("""CREATE TABLE session
31 self._cursor.execute("""CREATE TABLE session
48 (session_id, name, path, kernel_id)""")
32 (session_id, name, path, kernel_id)""")
49 return self._cursor
33 return self._cursor
50
34
51 @property
35 @property
52 def connection(self):
36 def connection(self):
53 """Start a database connection"""
37 """Start a database connection"""
54 if self._connection is None:
38 if self._connection is None:
55 self._connection = sqlite3.connect(':memory:')
39 self._connection = sqlite3.connect(':memory:')
56 self._connection.row_factory = sqlite3.Row
40 self._connection.row_factory = sqlite3.Row
57 return self._connection
41 return self._connection
58
42
59 def __del__(self):
43 def __del__(self):
60 """Close connection once SessionManager closes"""
44 """Close connection once SessionManager closes"""
61 self.cursor.close()
45 self.cursor.close()
62
46
63 def session_exists(self, name, path):
47 def session_exists(self, name, path):
64 """Check to see if the session for a given notebook exists"""
48 """Check to see if the session for a given notebook exists"""
65 self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name, path))
49 self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name, path))
66 reply = self.cursor.fetchone()
50 reply = self.cursor.fetchone()
67 if reply is None:
51 if reply is None:
68 return False
52 return False
69 else:
53 else:
70 return True
54 return True
71
55
72 def new_session_id(self):
56 def new_session_id(self):
73 "Create a uuid for a new session"
57 "Create a uuid for a new session"
74 return unicode_type(uuid.uuid4())
58 return unicode_type(uuid.uuid4())
75
59
76 def create_session(self, name=None, path=None, kernel_name='python'):
60 def create_session(self, name=None, path=None, kernel_name=None):
77 """Creates a session and returns its model"""
61 """Creates a session and returns its model"""
78 session_id = self.new_session_id()
62 session_id = self.new_session_id()
79 # allow nbm to specify kernels cwd
63 # allow nbm to specify kernels cwd
80 kernel_path = self.contents_manager.get_kernel_path(name=name, path=path)
64 kernel_path = self.contents_manager.get_kernel_path(name=name, path=path)
81 kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
65 kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
82 kernel_name=kernel_name)
66 kernel_name=kernel_name)
83 return self.save_session(session_id, name=name, path=path,
67 return self.save_session(session_id, name=name, path=path,
84 kernel_id=kernel_id)
68 kernel_id=kernel_id)
85
69
86 def save_session(self, session_id, name=None, path=None, kernel_id=None):
70 def save_session(self, session_id, name=None, path=None, kernel_id=None):
87 """Saves the items for the session with the given session_id
71 """Saves the items for the session with the given session_id
88
72
89 Given a session_id (and any other of the arguments), this method
73 Given a session_id (and any other of the arguments), this method
90 creates a row in the sqlite session database that holds the information
74 creates a row in the sqlite session database that holds the information
91 for a session.
75 for a session.
92
76
93 Parameters
77 Parameters
94 ----------
78 ----------
95 session_id : str
79 session_id : str
96 uuid for the session; this method must be given a session_id
80 uuid for the session; this method must be given a session_id
97 name : str
81 name : str
98 the .ipynb notebook name that started the session
82 the .ipynb notebook name that started the session
99 path : str
83 path : str
100 the path to the named notebook
84 the path to the named notebook
101 kernel_id : str
85 kernel_id : str
102 a uuid for the kernel associated with this session
86 a uuid for the kernel associated with this session
103
87
104 Returns
88 Returns
105 -------
89 -------
106 model : dict
90 model : dict
107 a dictionary of the session model
91 a dictionary of the session model
108 """
92 """
109 self.cursor.execute("INSERT INTO session VALUES (?,?,?,?)",
93 self.cursor.execute("INSERT INTO session VALUES (?,?,?,?)",
110 (session_id, name, path, kernel_id)
94 (session_id, name, path, kernel_id)
111 )
95 )
112 return self.get_session(session_id=session_id)
96 return self.get_session(session_id=session_id)
113
97
114 def get_session(self, **kwargs):
98 def get_session(self, **kwargs):
115 """Returns the model for a particular session.
99 """Returns the model for a particular session.
116
100
117 Takes a keyword argument and searches for the value in the session
101 Takes a keyword argument and searches for the value in the session
118 database, then returns the rest of the session's info.
102 database, then returns the rest of the session's info.
119
103
120 Parameters
104 Parameters
121 ----------
105 ----------
122 **kwargs : keyword argument
106 **kwargs : keyword argument
123 must be given one of the keywords and values from the session database
107 must be given one of the keywords and values from the session database
124 (i.e. session_id, name, path, kernel_id)
108 (i.e. session_id, name, path, kernel_id)
125
109
126 Returns
110 Returns
127 -------
111 -------
128 model : dict
112 model : dict
129 returns a dictionary that includes all the information from the
113 returns a dictionary that includes all the information from the
130 session described by the kwarg.
114 session described by the kwarg.
131 """
115 """
132 if not kwargs:
116 if not kwargs:
133 raise TypeError("must specify a column to query")
117 raise TypeError("must specify a column to query")
134
118
135 conditions = []
119 conditions = []
136 for column in kwargs.keys():
120 for column in kwargs.keys():
137 if column not in self._columns:
121 if column not in self._columns:
138 raise TypeError("No such column: %r", column)
122 raise TypeError("No such column: %r", column)
139 conditions.append("%s=?" % column)
123 conditions.append("%s=?" % column)
140
124
141 query = "SELECT * FROM session WHERE %s" % (' AND '.join(conditions))
125 query = "SELECT * FROM session WHERE %s" % (' AND '.join(conditions))
142
126
143 self.cursor.execute(query, list(kwargs.values()))
127 self.cursor.execute(query, list(kwargs.values()))
144 try:
128 try:
145 row = self.cursor.fetchone()
129 row = self.cursor.fetchone()
146 except KeyError:
130 except KeyError:
147 # The kernel is missing, so the session just got deleted.
131 # The kernel is missing, so the session just got deleted.
148 row = None
132 row = None
149
133
150 if row is None:
134 if row is None:
151 q = []
135 q = []
152 for key, value in kwargs.items():
136 for key, value in kwargs.items():
153 q.append("%s=%r" % (key, value))
137 q.append("%s=%r" % (key, value))
154
138
155 raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q)))
139 raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q)))
156
140
157 return self.row_to_model(row)
141 return self.row_to_model(row)
158
142
159 def update_session(self, session_id, **kwargs):
143 def update_session(self, session_id, **kwargs):
160 """Updates the values in the session database.
144 """Updates the values in the session database.
161
145
162 Changes the values of the session with the given session_id
146 Changes the values of the session with the given session_id
163 with the values from the keyword arguments.
147 with the values from the keyword arguments.
164
148
165 Parameters
149 Parameters
166 ----------
150 ----------
167 session_id : str
151 session_id : str
168 a uuid that identifies a session in the sqlite3 database
152 a uuid that identifies a session in the sqlite3 database
169 **kwargs : str
153 **kwargs : str
170 the key must correspond to a column title in session database,
154 the key must correspond to a column title in session database,
171 and the value replaces the current value in the session
155 and the value replaces the current value in the session
172 with session_id.
156 with session_id.
173 """
157 """
174 self.get_session(session_id=session_id)
158 self.get_session(session_id=session_id)
175
159
176 if not kwargs:
160 if not kwargs:
177 # no changes
161 # no changes
178 return
162 return
179
163
180 sets = []
164 sets = []
181 for column in kwargs.keys():
165 for column in kwargs.keys():
182 if column not in self._columns:
166 if column not in self._columns:
183 raise TypeError("No such column: %r" % column)
167 raise TypeError("No such column: %r" % column)
184 sets.append("%s=?" % column)
168 sets.append("%s=?" % column)
185 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
169 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
186 self.cursor.execute(query, list(kwargs.values()) + [session_id])
170 self.cursor.execute(query, list(kwargs.values()) + [session_id])
187
171
188 def row_to_model(self, row):
172 def row_to_model(self, row):
189 """Takes sqlite database session row and turns it into a dictionary"""
173 """Takes sqlite database session row and turns it into a dictionary"""
190 if row['kernel_id'] not in self.kernel_manager:
174 if row['kernel_id'] not in self.kernel_manager:
191 # The kernel was killed or died without deleting the session.
175 # The kernel was killed or died without deleting the session.
192 # We can't use delete_session here because that tries to find
176 # We can't use delete_session here because that tries to find
193 # and shut down the kernel.
177 # and shut down the kernel.
194 self.cursor.execute("DELETE FROM session WHERE session_id=?",
178 self.cursor.execute("DELETE FROM session WHERE session_id=?",
195 (row['session_id'],))
179 (row['session_id'],))
196 raise KeyError
180 raise KeyError
197
181
198 model = {
182 model = {
199 'id': row['session_id'],
183 'id': row['session_id'],
200 'notebook': {
184 'notebook': {
201 'name': row['name'],
185 'name': row['name'],
202 'path': row['path']
186 'path': row['path']
203 },
187 },
204 'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
188 'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
205 }
189 }
206 return model
190 return model
207
191
208 def list_sessions(self):
192 def list_sessions(self):
209 """Returns a list of dictionaries containing all the information from
193 """Returns a list of dictionaries containing all the information from
210 the session database"""
194 the session database"""
211 c = self.cursor.execute("SELECT * FROM session")
195 c = self.cursor.execute("SELECT * FROM session")
212 result = []
196 result = []
213 # We need to use fetchall() here, because row_to_model can delete rows,
197 # We need to use fetchall() here, because row_to_model can delete rows,
214 # which messes up the cursor if we're iterating over rows.
198 # which messes up the cursor if we're iterating over rows.
215 for row in c.fetchall():
199 for row in c.fetchall():
216 try:
200 try:
217 result.append(self.row_to_model(row))
201 result.append(self.row_to_model(row))
218 except KeyError:
202 except KeyError:
219 pass
203 pass
220 return result
204 return result
221
205
222 def delete_session(self, session_id):
206 def delete_session(self, session_id):
223 """Deletes the row in the session database with given session_id"""
207 """Deletes the row in the session database with given session_id"""
224 # Check that session exists before deleting
208 # Check that session exists before deleting
225 session = self.get_session(session_id=session_id)
209 session = self.get_session(session_id=session_id)
226 self.kernel_manager.shutdown_kernel(session['kernel']['id'])
210 self.kernel_manager.shutdown_kernel(session['kernel']['id'])
227 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
211 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
General Comments 0
You need to be logged in to leave comments. Login now