##// END OF EJS Templates
remove notebook['name'] from sessions
Min RK -
Show More
@@ -1,128 +1,122 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 from IPython.kernel.kernelspec import NoSuchKernel
13 from IPython.kernel.kernelspec import NoSuchKernel
14
14
15
15
16 class SessionRootHandler(IPythonHandler):
16 class SessionRootHandler(IPythonHandler):
17
17
18 @web.authenticated
18 @web.authenticated
19 @json_errors
19 @json_errors
20 def get(self):
20 def get(self):
21 # Return a list of running sessions
21 # Return a list of running sessions
22 sm = self.session_manager
22 sm = self.session_manager
23 sessions = sm.list_sessions()
23 sessions = sm.list_sessions()
24 self.finish(json.dumps(sessions, default=date_default))
24 self.finish(json.dumps(sessions, default=date_default))
25
25
26 @web.authenticated
26 @web.authenticated
27 @json_errors
27 @json_errors
28 def post(self):
28 def post(self):
29 # Creates a new session
29 # Creates a new session
30 #(unless a session already exists for the named nb)
30 #(unless a session already exists for the named nb)
31 sm = self.session_manager
31 sm = self.session_manager
32 cm = self.contents_manager
32 cm = self.contents_manager
33 km = self.kernel_manager
33 km = self.kernel_manager
34
34
35 model = self.get_json_body()
35 model = self.get_json_body()
36 if model is None:
36 if model is None:
37 raise web.HTTPError(400, "No JSON data provided")
37 raise web.HTTPError(400, "No JSON data provided")
38 try:
38 try:
39 name = model['notebook']['name']
40 except KeyError:
41 raise web.HTTPError(400, "Missing field in JSON data: notebook.name")
42 try:
43 path = model['notebook']['path']
39 path = model['notebook']['path']
44 except KeyError:
40 except KeyError:
45 raise web.HTTPError(400, "Missing field in JSON data: notebook.path")
41 raise web.HTTPError(400, "Missing field in JSON data: notebook.path")
46 try:
42 try:
47 kernel_name = model['kernel']['name']
43 kernel_name = model['kernel']['name']
48 except KeyError:
44 except KeyError:
49 self.log.debug("No kernel name specified, using default kernel")
45 self.log.debug("No kernel name specified, using default kernel")
50 kernel_name = None
46 kernel_name = None
51
47
52 # Check to see if session exists
48 # Check to see if session exists
53 if sm.session_exists(name=name, path=path):
49 if sm.session_exists(path=path):
54 model = sm.get_session(name=name, path=path)
50 model = sm.get_session(path=path)
55 else:
51 else:
56 try:
52 try:
57 model = sm.create_session(name=name, path=path, kernel_name=kernel_name)
53 model = sm.create_session(path=path, kernel_name=kernel_name)
58 except NoSuchKernel:
54 except NoSuchKernel:
59 msg = ("The '%s' kernel is not available. Please pick another "
55 msg = ("The '%s' kernel is not available. Please pick another "
60 "suitable kernel instead, or install that kernel." % kernel_name)
56 "suitable kernel instead, or install that kernel." % kernel_name)
61 status_msg = '%s not found' % kernel_name
57 status_msg = '%s not found' % kernel_name
62 self.log.warn('Kernel not found: %s' % kernel_name)
58 self.log.warn('Kernel not found: %s' % kernel_name)
63 self.set_status(501)
59 self.set_status(501)
64 self.finish(json.dumps(dict(message=msg, short_message=status_msg)))
60 self.finish(json.dumps(dict(message=msg, short_message=status_msg)))
65 return
61 return
66
62
67 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
63 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
68 self.set_header('Location', url_escape(location))
64 self.set_header('Location', url_escape(location))
69 self.set_status(201)
65 self.set_status(201)
70 self.finish(json.dumps(model, default=date_default))
66 self.finish(json.dumps(model, default=date_default))
71
67
72 class SessionHandler(IPythonHandler):
68 class SessionHandler(IPythonHandler):
73
69
74 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
70 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
75
71
76 @web.authenticated
72 @web.authenticated
77 @json_errors
73 @json_errors
78 def get(self, session_id):
74 def get(self, session_id):
79 # Returns the JSON model for a single session
75 # Returns the JSON model for a single session
80 sm = self.session_manager
76 sm = self.session_manager
81 model = sm.get_session(session_id=session_id)
77 model = sm.get_session(session_id=session_id)
82 self.finish(json.dumps(model, default=date_default))
78 self.finish(json.dumps(model, default=date_default))
83
79
84 @web.authenticated
80 @web.authenticated
85 @json_errors
81 @json_errors
86 def patch(self, session_id):
82 def patch(self, session_id):
87 # Currently, this handler is strictly for renaming notebooks
83 # Currently, this handler is strictly for renaming notebooks
88 sm = self.session_manager
84 sm = self.session_manager
89 model = self.get_json_body()
85 model = self.get_json_body()
90 if model is None:
86 if model is None:
91 raise web.HTTPError(400, "No JSON data provided")
87 raise web.HTTPError(400, "No JSON data provided")
92 changes = {}
88 changes = {}
93 if 'notebook' in model:
89 if 'notebook' in model:
94 notebook = model['notebook']
90 notebook = model['notebook']
95 if 'name' in notebook:
96 changes['name'] = notebook['name']
97 if 'path' in notebook:
91 if 'path' in notebook:
98 changes['path'] = notebook['path']
92 changes['path'] = notebook['path']
99
93
100 sm.update_session(session_id, **changes)
94 sm.update_session(session_id, **changes)
101 model = sm.get_session(session_id=session_id)
95 model = sm.get_session(session_id=session_id)
102 self.finish(json.dumps(model, default=date_default))
96 self.finish(json.dumps(model, default=date_default))
103
97
104 @web.authenticated
98 @web.authenticated
105 @json_errors
99 @json_errors
106 def delete(self, session_id):
100 def delete(self, session_id):
107 # Deletes the session with given session_id
101 # Deletes the session with given session_id
108 sm = self.session_manager
102 sm = self.session_manager
109 try:
103 try:
110 sm.delete_session(session_id)
104 sm.delete_session(session_id)
111 except KeyError:
105 except KeyError:
112 # the kernel was deleted but the session wasn't!
106 # the kernel was deleted but the session wasn't!
113 raise web.HTTPError(410, "Kernel deleted before session")
107 raise web.HTTPError(410, "Kernel deleted before session")
114 self.set_status(204)
108 self.set_status(204)
115 self.finish()
109 self.finish()
116
110
117
111
118 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
119 # URL to handler mappings
113 # URL to handler mappings
120 #-----------------------------------------------------------------------------
114 #-----------------------------------------------------------------------------
121
115
122 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
116 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
123
117
124 default_handlers = [
118 default_handlers = [
125 (r"/api/sessions/%s" % _session_id_regex, SessionHandler),
119 (r"/api/sessions/%s" % _session_id_regex, SessionHandler),
126 (r"/api/sessions", SessionRootHandler)
120 (r"/api/sessions", SessionRootHandler)
127 ]
121 ]
128
122
@@ -1,211 +1,208 b''
1 """A base class session manager."""
1 """A base class session manager."""
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 uuid
6 import uuid
7 import sqlite3
7 import sqlite3
8
8
9 from tornado import web
9 from tornado import web
10
10
11 from IPython.config.configurable import LoggingConfigurable
11 from IPython.config.configurable import LoggingConfigurable
12 from IPython.utils.py3compat import unicode_type
12 from IPython.utils.py3compat import unicode_type
13 from IPython.utils.traitlets import Instance
13 from IPython.utils.traitlets import Instance
14
14
15
15
16 class SessionManager(LoggingConfigurable):
16 class SessionManager(LoggingConfigurable):
17
17
18 kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
18 kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
19 contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
19 contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
20
20
21 # Session database initialized below
21 # Session database initialized below
22 _cursor = None
22 _cursor = None
23 _connection = None
23 _connection = None
24 _columns = {'session_id', 'name', 'path', 'kernel_id'}
24 _columns = {'session_id', 'path', 'kernel_id'}
25
25
26 @property
26 @property
27 def cursor(self):
27 def cursor(self):
28 """Start a cursor and create a database called 'session'"""
28 """Start a cursor and create a database called 'session'"""
29 if self._cursor is None:
29 if self._cursor is None:
30 self._cursor = self.connection.cursor()
30 self._cursor = self.connection.cursor()
31 self._cursor.execute("""CREATE TABLE session
31 self._cursor.execute("""CREATE TABLE session
32 (session_id, name, path, kernel_id)""")
32 (session_id, path, kernel_id)""")
33 return self._cursor
33 return self._cursor
34
34
35 @property
35 @property
36 def connection(self):
36 def connection(self):
37 """Start a database connection"""
37 """Start a database connection"""
38 if self._connection is None:
38 if self._connection is None:
39 self._connection = sqlite3.connect(':memory:')
39 self._connection = sqlite3.connect(':memory:')
40 self._connection.row_factory = sqlite3.Row
40 self._connection.row_factory = sqlite3.Row
41 return self._connection
41 return self._connection
42
42
43 def __del__(self):
43 def __del__(self):
44 """Close connection once SessionManager closes"""
44 """Close connection once SessionManager closes"""
45 self.cursor.close()
45 self.cursor.close()
46
46
47 def session_exists(self, name, path):
47 def session_exists(self, path):
48 """Check to see if the session for a given notebook exists"""
48 """Check to see if the session for a given notebook exists"""
49 self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name, path))
49 self.cursor.execute("SELECT * FROM session WHERE path=?", (path,))
50 reply = self.cursor.fetchone()
50 reply = self.cursor.fetchone()
51 if reply is None:
51 if reply is None:
52 return False
52 return False
53 else:
53 else:
54 return True
54 return True
55
55
56 def new_session_id(self):
56 def new_session_id(self):
57 "Create a uuid for a new session"
57 "Create a uuid for a new session"
58 return unicode_type(uuid.uuid4())
58 return unicode_type(uuid.uuid4())
59
59
60 def create_session(self, name=None, path=None, kernel_name=None):
60 def create_session(self, path=None, kernel_name=None):
61 """Creates a session and returns its model"""
61 """Creates a session and returns its model"""
62 session_id = self.new_session_id()
62 session_id = self.new_session_id()
63 # allow nbm to specify kernels cwd
63 # allow nbm to specify kernels cwd
64 kernel_path = self.contents_manager.get_kernel_path(name=name, path=path)
64 kernel_path = self.contents_manager.get_kernel_path(path=path)
65 kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
65 kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
66 kernel_name=kernel_name)
66 kernel_name=kernel_name)
67 return self.save_session(session_id, name=name, path=path,
67 return self.save_session(session_id, path=path,
68 kernel_id=kernel_id)
68 kernel_id=kernel_id)
69
69
70 def save_session(self, session_id, name=None, path=None, kernel_id=None):
70 def save_session(self, session_id, path=None, kernel_id=None):
71 """Saves the items for the session with the given session_id
71 """Saves the items for the session with the given session_id
72
72
73 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
74 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
75 for a session.
75 for a session.
76
76
77 Parameters
77 Parameters
78 ----------
78 ----------
79 session_id : str
79 session_id : str
80 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
81 name : str
82 the .ipynb notebook name that started the session
83 path : str
81 path : str
84 the path to the named notebook
82 the path for the given notebook
85 kernel_id : str
83 kernel_id : str
86 a uuid for the kernel associated with this session
84 a uuid for the kernel associated with this session
87
85
88 Returns
86 Returns
89 -------
87 -------
90 model : dict
88 model : dict
91 a dictionary of the session model
89 a dictionary of the session model
92 """
90 """
93 self.cursor.execute("INSERT INTO session VALUES (?,?,?,?)",
91 self.cursor.execute("INSERT INTO session VALUES (?,?,?)",
94 (session_id, name, path, kernel_id)
92 (session_id, path, kernel_id)
95 )
93 )
96 return self.get_session(session_id=session_id)
94 return self.get_session(session_id=session_id)
97
95
98 def get_session(self, **kwargs):
96 def get_session(self, **kwargs):
99 """Returns the model for a particular session.
97 """Returns the model for a particular session.
100
98
101 Takes a keyword argument and searches for the value in the session
99 Takes a keyword argument and searches for the value in the session
102 database, then returns the rest of the session's info.
100 database, then returns the rest of the session's info.
103
101
104 Parameters
102 Parameters
105 ----------
103 ----------
106 **kwargs : keyword argument
104 **kwargs : keyword argument
107 must be given one of the keywords and values from the session database
105 must be given one of the keywords and values from the session database
108 (i.e. session_id, name, path, kernel_id)
106 (i.e. session_id, path, kernel_id)
109
107
110 Returns
108 Returns
111 -------
109 -------
112 model : dict
110 model : dict
113 returns a dictionary that includes all the information from the
111 returns a dictionary that includes all the information from the
114 session described by the kwarg.
112 session described by the kwarg.
115 """
113 """
116 if not kwargs:
114 if not kwargs:
117 raise TypeError("must specify a column to query")
115 raise TypeError("must specify a column to query")
118
116
119 conditions = []
117 conditions = []
120 for column in kwargs.keys():
118 for column in kwargs.keys():
121 if column not in self._columns:
119 if column not in self._columns:
122 raise TypeError("No such column: %r", column)
120 raise TypeError("No such column: %r", column)
123 conditions.append("%s=?" % column)
121 conditions.append("%s=?" % column)
124
122
125 query = "SELECT * FROM session WHERE %s" % (' AND '.join(conditions))
123 query = "SELECT * FROM session WHERE %s" % (' AND '.join(conditions))
126
124
127 self.cursor.execute(query, list(kwargs.values()))
125 self.cursor.execute(query, list(kwargs.values()))
128 try:
126 try:
129 row = self.cursor.fetchone()
127 row = self.cursor.fetchone()
130 except KeyError:
128 except KeyError:
131 # The kernel is missing, so the session just got deleted.
129 # The kernel is missing, so the session just got deleted.
132 row = None
130 row = None
133
131
134 if row is None:
132 if row is None:
135 q = []
133 q = []
136 for key, value in kwargs.items():
134 for key, value in kwargs.items():
137 q.append("%s=%r" % (key, value))
135 q.append("%s=%r" % (key, value))
138
136
139 raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q)))
137 raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q)))
140
138
141 return self.row_to_model(row)
139 return self.row_to_model(row)
142
140
143 def update_session(self, session_id, **kwargs):
141 def update_session(self, session_id, **kwargs):
144 """Updates the values in the session database.
142 """Updates the values in the session database.
145
143
146 Changes the values of the session with the given session_id
144 Changes the values of the session with the given session_id
147 with the values from the keyword arguments.
145 with the values from the keyword arguments.
148
146
149 Parameters
147 Parameters
150 ----------
148 ----------
151 session_id : str
149 session_id : str
152 a uuid that identifies a session in the sqlite3 database
150 a uuid that identifies a session in the sqlite3 database
153 **kwargs : str
151 **kwargs : str
154 the key must correspond to a column title in session database,
152 the key must correspond to a column title in session database,
155 and the value replaces the current value in the session
153 and the value replaces the current value in the session
156 with session_id.
154 with session_id.
157 """
155 """
158 self.get_session(session_id=session_id)
156 self.get_session(session_id=session_id)
159
157
160 if not kwargs:
158 if not kwargs:
161 # no changes
159 # no changes
162 return
160 return
163
161
164 sets = []
162 sets = []
165 for column in kwargs.keys():
163 for column in kwargs.keys():
166 if column not in self._columns:
164 if column not in self._columns:
167 raise TypeError("No such column: %r" % column)
165 raise TypeError("No such column: %r" % column)
168 sets.append("%s=?" % column)
166 sets.append("%s=?" % column)
169 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
167 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
170 self.cursor.execute(query, list(kwargs.values()) + [session_id])
168 self.cursor.execute(query, list(kwargs.values()) + [session_id])
171
169
172 def row_to_model(self, row):
170 def row_to_model(self, row):
173 """Takes sqlite database session row and turns it into a dictionary"""
171 """Takes sqlite database session row and turns it into a dictionary"""
174 if row['kernel_id'] not in self.kernel_manager:
172 if row['kernel_id'] not in self.kernel_manager:
175 # The kernel was killed or died without deleting the session.
173 # The kernel was killed or died without deleting the session.
176 # We can't use delete_session here because that tries to find
174 # We can't use delete_session here because that tries to find
177 # and shut down the kernel.
175 # and shut down the kernel.
178 self.cursor.execute("DELETE FROM session WHERE session_id=?",
176 self.cursor.execute("DELETE FROM session WHERE session_id=?",
179 (row['session_id'],))
177 (row['session_id'],))
180 raise KeyError
178 raise KeyError
181
179
182 model = {
180 model = {
183 'id': row['session_id'],
181 'id': row['session_id'],
184 'notebook': {
182 'notebook': {
185 'name': row['name'],
186 'path': row['path']
183 'path': row['path']
187 },
184 },
188 'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
185 'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
189 }
186 }
190 return model
187 return model
191
188
192 def list_sessions(self):
189 def list_sessions(self):
193 """Returns a list of dictionaries containing all the information from
190 """Returns a list of dictionaries containing all the information from
194 the session database"""
191 the session database"""
195 c = self.cursor.execute("SELECT * FROM session")
192 c = self.cursor.execute("SELECT * FROM session")
196 result = []
193 result = []
197 # We need to use fetchall() here, because row_to_model can delete rows,
194 # We need to use fetchall() here, because row_to_model can delete rows,
198 # which messes up the cursor if we're iterating over rows.
195 # which messes up the cursor if we're iterating over rows.
199 for row in c.fetchall():
196 for row in c.fetchall():
200 try:
197 try:
201 result.append(self.row_to_model(row))
198 result.append(self.row_to_model(row))
202 except KeyError:
199 except KeyError:
203 pass
200 pass
204 return result
201 return result
205
202
206 def delete_session(self, session_id):
203 def delete_session(self, session_id):
207 """Deletes the row in the session database with given session_id"""
204 """Deletes the row in the session database with given session_id"""
208 # Check that session exists before deleting
205 # Check that session exists before deleting
209 session = self.get_session(session_id=session_id)
206 session = self.get_session(session_id=session_id)
210 self.kernel_manager.shutdown_kernel(session['kernel']['id'])
207 self.kernel_manager.shutdown_kernel(session['kernel']['id'])
211 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
208 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
@@ -1,140 +1,154 b''
1 """Tests for the session manager."""
1 """Tests for the session manager."""
2
2
3 from unittest import TestCase
3 from unittest import TestCase
4
4
5 from tornado import web
5 from tornado import web
6
6
7 from ..sessionmanager import SessionManager
7 from ..sessionmanager import SessionManager
8 from IPython.html.services.kernels.kernelmanager import MappingKernelManager
8 from IPython.html.services.kernels.kernelmanager import MappingKernelManager
9
9
10 class DummyKernel(object):
10 class DummyKernel(object):
11 def __init__(self, kernel_name='python'):
11 def __init__(self, kernel_name='python'):
12 self.kernel_name = kernel_name
12 self.kernel_name = kernel_name
13
13
14 class DummyMKM(MappingKernelManager):
14 class DummyMKM(MappingKernelManager):
15 """MappingKernelManager interface that doesn't start kernels, for testing"""
15 """MappingKernelManager interface that doesn't start kernels, for testing"""
16 def __init__(self, *args, **kwargs):
16 def __init__(self, *args, **kwargs):
17 super(DummyMKM, self).__init__(*args, **kwargs)
17 super(DummyMKM, self).__init__(*args, **kwargs)
18 self.id_letters = iter(u'ABCDEFGHIJK')
18 self.id_letters = iter(u'ABCDEFGHIJK')
19
19
20 def _new_id(self):
20 def _new_id(self):
21 return next(self.id_letters)
21 return next(self.id_letters)
22
22
23 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
23 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
24 kernel_id = kernel_id or self._new_id()
24 kernel_id = kernel_id or self._new_id()
25 self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name)
25 self._kernels[kernel_id] = DummyKernel(kernel_name=kernel_name)
26 return kernel_id
26 return kernel_id
27
27
28 def shutdown_kernel(self, kernel_id, now=False):
28 def shutdown_kernel(self, kernel_id, now=False):
29 del self._kernels[kernel_id]
29 del self._kernels[kernel_id]
30
30
31 class TestSessionManager(TestCase):
31 class TestSessionManager(TestCase):
32
32
33 def test_get_session(self):
33 def test_get_session(self):
34 sm = SessionManager(kernel_manager=DummyMKM())
34 sm = SessionManager(kernel_manager=DummyMKM())
35 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
35 session_id = sm.create_session(path='/path/to/test.ipynb',
36 kernel_name='bar')['id']
36 kernel_name='bar')['id']
37 model = sm.get_session(session_id=session_id)
37 model = sm.get_session(session_id=session_id)
38 expected = {'id':session_id,
38 expected = {'id':session_id,
39 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'},
39 'notebook':{'path': u'/path/to/test.ipynb'},
40 'kernel': {'id':u'A', 'name': 'bar'}}
40 'kernel': {'id':u'A', 'name': 'bar'}}
41 self.assertEqual(model, expected)
41 self.assertEqual(model, expected)
42
42
43 def test_bad_get_session(self):
43 def test_bad_get_session(self):
44 # Should raise error if a bad key is passed to the database.
44 # Should raise error if a bad key is passed to the database.
45 sm = SessionManager(kernel_manager=DummyMKM())
45 sm = SessionManager(kernel_manager=DummyMKM())
46 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
46 session_id = sm.create_session(path='/path/to/test.ipynb',
47 kernel_name='foo')['id']
47 kernel_name='foo')['id']
48 self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword
48 self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword
49
49
50 def test_get_session_dead_kernel(self):
50 def test_get_session_dead_kernel(self):
51 sm = SessionManager(kernel_manager=DummyMKM())
51 sm = SessionManager(kernel_manager=DummyMKM())
52 session = sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python')
52 session = sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python')
53 # kill the kernel
53 # kill the kernel
54 sm.kernel_manager.shutdown_kernel(session['kernel']['id'])
54 sm.kernel_manager.shutdown_kernel(session['kernel']['id'])
55 with self.assertRaises(KeyError):
55 with self.assertRaises(KeyError):
56 sm.get_session(session_id=session['id'])
56 sm.get_session(session_id=session['id'])
57 # no sessions left
57 # no sessions left
58 listed = sm.list_sessions()
58 listed = sm.list_sessions()
59 self.assertEqual(listed, [])
59 self.assertEqual(listed, [])
60
60
61 def test_list_sessions(self):
61 def test_list_sessions(self):
62 sm = SessionManager(kernel_manager=DummyMKM())
62 sm = SessionManager(kernel_manager=DummyMKM())
63 sessions = [
63 sessions = [
64 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
64 sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'),
65 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
65 sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'),
66 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
66 sm.create_session(path='/path/to/3/test3.ipynb', kernel_name='python'),
67 ]
67 ]
68 sessions = sm.list_sessions()
68 sessions = sm.list_sessions()
69 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
69 expected = [
70 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
70 {
71 {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb',
71 'id':sessions[0]['id'],
72 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}},
72 'notebook':{'path': u'/path/to/1/test1.ipynb'},
73 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
73 'kernel':{'id':u'A', 'name':'python'}
74 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
74 }, {
75 'id':sessions[1]['id'],
76 'notebook': {'path': u'/path/to/2/test2.ipynb'},
77 'kernel':{'id':u'B', 'name':'python'}
78 }, {
79 'id':sessions[2]['id'],
80 'notebook':{'path': u'/path/to/3/test3.ipynb'},
81 'kernel':{'id':u'C', 'name':'python'}
82 }
83 ]
75 self.assertEqual(sessions, expected)
84 self.assertEqual(sessions, expected)
76
85
77 def test_list_sessions_dead_kernel(self):
86 def test_list_sessions_dead_kernel(self):
78 sm = SessionManager(kernel_manager=DummyMKM())
87 sm = SessionManager(kernel_manager=DummyMKM())
79 sessions = [
88 sessions = [
80 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
89 sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'),
81 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
90 sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'),
82 ]
91 ]
83 # kill one of the kernels
92 # kill one of the kernels
84 sm.kernel_manager.shutdown_kernel(sessions[0]['kernel']['id'])
93 sm.kernel_manager.shutdown_kernel(sessions[0]['kernel']['id'])
85 listed = sm.list_sessions()
94 listed = sm.list_sessions()
86 expected = [
95 expected = [
87 {
96 {
88 'id': sessions[1]['id'],
97 'id': sessions[1]['id'],
89 'notebook': {
98 'notebook': {
90 'name': u'test2.ipynb',
99 'path': u'/path/to/2/test2.ipynb',
91 'path': u'/path/to/2/',
92 },
100 },
93 'kernel': {
101 'kernel': {
94 'id': u'B',
102 'id': u'B',
95 'name':'python',
103 'name':'python',
96 }
104 }
97 }
105 }
98 ]
106 ]
99 self.assertEqual(listed, expected)
107 self.assertEqual(listed, expected)
100
108
101 def test_update_session(self):
109 def test_update_session(self):
102 sm = SessionManager(kernel_manager=DummyMKM())
110 sm = SessionManager(kernel_manager=DummyMKM())
103 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
111 session_id = sm.create_session(path='/path/to/test.ipynb',
104 kernel_name='julia')['id']
112 kernel_name='julia')['id']
105 sm.update_session(session_id, name='new_name.ipynb')
113 sm.update_session(session_id, path='/path/to/new_name.ipynb')
106 model = sm.get_session(session_id=session_id)
114 model = sm.get_session(session_id=session_id)
107 expected = {'id':session_id,
115 expected = {'id':session_id,
108 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'},
116 'notebook':{'path': u'/path/to/new_name.ipynb'},
109 'kernel':{'id':u'A', 'name':'julia'}}
117 'kernel':{'id':u'A', 'name':'julia'}}
110 self.assertEqual(model, expected)
118 self.assertEqual(model, expected)
111
119
112 def test_bad_update_session(self):
120 def test_bad_update_session(self):
113 # try to update a session with a bad keyword ~ raise error
121 # try to update a session with a bad keyword ~ raise error
114 sm = SessionManager(kernel_manager=DummyMKM())
122 sm = SessionManager(kernel_manager=DummyMKM())
115 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
123 session_id = sm.create_session(path='/path/to/test.ipynb',
116 kernel_name='ir')['id']
124 kernel_name='ir')['id']
117 self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
125 self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
118
126
119 def test_delete_session(self):
127 def test_delete_session(self):
120 sm = SessionManager(kernel_manager=DummyMKM())
128 sm = SessionManager(kernel_manager=DummyMKM())
121 sessions = [
129 sessions = [
122 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
130 sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'),
123 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
131 sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'),
124 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
132 sm.create_session(path='/path/to/3/test3.ipynb', kernel_name='python'),
125 ]
133 ]
126 sm.delete_session(sessions[1]['id'])
134 sm.delete_session(sessions[1]['id'])
127 new_sessions = sm.list_sessions()
135 new_sessions = sm.list_sessions()
128 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
136 expected = [{
129 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
137 'id': sessions[0]['id'],
130 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
138 'notebook': {'path': u'/path/to/1/test1.ipynb'},
131 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
139 'kernel': {'id':u'A', 'name':'python'}
140 }, {
141 'id': sessions[2]['id'],
142 'notebook': {'path': u'/path/to/3/test3.ipynb'},
143 'kernel': {'id':u'C', 'name':'python'}
144 }
145 ]
132 self.assertEqual(new_sessions, expected)
146 self.assertEqual(new_sessions, expected)
133
147
134 def test_bad_delete_session(self):
148 def test_bad_delete_session(self):
135 # try to delete a session that doesn't exist ~ raise error
149 # try to delete a session that doesn't exist ~ raise error
136 sm = SessionManager(kernel_manager=DummyMKM())
150 sm = SessionManager(kernel_manager=DummyMKM())
137 sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python')
151 sm.create_session(path='/path/to/test.ipynb', kernel_name='python')
138 self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
152 self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
139 self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
153 self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
140
154
@@ -1,117 +1,115 b''
1 """Test the sessions web service API."""
1 """Test the sessions web service API."""
2
2
3 import errno
3 import errno
4 import io
4 import io
5 import os
5 import os
6 import json
6 import json
7 import requests
7 import requests
8 import shutil
8 import shutil
9
9
10 pjoin = os.path.join
10 pjoin = os.path.join
11
11
12 from IPython.html.utils import url_path_join
12 from IPython.html.utils import url_path_join
13 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
13 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
14 from IPython.nbformat.v4 import new_notebook
14 from IPython.nbformat.v4 import new_notebook
15 from IPython.nbformat import write
15 from IPython.nbformat import write
16
16
17 class SessionAPI(object):
17 class SessionAPI(object):
18 """Wrapper for notebook API calls."""
18 """Wrapper for notebook API calls."""
19 def __init__(self, base_url):
19 def __init__(self, base_url):
20 self.base_url = base_url
20 self.base_url = base_url
21
21
22 def _req(self, verb, path, body=None):
22 def _req(self, verb, path, body=None):
23 response = requests.request(verb,
23 response = requests.request(verb,
24 url_path_join(self.base_url, 'api/sessions', path), data=body)
24 url_path_join(self.base_url, 'api/sessions', path), data=body)
25
25
26 if 400 <= response.status_code < 600:
26 if 400 <= response.status_code < 600:
27 try:
27 try:
28 response.reason = response.json()['message']
28 response.reason = response.json()['message']
29 except:
29 except:
30 pass
30 pass
31 response.raise_for_status()
31 response.raise_for_status()
32
32
33 return response
33 return response
34
34
35 def list(self):
35 def list(self):
36 return self._req('GET', '')
36 return self._req('GET', '')
37
37
38 def get(self, id):
38 def get(self, id):
39 return self._req('GET', id)
39 return self._req('GET', id)
40
40
41 def create(self, name, path, kernel_name='python'):
41 def create(self, path, kernel_name='python'):
42 body = json.dumps({'notebook': {'name':name, 'path':path},
42 body = json.dumps({'notebook': {'path':path},
43 'kernel': {'name': kernel_name}})
43 'kernel': {'name': kernel_name}})
44 return self._req('POST', '', body)
44 return self._req('POST', '', body)
45
45
46 def modify(self, id, name, path):
46 def modify(self, id, path):
47 body = json.dumps({'notebook': {'name':name, 'path':path}})
47 body = json.dumps({'notebook': {'path':path}})
48 return self._req('PATCH', id, body)
48 return self._req('PATCH', id, body)
49
49
50 def delete(self, id):
50 def delete(self, id):
51 return self._req('DELETE', id)
51 return self._req('DELETE', id)
52
52
53 class SessionAPITest(NotebookTestBase):
53 class SessionAPITest(NotebookTestBase):
54 """Test the sessions web service API"""
54 """Test the sessions web service API"""
55 def setUp(self):
55 def setUp(self):
56 nbdir = self.notebook_dir.name
56 nbdir = self.notebook_dir.name
57 try:
57 try:
58 os.mkdir(pjoin(nbdir, 'foo'))
58 os.mkdir(pjoin(nbdir, 'foo'))
59 except OSError as e:
59 except OSError as e:
60 # Deleting the folder in an earlier test may have failed
60 # Deleting the folder in an earlier test may have failed
61 if e.errno != errno.EEXIST:
61 if e.errno != errno.EEXIST:
62 raise
62 raise
63
63
64 with io.open(pjoin(nbdir, 'foo', 'nb1.ipynb'), 'w',
64 with io.open(pjoin(nbdir, 'foo', 'nb1.ipynb'), 'w',
65 encoding='utf-8') as f:
65 encoding='utf-8') as f:
66 nb = new_notebook()
66 nb = new_notebook()
67 write(nb, f, version=4)
67 write(nb, f, version=4)
68
68
69 self.sess_api = SessionAPI(self.base_url())
69 self.sess_api = SessionAPI(self.base_url())
70
70
71 def tearDown(self):
71 def tearDown(self):
72 for session in self.sess_api.list().json():
72 for session in self.sess_api.list().json():
73 self.sess_api.delete(session['id'])
73 self.sess_api.delete(session['id'])
74 shutil.rmtree(pjoin(self.notebook_dir.name, 'foo'),
74 shutil.rmtree(pjoin(self.notebook_dir.name, 'foo'),
75 ignore_errors=True)
75 ignore_errors=True)
76
76
77 def test_create(self):
77 def test_create(self):
78 sessions = self.sess_api.list().json()
78 sessions = self.sess_api.list().json()
79 self.assertEqual(len(sessions), 0)
79 self.assertEqual(len(sessions), 0)
80
80
81 resp = self.sess_api.create('nb1.ipynb', 'foo')
81 resp = self.sess_api.create('foo/nb1.ipynb')
82 self.assertEqual(resp.status_code, 201)
82 self.assertEqual(resp.status_code, 201)
83 newsession = resp.json()
83 newsession = resp.json()
84 self.assertIn('id', newsession)
84 self.assertIn('id', newsession)
85 self.assertEqual(newsession['notebook']['name'], 'nb1.ipynb')
85 self.assertEqual(newsession['notebook']['path'], 'foo/nb1.ipynb')
86 self.assertEqual(newsession['notebook']['path'], 'foo')
87 self.assertEqual(resp.headers['Location'], '/api/sessions/{0}'.format(newsession['id']))
86 self.assertEqual(resp.headers['Location'], '/api/sessions/{0}'.format(newsession['id']))
88
87
89 sessions = self.sess_api.list().json()
88 sessions = self.sess_api.list().json()
90 self.assertEqual(sessions, [newsession])
89 self.assertEqual(sessions, [newsession])
91
90
92 # Retrieve it
91 # Retrieve it
93 sid = newsession['id']
92 sid = newsession['id']
94 got = self.sess_api.get(sid).json()
93 got = self.sess_api.get(sid).json()
95 self.assertEqual(got, newsession)
94 self.assertEqual(got, newsession)
96
95
97 def test_delete(self):
96 def test_delete(self):
98 newsession = self.sess_api.create('nb1.ipynb', 'foo').json()
97 newsession = self.sess_api.create('foo/nb1.ipynb').json()
99 sid = newsession['id']
98 sid = newsession['id']
100
99
101 resp = self.sess_api.delete(sid)
100 resp = self.sess_api.delete(sid)
102 self.assertEqual(resp.status_code, 204)
101 self.assertEqual(resp.status_code, 204)
103
102
104 sessions = self.sess_api.list().json()
103 sessions = self.sess_api.list().json()
105 self.assertEqual(sessions, [])
104 self.assertEqual(sessions, [])
106
105
107 with assert_http_error(404):
106 with assert_http_error(404):
108 self.sess_api.get(sid)
107 self.sess_api.get(sid)
109
108
110 def test_modify(self):
109 def test_modify(self):
111 newsession = self.sess_api.create('nb1.ipynb', 'foo').json()
110 newsession = self.sess_api.create('foo/nb1.ipynb').json()
112 sid = newsession['id']
111 sid = newsession['id']
113
112
114 changed = self.sess_api.modify(sid, 'nb2.ipynb', '').json()
113 changed = self.sess_api.modify(sid, 'nb2.ipynb').json()
115 self.assertEqual(changed['id'], sid)
114 self.assertEqual(changed['id'], sid)
116 self.assertEqual(changed['notebook']['name'], 'nb2.ipynb')
115 self.assertEqual(changed['notebook']['path'], 'nb2.ipynb')
117 self.assertEqual(changed['notebook']['path'], '')
General Comments 0
You need to be logged in to leave comments. Login now