##// END OF EJS Templates
Merge pull request #6412 from takluyver/sessions-rest-api-fix...
Matthias Bussonnier -
r17796:6ee949eb merge
parent child Browse files
Show More
@@ -1,206 +1,227 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 uuid
19 import uuid
20 import sqlite3
20 import sqlite3
21
21
22 from tornado import web
22 from tornado import web
23
23
24 from IPython.config.configurable import LoggingConfigurable
24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.utils.py3compat import unicode_type
25 from IPython.utils.py3compat import unicode_type
26 from IPython.utils.traitlets import Instance
26 from IPython.utils.traitlets import Instance
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Classes
29 # Classes
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 class SessionManager(LoggingConfigurable):
32 class SessionManager(LoggingConfigurable):
33
33
34 kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
34 kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
35 contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
35 contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
36
36
37 # Session database initialized below
37 # Session database initialized below
38 _cursor = None
38 _cursor = None
39 _connection = None
39 _connection = None
40 _columns = {'session_id', 'name', 'path', 'kernel_id'}
40 _columns = {'session_id', 'name', 'path', 'kernel_id'}
41
41
42 @property
42 @property
43 def cursor(self):
43 def cursor(self):
44 """Start a cursor and create a database called 'session'"""
44 """Start a cursor and create a database called 'session'"""
45 if self._cursor is None:
45 if self._cursor is None:
46 self._cursor = self.connection.cursor()
46 self._cursor = self.connection.cursor()
47 self._cursor.execute("""CREATE TABLE session
47 self._cursor.execute("""CREATE TABLE session
48 (session_id, name, path, kernel_id)""")
48 (session_id, name, path, kernel_id)""")
49 return self._cursor
49 return self._cursor
50
50
51 @property
51 @property
52 def connection(self):
52 def connection(self):
53 """Start a database connection"""
53 """Start a database connection"""
54 if self._connection is None:
54 if self._connection is None:
55 self._connection = sqlite3.connect(':memory:')
55 self._connection = sqlite3.connect(':memory:')
56 self._connection.row_factory = self.row_factory
56 self._connection.row_factory = sqlite3.Row
57 return self._connection
57 return self._connection
58
58
59 def __del__(self):
59 def __del__(self):
60 """Close connection once SessionManager closes"""
60 """Close connection once SessionManager closes"""
61 self.cursor.close()
61 self.cursor.close()
62
62
63 def session_exists(self, name, path):
63 def session_exists(self, name, path):
64 """Check to see if the session for a given notebook exists"""
64 """Check to see if the session for a given notebook exists"""
65 self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name, path))
65 self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name, path))
66 reply = self.cursor.fetchone()
66 reply = self.cursor.fetchone()
67 if reply is None:
67 if reply is None:
68 return False
68 return False
69 else:
69 else:
70 return True
70 return True
71
71
72 def new_session_id(self):
72 def new_session_id(self):
73 "Create a uuid for a new session"
73 "Create a uuid for a new session"
74 return unicode_type(uuid.uuid4())
74 return unicode_type(uuid.uuid4())
75
75
76 def create_session(self, name=None, path=None, kernel_name='python'):
76 def create_session(self, name=None, path=None, kernel_name='python'):
77 """Creates a session and returns its model"""
77 """Creates a session and returns its model"""
78 session_id = self.new_session_id()
78 session_id = self.new_session_id()
79 # allow nbm to specify kernels cwd
79 # allow nbm to specify kernels cwd
80 kernel_path = self.contents_manager.get_kernel_path(name=name, path=path)
80 kernel_path = self.contents_manager.get_kernel_path(name=name, path=path)
81 kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
81 kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
82 kernel_name=kernel_name)
82 kernel_name=kernel_name)
83 return self.save_session(session_id, name=name, path=path,
83 return self.save_session(session_id, name=name, path=path,
84 kernel_id=kernel_id)
84 kernel_id=kernel_id)
85
85
86 def save_session(self, session_id, name=None, path=None, kernel_id=None):
86 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
87 """Saves the items for the session with the given session_id
88
88
89 Given a session_id (and any other of the arguments), this method
89 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
90 creates a row in the sqlite session database that holds the information
91 for a session.
91 for a session.
92
92
93 Parameters
93 Parameters
94 ----------
94 ----------
95 session_id : str
95 session_id : str
96 uuid for the session; this method must be given a session_id
96 uuid for the session; this method must be given a session_id
97 name : str
97 name : str
98 the .ipynb notebook name that started the session
98 the .ipynb notebook name that started the session
99 path : str
99 path : str
100 the path to the named notebook
100 the path to the named notebook
101 kernel_id : str
101 kernel_id : str
102 a uuid for the kernel associated with this session
102 a uuid for the kernel associated with this session
103
103
104 Returns
104 Returns
105 -------
105 -------
106 model : dict
106 model : dict
107 a dictionary of the session model
107 a dictionary of the session model
108 """
108 """
109 self.cursor.execute("INSERT INTO session VALUES (?,?,?,?)",
109 self.cursor.execute("INSERT INTO session VALUES (?,?,?,?)",
110 (session_id, name, path, kernel_id)
110 (session_id, name, path, kernel_id)
111 )
111 )
112 return self.get_session(session_id=session_id)
112 return self.get_session(session_id=session_id)
113
113
114 def get_session(self, **kwargs):
114 def get_session(self, **kwargs):
115 """Returns the model for a particular session.
115 """Returns the model for a particular session.
116
116
117 Takes a keyword argument and searches for the value in the session
117 Takes a keyword argument and searches for the value in the session
118 database, then returns the rest of the session's info.
118 database, then returns the rest of the session's info.
119
119
120 Parameters
120 Parameters
121 ----------
121 ----------
122 **kwargs : keyword argument
122 **kwargs : keyword argument
123 must be given one of the keywords and values from the session database
123 must be given one of the keywords and values from the session database
124 (i.e. session_id, name, path, kernel_id)
124 (i.e. session_id, name, path, kernel_id)
125
125
126 Returns
126 Returns
127 -------
127 -------
128 model : dict
128 model : dict
129 returns a dictionary that includes all the information from the
129 returns a dictionary that includes all the information from the
130 session described by the kwarg.
130 session described by the kwarg.
131 """
131 """
132 if not kwargs:
132 if not kwargs:
133 raise TypeError("must specify a column to query")
133 raise TypeError("must specify a column to query")
134
134
135 conditions = []
135 conditions = []
136 for column in kwargs.keys():
136 for column in kwargs.keys():
137 if column not in self._columns:
137 if column not in self._columns:
138 raise TypeError("No such column: %r", column)
138 raise TypeError("No such column: %r", column)
139 conditions.append("%s=?" % column)
139 conditions.append("%s=?" % column)
140
140
141 query = "SELECT * FROM session WHERE %s" % (' AND '.join(conditions))
141 query = "SELECT * FROM session WHERE %s" % (' AND '.join(conditions))
142
142
143 self.cursor.execute(query, list(kwargs.values()))
143 self.cursor.execute(query, list(kwargs.values()))
144 model = self.cursor.fetchone()
144 try:
145 if model is None:
145 row = self.cursor.fetchone()
146 except KeyError:
147 # The kernel is missing, so the session just got deleted.
148 row = None
149
150 if row is None:
146 q = []
151 q = []
147 for key, value in kwargs.items():
152 for key, value in kwargs.items():
148 q.append("%s=%r" % (key, value))
153 q.append("%s=%r" % (key, value))
149
154
150 raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q)))
155 raise web.HTTPError(404, u'Session not found: %s' % (', '.join(q)))
151 return model
156
157 return self.row_to_model(row)
152
158
153 def update_session(self, session_id, **kwargs):
159 def update_session(self, session_id, **kwargs):
154 """Updates the values in the session database.
160 """Updates the values in the session database.
155
161
156 Changes the values of the session with the given session_id
162 Changes the values of the session with the given session_id
157 with the values from the keyword arguments.
163 with the values from the keyword arguments.
158
164
159 Parameters
165 Parameters
160 ----------
166 ----------
161 session_id : str
167 session_id : str
162 a uuid that identifies a session in the sqlite3 database
168 a uuid that identifies a session in the sqlite3 database
163 **kwargs : str
169 **kwargs : str
164 the key must correspond to a column title in session database,
170 the key must correspond to a column title in session database,
165 and the value replaces the current value in the session
171 and the value replaces the current value in the session
166 with session_id.
172 with session_id.
167 """
173 """
168 self.get_session(session_id=session_id)
174 self.get_session(session_id=session_id)
169
175
170 if not kwargs:
176 if not kwargs:
171 # no changes
177 # no changes
172 return
178 return
173
179
174 sets = []
180 sets = []
175 for column in kwargs.keys():
181 for column in kwargs.keys():
176 if column not in self._columns:
182 if column not in self._columns:
177 raise TypeError("No such column: %r" % column)
183 raise TypeError("No such column: %r" % column)
178 sets.append("%s=?" % column)
184 sets.append("%s=?" % column)
179 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
185 query = "UPDATE session SET %s WHERE session_id=?" % (', '.join(sets))
180 self.cursor.execute(query, list(kwargs.values()) + [session_id])
186 self.cursor.execute(query, list(kwargs.values()) + [session_id])
181
187
182 def row_factory(self, cursor, row):
188 def row_to_model(self, row):
183 """Takes sqlite database session row and turns it into a dictionary"""
189 """Takes sqlite database session row and turns it into a dictionary"""
184 row = sqlite3.Row(cursor, row)
190 if row['kernel_id'] not in self.kernel_manager:
191 # The kernel was killed or died without deleting the session.
192 # We can't use delete_session here because that tries to find
193 # and shut down the kernel.
194 self.cursor.execute("DELETE FROM session WHERE session_id=?",
195 (row['session_id'],))
196 raise KeyError
197
185 model = {
198 model = {
186 'id': row['session_id'],
199 'id': row['session_id'],
187 'notebook': {
200 'notebook': {
188 'name': row['name'],
201 'name': row['name'],
189 'path': row['path']
202 'path': row['path']
190 },
203 },
191 'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
204 'kernel': self.kernel_manager.kernel_model(row['kernel_id'])
192 }
205 }
193 return model
206 return model
194
207
195 def list_sessions(self):
208 def list_sessions(self):
196 """Returns a list of dictionaries containing all the information from
209 """Returns a list of dictionaries containing all the information from
197 the session database"""
210 the session database"""
198 c = self.cursor.execute("SELECT * FROM session")
211 c = self.cursor.execute("SELECT * FROM session")
199 return list(c.fetchall())
212 result = []
213 # 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.
215 for row in c.fetchall():
216 try:
217 result.append(self.row_to_model(row))
218 except KeyError:
219 pass
220 return result
200
221
201 def delete_session(self, session_id):
222 def delete_session(self, session_id):
202 """Deletes the row in the session database with given session_id"""
223 """Deletes the row in the session database with given session_id"""
203 # Check that session exists before deleting
224 # Check that session exists before deleting
204 session = self.get_session(session_id=session_id)
225 session = self.get_session(session_id=session_id)
205 self.kernel_manager.shutdown_kernel(session['kernel']['id'])
226 self.kernel_manager.shutdown_kernel(session['kernel']['id'])
206 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
227 self.cursor.execute("DELETE FROM session WHERE session_id=?", (session_id,))
@@ -1,105 +1,140 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(name='test.ipynb', path='/path/to/',
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':{'name':u'test.ipynb', 'path': u'/path/to/'},
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(name='test.ipynb', path='/path/to/',
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):
51 sm = SessionManager(kernel_manager=DummyMKM())
52 session = sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python')
53 # kill the kernel
54 sm.kernel_manager.shutdown_kernel(session['kernel']['id'])
55 with self.assertRaises(KeyError):
56 sm.get_session(session_id=session['id'])
57 # no sessions left
58 listed = sm.list_sessions()
59 self.assertEqual(listed, [])
60
50 def test_list_sessions(self):
61 def test_list_sessions(self):
51 sm = SessionManager(kernel_manager=DummyMKM())
62 sm = SessionManager(kernel_manager=DummyMKM())
52 sessions = [
63 sessions = [
53 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
64 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
54 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
65 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
55 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
66 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
56 ]
67 ]
57 sessions = sm.list_sessions()
68 sessions = sm.list_sessions()
58 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
69 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
59 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
70 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
60 {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb',
71 {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb',
61 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}},
72 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}},
62 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
73 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
63 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
74 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
64 self.assertEqual(sessions, expected)
75 self.assertEqual(sessions, expected)
65
76
77 def test_list_sessions_dead_kernel(self):
78 sm = SessionManager(kernel_manager=DummyMKM())
79 sessions = [
80 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
81 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
82 ]
83 # kill one of the kernels
84 sm.kernel_manager.shutdown_kernel(sessions[0]['kernel']['id'])
85 listed = sm.list_sessions()
86 expected = [
87 {
88 'id': sessions[1]['id'],
89 'notebook': {
90 'name': u'test2.ipynb',
91 'path': u'/path/to/2/',
92 },
93 'kernel': {
94 'id': u'B',
95 'name':'python',
96 }
97 }
98 ]
99 self.assertEqual(listed, expected)
100
66 def test_update_session(self):
101 def test_update_session(self):
67 sm = SessionManager(kernel_manager=DummyMKM())
102 sm = SessionManager(kernel_manager=DummyMKM())
68 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
103 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
69 kernel_name='julia')['id']
104 kernel_name='julia')['id']
70 sm.update_session(session_id, name='new_name.ipynb')
105 sm.update_session(session_id, name='new_name.ipynb')
71 model = sm.get_session(session_id=session_id)
106 model = sm.get_session(session_id=session_id)
72 expected = {'id':session_id,
107 expected = {'id':session_id,
73 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'},
108 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'},
74 'kernel':{'id':u'A', 'name':'julia'}}
109 'kernel':{'id':u'A', 'name':'julia'}}
75 self.assertEqual(model, expected)
110 self.assertEqual(model, expected)
76
111
77 def test_bad_update_session(self):
112 def test_bad_update_session(self):
78 # try to update a session with a bad keyword ~ raise error
113 # try to update a session with a bad keyword ~ raise error
79 sm = SessionManager(kernel_manager=DummyMKM())
114 sm = SessionManager(kernel_manager=DummyMKM())
80 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
115 session_id = sm.create_session(name='test.ipynb', path='/path/to/',
81 kernel_name='ir')['id']
116 kernel_name='ir')['id']
82 self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
117 self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword
83
118
84 def test_delete_session(self):
119 def test_delete_session(self):
85 sm = SessionManager(kernel_manager=DummyMKM())
120 sm = SessionManager(kernel_manager=DummyMKM())
86 sessions = [
121 sessions = [
87 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
122 sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'),
88 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
123 sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'),
89 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
124 sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'),
90 ]
125 ]
91 sm.delete_session(sessions[1]['id'])
126 sm.delete_session(sessions[1]['id'])
92 new_sessions = sm.list_sessions()
127 new_sessions = sm.list_sessions()
93 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
128 expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb',
94 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
129 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}},
95 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
130 {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb',
96 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
131 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}]
97 self.assertEqual(new_sessions, expected)
132 self.assertEqual(new_sessions, expected)
98
133
99 def test_bad_delete_session(self):
134 def test_bad_delete_session(self):
100 # try to delete a session that doesn't exist ~ raise error
135 # try to delete a session that doesn't exist ~ raise error
101 sm = SessionManager(kernel_manager=DummyMKM())
136 sm = SessionManager(kernel_manager=DummyMKM())
102 sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python')
137 sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python')
103 self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
138 self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword
104 self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
139 self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant
105
140
@@ -1,102 +1,102 b''
1 """Base class for notebook tests."""
1 """Base class for notebook tests."""
2
2
3 from __future__ import print_function
3 from __future__ import print_function
4
4
5 import sys
5 import sys
6 import time
6 import time
7 import requests
7 import requests
8 from contextlib import contextmanager
8 from contextlib import contextmanager
9 from subprocess import Popen, STDOUT
9 from subprocess import Popen, STDOUT
10 from unittest import TestCase
10 from unittest import TestCase
11
11
12 import nose
12 import nose
13
13
14 from IPython.utils.tempdir import TemporaryDirectory
14 from IPython.utils.tempdir import TemporaryDirectory
15
15
16 MAX_WAITTIME = 30 # seconds to wait for notebook server to start
16 MAX_WAITTIME = 30 # seconds to wait for notebook server to start
17 POLL_INTERVAL = 0.1 # time between attempts
17 POLL_INTERVAL = 0.1 # time between attempts
18
18
19 # TimeoutError is a builtin on Python 3. This can be removed when we stop
19 # TimeoutError is a builtin on Python 3. This can be removed when we stop
20 # supporting Python 2.
20 # supporting Python 2.
21 class TimeoutError(Exception):
21 class TimeoutError(Exception):
22 pass
22 pass
23
23
24 class NotebookTestBase(TestCase):
24 class NotebookTestBase(TestCase):
25 """A base class for tests that need a running notebook.
25 """A base class for tests that need a running notebook.
26
26
27 This creates an empty profile in a temp ipython_dir
27 This creates an empty profile in a temp ipython_dir
28 and then starts the notebook server with a separate temp notebook_dir.
28 and then starts the notebook server with a separate temp notebook_dir.
29 """
29 """
30
30
31 port = 12341
31 port = 12341
32
32
33 @classmethod
33 @classmethod
34 def wait_until_alive(cls):
34 def wait_until_alive(cls):
35 """Wait for the server to be alive"""
35 """Wait for the server to be alive"""
36 url = 'http://localhost:%i/api/contents' % cls.port
36 url = 'http://localhost:%i/api/contents' % cls.port
37 for _ in range(int(MAX_WAITTIME/POLL_INTERVAL)):
37 for _ in range(int(MAX_WAITTIME/POLL_INTERVAL)):
38 try:
38 try:
39 requests.get(url)
39 requests.get(url)
40 except Exception as e:
40 except Exception as e:
41 if cls.notebook.poll() is not None:
41 if cls.notebook.poll() is not None:
42 raise RuntimeError("The notebook server exited with status %s" \
42 raise RuntimeError("The notebook server exited with status %s" \
43 % cls.notebook.poll())
43 % cls.notebook.poll())
44 time.sleep(POLL_INTERVAL)
44 time.sleep(POLL_INTERVAL)
45 else:
45 else:
46 return
46 return
47
47
48 raise TimeoutError("The notebook server didn't start up correctly.")
48 raise TimeoutError("The notebook server didn't start up correctly.")
49
49
50 @classmethod
50 @classmethod
51 def wait_until_dead(cls):
51 def wait_until_dead(cls):
52 """Wait for the server process to terminate after shutdown"""
52 """Wait for the server process to terminate after shutdown"""
53 for _ in range(int(MAX_WAITTIME/POLL_INTERVAL)):
53 for _ in range(int(MAX_WAITTIME/POLL_INTERVAL)):
54 if cls.notebook.poll() is not None:
54 if cls.notebook.poll() is not None:
55 return
55 return
56 time.sleep(POLL_INTERVAL)
56 time.sleep(POLL_INTERVAL)
57
57
58 raise TimeoutError("Undead notebook server")
58 raise TimeoutError("Undead notebook server")
59
59
60 @classmethod
60 @classmethod
61 def setup_class(cls):
61 def setup_class(cls):
62 cls.ipython_dir = TemporaryDirectory()
62 cls.ipython_dir = TemporaryDirectory()
63 cls.notebook_dir = TemporaryDirectory()
63 cls.notebook_dir = TemporaryDirectory()
64 notebook_args = [
64 notebook_args = [
65 sys.executable, '-c',
65 sys.executable, '-c',
66 'from IPython.html.notebookapp import launch_new_instance; launch_new_instance()',
66 'from IPython.html.notebookapp import launch_new_instance; launch_new_instance()',
67 '--port=%d' % cls.port,
67 '--port=%d' % cls.port,
68 '--port-retries=0', # Don't try any other ports
68 '--port-retries=0', # Don't try any other ports
69 '--no-browser',
69 '--no-browser',
70 '--ipython-dir=%s' % cls.ipython_dir.name,
70 '--ipython-dir=%s' % cls.ipython_dir.name,
71 '--notebook-dir=%s' % cls.notebook_dir.name,
71 '--notebook-dir=%s' % cls.notebook_dir.name,
72 ]
72 ]
73 cls.notebook = Popen(notebook_args,
73 cls.notebook = Popen(notebook_args,
74 stdout=nose.iptest_stdstreams_fileno(),
74 stdout=nose.iptest_stdstreams_fileno(),
75 stderr=STDOUT,
75 stderr=STDOUT,
76 )
76 )
77 cls.wait_until_alive()
77 cls.wait_until_alive()
78
78
79 @classmethod
79 @classmethod
80 def teardown_class(cls):
80 def teardown_class(cls):
81 cls.notebook.terminate()
81 cls.notebook.terminate()
82 cls.wait_until_dead()
82 cls.wait_until_dead()
83 cls.ipython_dir.cleanup()
83 cls.ipython_dir.cleanup()
84 cls.notebook_dir.cleanup()
84 cls.notebook_dir.cleanup()
85
85
86 @classmethod
86 @classmethod
87 def base_url(cls):
87 def base_url(cls):
88 return 'http://localhost:%i/' % cls.port
88 return 'http://localhost:%i/' % cls.port
89
89
90
90
91 @contextmanager
91 @contextmanager
92 def assert_http_error(status, msg=None):
92 def assert_http_error(status, msg=None):
93 try:
93 try:
94 yield
94 yield
95 except requests.HTTPError as e:
95 except requests.HTTPError as e:
96 real_status = e.response.status_code
96 real_status = e.response.status_code
97 assert real_status == status, \
97 assert real_status == status, \
98 "Expected status %d, got %d" % (real_status, status)
98 "Expected status %d, got %d" % (status, real_status)
99 if msg:
99 if msg:
100 assert msg in str(e), e
100 assert msg in str(e), e
101 else:
101 else:
102 assert False, "Expected HTTP error status" No newline at end of file
102 assert False, "Expected HTTP error status"
General Comments 0
You need to be logged in to leave comments. Login now