##// END OF EJS Templates
add tests for session api
Zachary Sailer -
Show More
@@ -0,0 +1,95 b''
1 """Test the sessions web service API."""
2
3
4 import os
5 import sys
6 import json
7 from zmq.utils import jsonapi
8
9 import requests
10
11 from IPython.html.tests.launchnotebook import NotebookTestBase
12
13
14 class SessionAPITest(NotebookTestBase):
15 """Test the sessions web service API"""
16
17 def notebook_url(self):
18 return super(SessionAPITest,self).base_url() + 'api/notebooks'
19
20 def session_url(self):
21 return super(SessionAPITest,self).base_url() + 'api/sessions'
22
23 def mknb(self, name='', path='/'):
24 url = self.notebook_url() + path
25 return url, requests.post(url)
26
27 def delnb(self, name, path='/'):
28 url = self.notebook_url() + path + name
29 r = requests.delete(url)
30 return r.status_code
31
32 def test_no_sessions(self):
33 """Make sure there are no sessions running at the start"""
34 url = self.session_url()
35 r = requests.get(url)
36 self.assertEqual(r.json(), [])
37
38 def test_session_root_handler(self):
39 # POST a session
40 url, nb = self.mknb()
41 notebook = nb.json()
42 param = {'notebook_path': notebook['path'] + notebook['name']}
43 r = requests.post(self.session_url(), params=param)
44 data = r.json()
45 assert isinstance(data, dict)
46 assert data.has_key('name')
47 self.assertEqual(data['name'], notebook['name'])
48
49 # GET sessions
50 r = requests.get(self.session_url())
51 assert isinstance(r.json(), list)
52 assert isinstance(r.json()[0], dict)
53 self.assertEqual(r.json()[0]['id'], data['id'])
54
55 # Clean up
56 self.delnb('Untitled0.ipynb')
57 sess_url = self.session_url() +'/'+data['id']
58 r = requests.delete(sess_url)
59 self.assertEqual(r.status_code, 204)
60
61 def test_session_handler(self):
62 # Create a session
63 url, nb = self.mknb()
64 notebook = nb.json()
65 param = {'notebook_path': notebook['path'] + notebook['name']}
66 r = requests.post(self.session_url(), params=param)
67 session = r.json()
68
69 # GET a session
70 sess_url = self.session_url() + '/' + session['id']
71 r = requests.get(sess_url)
72 assert isinstance(r.json(), dict)
73 self.assertEqual(r.json(), session)
74
75 # PATCH a session
76 data = {'notebook_path': 'test.ipynb'}
77 r = requests.patch(sess_url, data=jsonapi.dumps(data))
78 # Patching the notebook webservice too (just for consistency)
79 requests.patch(self.notebook_url() + '/Untitled0.ipynb',
80 data=jsonapi.dumps({'name':'test.ipynb'}))
81 assert isinstance(r.json(), dict)
82 assert r.json().has_key('name')
83 assert r.json().has_key('id')
84 self.assertEqual(r.json()['name'], 'test.ipynb')
85 self.assertEqual(r.json()['id'], session['id'])
86
87 # DELETE a session
88 r = requests.delete(sess_url)
89 self.assertEqual(r.status_code, 204)
90 r = requests.get(self.session_url())
91 assert r.json() == []
92
93 # Clean up
94 r = self.delnb('test.ipynb')
95 assert r == 204 No newline at end of file
@@ -1,187 +1,186 b''
1 1 """Tornado handlers for the notebook.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import logging
20 20 from tornado import web
21 21
22 22 from zmq.utils import jsonapi
23 23
24 24 from IPython.utils.jsonutil import date_default
25 25
26 26 from ...base.handlers import IPythonHandler
27 27 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Kernel handlers
31 31 #-----------------------------------------------------------------------------
32 32
33 33
34 34 class MainKernelHandler(IPythonHandler):
35 35
36 36 @web.authenticated
37 37 def get(self):
38 38 km = self.kernel_manager
39 39 self.finish(jsonapi.dumps(km.list_kernels()))
40 40
41 41 @web.authenticated
42 42 def post(self):
43 43 km = self.kernel_manager
44 nbm = self.notebook_manager
45 kernel_id = km.start_kernel(cwd=nbm.notebook_dir)
44 kernel_id = km.start_kernel()
46 45 model = km.kernel_model(kernel_id, self.ws_url)
47 46 self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
48 47 self.finish(jsonapi.dumps(model))
49 48
50 49
51 50 class KernelHandler(IPythonHandler):
52 51
53 52 SUPPORTED_METHODS = ('DELETE', 'GET')
54 53
55 54 @web.authenticated
56 55 def get(self, kernel_id):
57 56 km = self.kernel_manager
58 57 model = km.kernel_model(kernel_id, self.ws_url)
59 58 self.finish(jsonapi.dumps(model))
60 59
61 60 @web.authenticated
62 61 def delete(self, kernel_id):
63 62 km = self.kernel_manager
64 63 km.shutdown_kernel(kernel_id)
65 64 self.set_status(204)
66 65 self.finish()
67 66
68 67
69 68 class KernelActionHandler(IPythonHandler):
70 69
71 70 @web.authenticated
72 71 def post(self, kernel_id, action):
73 72 km = self.kernel_manager
74 73 if action == 'interrupt':
75 74 km.interrupt_kernel(kernel_id)
76 75 self.set_status(204)
77 76 if action == 'restart':
78 77 km.restart_kernel(kernel_id)
79 78 model = km.kernel_model(kernel_id, self.ws_url)
80 79 self.set_header('Location', '{0}api/kernels/{1}'.format(self.base_kernel_url, kernel_id))
81 80 self.write(jsonapi.dumps(model))
82 81 self.finish()
83 82
84 83
85 84 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
86 85
87 86 def create_stream(self):
88 87 km = self.kernel_manager
89 88 meth = getattr(km, 'connect_%s' % self.channel)
90 89 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
91 90
92 91 def initialize(self, *args, **kwargs):
93 92 self.zmq_stream = None
94 93
95 94 def on_first_message(self, msg):
96 95 try:
97 96 super(ZMQChannelHandler, self).on_first_message(msg)
98 97 except web.HTTPError:
99 98 self.close()
100 99 return
101 100 try:
102 101 self.create_stream()
103 102 except web.HTTPError:
104 103 # WebSockets don't response to traditional error codes so we
105 104 # close the connection.
106 105 if not self.stream.closed():
107 106 self.stream.close()
108 107 self.close()
109 108 else:
110 109 self.zmq_stream.on_recv(self._on_zmq_reply)
111 110
112 111 def on_message(self, msg):
113 112 msg = jsonapi.loads(msg)
114 113 self.session.send(self.zmq_stream, msg)
115 114
116 115 def on_close(self):
117 116 # This method can be called twice, once by self.kernel_died and once
118 117 # from the WebSocket close event. If the WebSocket connection is
119 118 # closed before the ZMQ streams are setup, they could be None.
120 119 if self.zmq_stream is not None and not self.zmq_stream.closed():
121 120 self.zmq_stream.on_recv(None)
122 121 self.zmq_stream.close()
123 122
124 123
125 124 class IOPubHandler(ZMQChannelHandler):
126 125 channel = 'iopub'
127 126
128 127 def create_stream(self):
129 128 super(IOPubHandler, self).create_stream()
130 129 km = self.kernel_manager
131 130 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
132 131 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
133 132
134 133 def on_close(self):
135 134 km = self.kernel_manager
136 135 if self.kernel_id in km:
137 136 km.remove_restart_callback(
138 137 self.kernel_id, self.on_kernel_restarted,
139 138 )
140 139 km.remove_restart_callback(
141 140 self.kernel_id, self.on_restart_failed, 'dead',
142 141 )
143 142 super(IOPubHandler, self).on_close()
144 143
145 144 def _send_status_message(self, status):
146 145 msg = self.session.msg("status",
147 146 {'execution_state': status}
148 147 )
149 148 self.write_message(jsonapi.dumps(msg, default=date_default))
150 149
151 150 def on_kernel_restarted(self):
152 151 logging.warn("kernel %s restarted", self.kernel_id)
153 152 self._send_status_message('restarting')
154 153
155 154 def on_restart_failed(self):
156 155 logging.error("kernel %s restarted failed!", self.kernel_id)
157 156 self._send_status_message('dead')
158 157
159 158 def on_message(self, msg):
160 159 """IOPub messages make no sense"""
161 160 pass
162 161
163 162
164 163 class ShellHandler(ZMQChannelHandler):
165 164 channel = 'shell'
166 165
167 166
168 167 class StdinHandler(ZMQChannelHandler):
169 168 channel = 'stdin'
170 169
171 170
172 171 #-----------------------------------------------------------------------------
173 172 # URL to handler mappings
174 173 #-----------------------------------------------------------------------------
175 174
176 175
177 176 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
178 177 _kernel_action_regex = r"(?P<action>restart|interrupt)"
179 178
180 179 default_handlers = [
181 180 (r"/api/kernels", MainKernelHandler),
182 181 (r"/api/kernels/%s" % _kernel_id_regex, KernelHandler),
183 182 (r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
184 183 (r"/api/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
185 184 (r"/api/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
186 185 (r"/api/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
187 186 ]
@@ -1,23 +1,36 b''
1 1 """Test the kernels service API."""
2 2
3 3
4 4 import os
5 5 import sys
6 6 import json
7 7
8 8 import requests
9 9
10 10 from IPython.html.tests.launchnotebook import NotebookTestBase
11 11
12 12
13 13 class KernelAPITest(NotebookTestBase):
14 14 """Test the kernels web service API"""
15 15
16 16 def base_url(self):
17 17 return super(KernelAPITest,self).base_url() + 'api/kernels'
18 18
19 19 def test_no_kernels(self):
20 20 """Make sure there are no kernels running at the start"""
21 21 url = self.base_url()
22 22 r = requests.get(url)
23 23 assert r.json() == []
24
25 def test_main_kernel_handler(self):
26 # POST request
27 r = requests.post(self.base_url())
28 data = r.json()
29 assert isinstance(data, dict)
30
31 # GET request
32 r = requests.get(self.base_url())
33 assert isinstance(r.json(), list)
34 self.assertEqual(r.json()[0], data['id'])
35
36 No newline at end of file
@@ -1,347 +1,347 b''
1 1 """A notebook manager that uses the local file system for storage.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import datetime
20 20 import io
21 21 import os
22 22 import glob
23 23 import shutil
24 24
25 25 from unicodedata import normalize
26 26
27 27 from tornado import web
28 28
29 29 from .nbmanager import NotebookManager
30 30 from IPython.nbformat import current
31 31 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
32 32 from IPython.utils import tz
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Classes
36 36 #-----------------------------------------------------------------------------
37 37
38 38 class FileNotebookManager(NotebookManager):
39 39
40 40 save_script = Bool(False, config=True,
41 41 help="""Automatically create a Python script when saving the notebook.
42 42
43 43 For easier use of import, %run and %load across notebooks, a
44 44 <notebook-name>.py script will be created next to any
45 45 <notebook-name>.ipynb on each save. This can also be set with the
46 46 short `--script` flag.
47 47 """
48 48 )
49 49
50 50 checkpoint_dir = Unicode(config=True,
51 51 help="""The location in which to keep notebook checkpoints
52 52
53 53 By default, it is notebook-dir/.ipynb_checkpoints
54 54 """
55 55 )
56 56 def _checkpoint_dir_default(self):
57 57 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
58 58
59 59 def _checkpoint_dir_changed(self, name, old, new):
60 60 """do a bit of validation of the checkpoint dir"""
61 61 if not os.path.isabs(new):
62 62 # If we receive a non-absolute path, make it absolute.
63 63 abs_new = os.path.abspath(new)
64 64 self.checkpoint_dir = abs_new
65 65 return
66 66 if os.path.exists(new) and not os.path.isdir(new):
67 67 raise TraitError("checkpoint dir %r is not a directory" % new)
68 68 if not os.path.exists(new):
69 69 self.log.info("Creating checkpoint dir %s", new)
70 70 try:
71 71 os.mkdir(new)
72 72 except:
73 73 raise TraitError("Couldn't create checkpoint dir %r" % new)
74 74
75 75 filename_ext = Unicode(u'.ipynb')
76 76
77 77
78 78 def get_notebook_names(self, path):
79 79 """List all notebook names in the notebook dir."""
80 80 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
81 81 names = [os.path.basename(name)
82 82 for name in names]
83 83 return names
84 84
85 85 def list_notebooks(self, path):
86 86 """List all notebooks in the notebook dir."""
87 87 notebook_names = self.get_notebook_names(path)
88 88 notebooks = []
89 89 for name in notebook_names:
90 90 model = self.notebook_model(name, path, content=False)
91 91 notebooks.append(model)
92 92 return notebooks
93 93
94 94 def change_notebook(self, data, notebook_name, notebook_path='/'):
95 95 """Changes notebook"""
96 96 changes = data.keys()
97 97 for change in changes:
98 98 full_path = self.get_os_path(notebook_name, notebook_path)
99 99 if change == "name":
100 100 new_path = self.get_os_path(data['name'], notebook_path)
101 try:
101 if not os.path.isfile(new_path):
102 102 os.rename(full_path,
103 103 self.get_os_path(data['name'], notebook_path))
104 104 notebook_name = data['name']
105 except OSError as e:
105 else:
106 106 raise web.HTTPError(409, u'Notebook name already exists.')
107 107 if change == "path":
108 108 new_path = self.get_os_path(data['name'], data['path'])
109 109 stutil.move(full_path, new_path)
110 110 notebook_path = data['path']
111 111 if change == "content":
112 112 self.save_notebook(data, notebook_name, notebook_path)
113 113 model = self.notebook_model(notebook_name, notebook_path)
114 114 return model
115 115
116 116 def notebook_exists(self, name, path):
117 117 """Returns a True if the notebook exists. Else, returns False.
118 118
119 119 Parameters
120 120 ----------
121 121 name : string
122 122 The name of the notebook you are checking.
123 123 path : string
124 124 The relative path to the notebook (with '/' as separator)
125 125
126 126 Returns
127 127 -------
128 128 bool
129 129 """
130 130 path = self.get_os_path(name, path)
131 131 return os.path.isfile(path)
132 132
133 133 def read_notebook_object_from_path(self, path):
134 134 """read a notebook object from a path"""
135 135 info = os.stat(path)
136 136 last_modified = tz.utcfromtimestamp(info.st_mtime)
137 137 with open(path,'r') as f:
138 138 s = f.read()
139 139 try:
140 140 # v1 and v2 and json in the .ipynb files.
141 141 nb = current.reads(s, u'json')
142 142 except ValueError as e:
143 143 msg = u"Unreadable Notebook: %s" % e
144 144 raise web.HTTPError(400, msg, reason=msg)
145 145 return last_modified, nb
146 146
147 147 def read_notebook_object(self, notebook_name, notebook_path='/'):
148 148 """Get the Notebook representation of a notebook by notebook_name."""
149 149 path = self.get_os_path(notebook_name, notebook_path)
150 150 if not os.path.isfile(path):
151 151 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
152 152 last_modified, nb = self.read_notebook_object_from_path(path)
153 153 # Always use the filename as the notebook name.
154 154 # Eventually we will get rid of the notebook name in the metadata
155 155 # but for now, that name is just an empty string. Until the notebooks
156 156 # web service knows about names in URLs we still pass the name
157 157 # back to the web app using the metadata though.
158 158 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
159 159 return last_modified, nb
160 160
161 161 def write_notebook_object(self, nb, notebook_name=None, notebook_path='/', new_name= None):
162 162 """Save an existing notebook object by notebook_name."""
163 163 if new_name == None:
164 164 try:
165 165 new_name = normalize('NFC', nb.metadata.name)
166 166 except AttributeError:
167 167 raise web.HTTPError(400, u'Missing notebook name')
168 168
169 169 new_path = notebook_path
170 170 old_name = notebook_name
171 171 old_checkpoints = self.list_checkpoints(old_name)
172 172
173 173 path = self.get_os_path(new_name, new_path)
174 174
175 175 # Right before we save the notebook, we write an empty string as the
176 176 # notebook name in the metadata. This is to prepare for removing
177 177 # this attribute entirely post 1.0. The web app still uses the metadata
178 178 # name for now.
179 179 nb.metadata.name = u''
180 180
181 181 try:
182 182 self.log.debug("Autosaving notebook %s", path)
183 183 with open(path,'w') as f:
184 184 current.write(nb, f, u'json')
185 185 except Exception as e:
186 186 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s' % e)
187 187
188 188 # save .py script as well
189 189 if self.save_script:
190 190 pypath = os.path.splitext(path)[0] + '.py'
191 191 self.log.debug("Writing script %s", pypath)
192 192 try:
193 193 with io.open(pypath,'w', encoding='utf-8') as f:
194 194 current.write(nb, f, u'py')
195 195 except Exception as e:
196 196 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
197 197
198 198 if old_name != None:
199 199 # remove old files if the name changed
200 200 if old_name != new_name:
201 201 # remove renamed original, if it exists
202 202 old_path = self.get_os_path(old_name, notebook_path)
203 203 if os.path.isfile(old_path):
204 204 self.log.debug("unlinking notebook %s", old_path)
205 205 os.unlink(old_path)
206 206
207 207 # cleanup old script, if it exists
208 208 if self.save_script:
209 209 old_pypath = os.path.splitext(old_path)[0] + '.py'
210 210 if os.path.isfile(old_pypath):
211 211 self.log.debug("unlinking script %s", old_pypath)
212 212 os.unlink(old_pypath)
213 213
214 214 # rename checkpoints to follow file
215 215 for cp in old_checkpoints:
216 216 checkpoint_id = cp['checkpoint_id']
217 217 old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
218 218 new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
219 219 if os.path.isfile(old_cp_path):
220 220 self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
221 221 os.rename(old_cp_path, new_cp_path)
222 222
223 223 return new_name
224 224
225 225 def delete_notebook(self, notebook_name, notebook_path):
226 226 """Delete notebook by notebook_name."""
227 227 nb_path = self.get_os_path(notebook_name, notebook_path)
228 228 if not os.path.isfile(nb_path):
229 229 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_name)
230 230
231 231 # clear checkpoints
232 232 for checkpoint in self.list_checkpoints(notebook_name):
233 233 checkpoint_id = checkpoint['checkpoint_id']
234 234 path = self.get_checkpoint_path(notebook_name, checkpoint_id)
235 235 self.log.debug(path)
236 236 if os.path.isfile(path):
237 237 self.log.debug("unlinking checkpoint %s", path)
238 238 os.unlink(path)
239 239
240 240 self.log.debug("unlinking notebook %s", nb_path)
241 241 os.unlink(nb_path)
242 242
243 243 def increment_filename(self, basename, notebook_path='/'):
244 244 """Return a non-used filename of the form basename<int>.
245 245
246 246 This searches through the filenames (basename0, basename1, ...)
247 247 until is find one that is not already being used. It is used to
248 248 create Untitled and Copy names that are unique.
249 249 """
250 250 i = 0
251 251 while True:
252 252 name = u'%s%i.ipynb' % (basename,i)
253 253 path = self.get_os_path(name, notebook_path)
254 254 if not os.path.isfile(path):
255 255 break
256 256 else:
257 257 i = i+1
258 258 return name
259 259
260 260 # Checkpoint-related utilities
261 261
262 262 def get_checkpoint_path_by_name(self, name, checkpoint_id, notebook_path='/'):
263 263 """Return a full path to a notebook checkpoint, given its name and checkpoint id."""
264 264 filename = u"{name}-{checkpoint_id}{ext}".format(
265 265 name=name,
266 266 checkpoint_id=checkpoint_id,
267 267 ext=self.filename_ext,
268 268 )
269 269 if notebook_path ==None:
270 270 path = os.path.join(self.checkpoint_dir, filename)
271 271 else:
272 272 path = os.path.join(notebook_path, self.checkpoint_dir, filename)
273 273 return path
274 274
275 275 def get_checkpoint_path(self, notebook_name, checkpoint_id, notebook_path='/'):
276 276 """find the path to a checkpoint"""
277 277 name = notebook_name
278 278 return self.get_checkpoint_path_by_name(name, checkpoint_id, notebook_path)
279 279
280 280 def get_checkpoint_info(self, notebook_name, checkpoint_id, notebook_path='/'):
281 281 """construct the info dict for a given checkpoint"""
282 282 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
283 283 stats = os.stat(path)
284 284 last_modified = tz.utcfromtimestamp(stats.st_mtime)
285 285 info = dict(
286 286 checkpoint_id = checkpoint_id,
287 287 last_modified = last_modified,
288 288 )
289 289
290 290 return info
291 291
292 292 # public checkpoint API
293 293
294 294 def create_checkpoint(self, notebook_name, notebook_path='/'):
295 295 """Create a checkpoint from the current state of a notebook"""
296 296 nb_path = self.get_os_path(notebook_name, notebook_path)
297 297 # only the one checkpoint ID:
298 298 checkpoint_id = u"checkpoint"
299 299 cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
300 300 self.log.debug("creating checkpoint for notebook %s", notebook_name)
301 301 if not os.path.exists(self.checkpoint_dir):
302 302 os.mkdir(self.checkpoint_dir)
303 303 shutil.copy2(nb_path, cp_path)
304 304
305 305 # return the checkpoint info
306 306 return self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)
307 307
308 308 def list_checkpoints(self, notebook_name, notebook_path='/'):
309 309 """list the checkpoints for a given notebook
310 310
311 311 This notebook manager currently only supports one checkpoint per notebook.
312 312 """
313 313 checkpoint_id = "checkpoint"
314 314 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
315 315 if not os.path.exists(path):
316 316 return []
317 317 else:
318 318 return [self.get_checkpoint_info(notebook_name, checkpoint_id, notebook_path)]
319 319
320 320
321 321 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path='/'):
322 322 """restore a notebook to a checkpointed state"""
323 323 self.log.info("restoring Notebook %s from checkpoint %s", notebook_name, checkpoint_id)
324 324 nb_path = self.get_os_path(notebook_name, notebook_path)
325 325 cp_path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
326 326 if not os.path.isfile(cp_path):
327 327 self.log.debug("checkpoint file does not exist: %s", cp_path)
328 328 raise web.HTTPError(404,
329 329 u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
330 330 )
331 331 # ensure notebook is readable (never restore from an unreadable notebook)
332 332 last_modified, nb = self.read_notebook_object_from_path(cp_path)
333 333 shutil.copy2(cp_path, nb_path)
334 334 self.log.debug("copying %s -> %s", cp_path, nb_path)
335 335
336 336 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path='/'):
337 337 """delete a notebook's checkpoint"""
338 338 path = self.get_checkpoint_path(notebook_name, checkpoint_id, notebook_path)
339 339 if not os.path.isfile(path):
340 340 raise web.HTTPError(404,
341 341 u'Notebook checkpoint does not exist: %s-%s' % (notebook_name, checkpoint_id)
342 342 )
343 343 self.log.debug("unlinking %s", path)
344 344 os.unlink(path)
345 345
346 346 def info_string(self):
347 347 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,216 +1,217 b''
1 1 """Tornado handlers for the notebooks web service.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from tornado import web
20 20
21 21 from zmq.utils import jsonapi
22 22
23 23 from IPython.utils.jsonutil import date_default
24 24
25 25 from ...base.handlers import IPythonHandler
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Notebook web service handlers
29 29 #-----------------------------------------------------------------------------
30 30
31 31
32 32 class NotebookRootHandler(IPythonHandler):
33 33
34 34 @web.authenticated
35 35 def get(self):
36 36 """get returns a list of notebooks from the location
37 37 where the server was started."""
38 38 nbm = self.notebook_manager
39 39 notebooks = nbm.list_notebooks("/")
40 40 self.finish(jsonapi.dumps(notebooks))
41 41
42 42 @web.authenticated
43 43 def post(self):
44 44 """post creates a notebooks in the directory where the
45 45 server was started"""
46 46 nbm = self.notebook_manager
47 self.log.info(nbm.notebook_dir)
47 48 body = self.request.body.strip()
48 49 format = self.get_argument('format', default='json')
49 50 name = self.get_argument('name', default=None)
50 51 if body:
51 52 fname = nbm.save_new_notebook(body, notebook_path='/', name=name, format=format)
52 53 else:
53 54 fname = nbm.new_notebook(notebook_path='/')
54 55 self.set_header('Location', nbm.notebook_dir + fname)
55 56 model = nbm.notebook_model(fname)
56 57 self.set_header('Location', '{0}api/notebooks/{1}'.format(self.base_project_url, fname))
57 58 self.finish(jsonapi.dumps(model))
58 59
59 60 class NotebookHandler(IPythonHandler):
60 61
61 62 SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH', 'POST','DELETE')
62 63
63 64 @web.authenticated
64 65 def get(self, notebook_path):
65 66 """get checks if a notebook is not named, an returns a list of notebooks
66 67 in the notebook path given. If a name is given, return
67 68 the notebook representation"""
68 69 nbm = self.notebook_manager
69 70 name, path = nbm.named_notebook_path(notebook_path)
70 71
71 72 # Check to see if a notebook name was given
72 73 if name is None:
73 74 # List notebooks in 'notebook_path'
74 75 notebooks = nbm.list_notebooks(path)
75 76 self.finish(jsonapi.dumps(notebooks))
76 77 else:
77 78 # get and return notebook representation
78 79 format = self.get_argument('format', default='json')
79 80 download = self.get_argument('download', default='False')
80 81 model = nbm.notebook_model(name, path)
81 82 last_mod, representation, name = nbm.get_notebook(name, path, format)
82 83 self.set_header('Last-Modified', last_mod)
83 84
84 85 if download == 'True':
85 86 if format == u'json':
86 87 self.set_header('Content-Type', 'application/json')
87 88 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
88 89 self.finish(representation)
89 90 elif format == u'py':
90 91 self.set_header('Content-Type', 'application/x-python')
91 92 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
92 93 self.finish(representation)
93 94 else:
94 95 self.finish(jsonapi.dumps(model))
95 96
96 97 @web.authenticated
97 98 def patch(self, notebook_path):
98 99 """patch is currently used strictly for notebook renaming.
99 100 Changes the notebook name to the name given in data."""
100 101 nbm = self.notebook_manager
101 102 name, path = nbm.named_notebook_path(notebook_path)
102 103 data = jsonapi.loads(self.request.body)
103 104 model = nbm.change_notebook(data, name, path)
104 105 self.finish(jsonapi.dumps(model))
105 106
106 107 @web.authenticated
107 108 def post(self,notebook_path):
108 109 """Create a new notebook in the location given by 'notebook_path'."""
109 110 nbm = self.notebook_manager
110 111 fname, path = nbm.named_notebook_path(notebook_path)
111 112 body = self.request.body.strip()
112 113 format = self.get_argument('format', default='json')
113 114 name = self.get_argument('name', default=None)
114 115 if body:
115 116 fname = nbm.save_new_notebook(body, notebook_path=path, name=name, format=format)
116 117 else:
117 118 fname = nbm.new_notebook(notebook_path=path)
118 119 self.set_header('Location', nbm.notebook_dir + path + fname)
119 120 model = nbm.notebook_model(fname, path)
120 121 self.finish(jsonapi.dumps(model))
121 122
122 123 @web.authenticated
123 124 def put(self, notebook_path):
124 125 """saves the notebook in the location given by 'notebook_path'."""
125 126 nbm = self.notebook_manager
126 127 name, path = nbm.named_notebook_path(notebook_path)
127 128 format = self.get_argument('format', default='json')
128 129 nbm.save_notebook(self.request.body, notebook_path=path, name=name, format=format)
129 130 model = nbm.notebook_model(name, path)
130 131 self.set_status(204)
131 132 self.finish(jsonapi.dumps(model))
132 133
133 134 @web.authenticated
134 135 def delete(self, notebook_path):
135 136 """delete rmoves the notebook in the given notebook path"""
136 137 nbm = self.notebook_manager
137 138 name, path = nbm.named_notebook_path(notebook_path)
138 139 nbm.delete_notebook(name, path)
139 140 self.set_status(204)
140 141 self.finish()
141 142
142 143
143 144 class NotebookCheckpointsHandler(IPythonHandler):
144 145
145 146 SUPPORTED_METHODS = ('GET', 'POST')
146 147
147 148 @web.authenticated
148 149 def get(self, notebook_path):
149 150 """get lists checkpoints for a notebook"""
150 151 nbm = self.notebook_manager
151 152 name, path = nbm.named_notebook_path(notebook_path)
152 153 checkpoints = nbm.list_checkpoints(name, path)
153 154 data = jsonapi.dumps(checkpoints, default=date_default)
154 155 self.finish(data)
155 156
156 157 @web.authenticated
157 158 def post(self, notebook_path):
158 159 """post creates a new checkpoint"""
159 160 nbm = self.notebook_manager
160 161 name, path = nbm.named_notebook_path(notebook_path)
161 162 checkpoint = nbm.create_checkpoint(name, path)
162 163 data = jsonapi.dumps(checkpoint, default=date_default)
163 164 if path == None:
164 165 self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
165 166 self.base_project_url, name, checkpoint['checkpoint_id']
166 167 ))
167 168 else:
168 169 self.set_header('Location', '{0}notebooks/{1}/{2}/checkpoints/{3}'.format(
169 170 self.base_project_url, path, name, checkpoint['checkpoint_id']
170 171 ))
171 172 self.finish(data)
172 173
173 174
174 175 class ModifyNotebookCheckpointsHandler(IPythonHandler):
175 176
176 177 SUPPORTED_METHODS = ('POST', 'DELETE')
177 178
178 179 @web.authenticated
179 180 def post(self, notebook_path, checkpoint_id):
180 181 """post restores a notebook from a checkpoint"""
181 182 nbm = self.notebook_manager
182 183 name, path = nbm.named_notebook_path(notebook_path)
183 184 nbm.restore_checkpoint(name, checkpoint_id, path)
184 185 self.set_status(204)
185 186 self.finish()
186 187
187 188 @web.authenticated
188 189 def delete(self, notebook_path, checkpoint_id):
189 190 """delete clears a checkpoint for a given notebook"""
190 191 nbm = self.notebook_manager
191 192 name, path = nbm.named_notebook_path(notebook_path)
192 193 nbm.delete_checkpoint(name, checkpoint_id, path)
193 194 self.set_status(204)
194 195 self.finish()
195 196
196 197 #-----------------------------------------------------------------------------
197 198 # URL to handler mappings
198 199 #-----------------------------------------------------------------------------
199 200
200 201
201 202 _notebook_path_regex = r"(?P<notebook_path>.+)"
202 203 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
203 204
204 205 default_handlers = [
205 206 (r"api/notebooks/%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
206 207 (r"api/notebooks/%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
207 208 ModifyNotebookCheckpointsHandler),
208 209 (r"api/notebooks/%s/" % _notebook_path_regex, NotebookHandler),
209 210 (r"api/notebooks/%s" % _notebook_path_regex, NotebookHandler),
210 211 (r"api/notebooks/", NotebookRootHandler),
211 212 (r"api/notebooks", NotebookRootHandler),
212 213 ]
213 214
214 215
215 216
216 217
@@ -1,118 +1,116 b''
1 """Test the all of the services API."""
1 """Test the notebooks webservice API."""
2 2
3 3
4 4 import os
5 5 import sys
6 6 import json
7 import urllib
8 7 from zmq.utils import jsonapi
9 8
10 9 import requests
11 10
12 11 from IPython.html.tests.launchnotebook import NotebookTestBase
13 12
14 13 class APITest(NotebookTestBase):
15 14 """Test the kernels web service API"""
16 15
17 def base_url(self):
18 return super(APITest,self).base_url()
19
20 def notebooks_url(self):
21 return self.base_url() + 'api/notebooks'
16 def notebook_url(self):
17 return super(APITest,self).base_url() + 'api/notebooks'
22 18
23 19 def mknb(self, name='', path='/'):
24 url = self.notebooks_url() + path
20 url = self.notebook_url() + path
25 21 return url, requests.post(url)
26 22
27 23 def delnb(self, name, path='/'):
28 url = self.notebooks_url() + path + name
24 url = self.notebook_url() + path + name
29 25 r = requests.delete(url)
30 26 return r.status_code
31 27
32 def test_no_notebooks(self):
33 url = self.notebooks_url()
34 r = requests.get(url)
35 self.assertEqual(r.json(), [])
36
37 28 def test_notebook_root_handler(self):
38 29 # POST a notebook and test the dict thats returned.
39 url, nb = self.mknb()
30 #url, nb = self.mknb()
31 url = self.notebook_url()
32 nb = requests.post(url)
40 33 data = nb.json()
41 34 assert isinstance(data, dict)
42 35 assert data.has_key("name")
43 36 assert data.has_key("path")
44 37 self.assertEqual(data['name'], u'Untitled0.ipynb')
45 38 self.assertEqual(data['path'], u'/')
46 39
47 40 # GET list of notebooks in directory.
48 41 r = requests.get(url)
49 42 assert isinstance(r.json(), list)
50 43 assert isinstance(r.json()[0], dict)
51 44
45 self.delnb('Untitled0.ipynb')
46
52 47 def test_notebook_handler(self):
53 48 # GET with a notebook name.
54 49 url, nb = self.mknb()
55 50 data = nb.json()
56 url = self.notebooks_url() + '/Untitled0.ipynb'
51 url = self.notebook_url() + '/Untitled0.ipynb'
57 52 r = requests.get(url)
58 53 assert isinstance(data, dict)
59 54 self.assertEqual(r.json(), data)
60 55
61 56 # PATCH (rename) request.
62 57 new_name = {'name':'test.ipynb'}
63 58 r = requests.patch(url, data=jsonapi.dumps(new_name))
64 59 data = r.json()
65 60 assert isinstance(data, dict)
66 61
67 62 # make sure the patch worked.
68 new_url = self.notebooks_url() + '/test.ipynb'
63 new_url = self.notebook_url() + '/test.ipynb'
69 64 r = requests.get(new_url)
70 65 assert isinstance(r.json(), dict)
71 66 self.assertEqual(r.json(), data)
72 67
73 68 # GET bad (old) notebook name.
74 69 r = requests.get(url)
75 70 self.assertEqual(r.status_code, 404)
76 71
77 72 # POST notebooks to folders one and two levels down.
78 73 os.makedirs(os.path.join(self.notebook_dir.name, 'foo'))
79 74 os.makedirs(os.path.join(self.notebook_dir.name, 'foo','bar'))
75 assert os.path.isdir(os.path.join(self.notebook_dir.name, 'foo'))
80 76 url, nb = self.mknb(path='/foo/')
81 77 url2, nb2 = self.mknb(path='/foo/bar/')
82 78 data = nb.json()
83 79 data2 = nb2.json()
84 80 assert isinstance(data, dict)
85 81 assert isinstance(data2, dict)
86 82 assert data.has_key("name")
87 83 assert data.has_key("path")
88 84 self.assertEqual(data['name'], u'Untitled0.ipynb')
89 85 self.assertEqual(data['path'], u'/foo/')
90 86 assert data2.has_key("name")
91 87 assert data2.has_key("path")
92 88 self.assertEqual(data2['name'], u'Untitled0.ipynb')
93 89 self.assertEqual(data2['path'], u'/foo/bar/')
94 90
95 91 # GET request on notebooks one and two levels down.
96 92 r = requests.get(url+'Untitled0.ipynb')
97 93 r2 = requests.get(url2+'Untitled0.ipynb')
98 94 assert isinstance(r.json(), dict)
99 95 self.assertEqual(r.json(), data)
100 96 assert isinstance(r2.json(), dict)
101 97 self.assertEqual(r2.json(), data2)
102 98
103 99 # PATCH notebooks that are one and two levels down.
104 100 new_name = {'name': 'testfoo.ipynb'}
105 101 r = requests.patch(url+'Untitled0.ipynb', data=jsonapi.dumps(new_name))
106 102 r = requests.get(url+'testfoo.ipynb')
107 103 data = r.json()
108 104 assert isinstance(data, dict)
109 105 assert data.has_key('name')
110 106 self.assertEqual(data['name'], 'testfoo.ipynb')
111 107 r = requests.get(url+'Untitled0.ipynb')
112 108 self.assertEqual(r.status_code, 404)
113 109
114 110 # DELETE notebooks
115 r = self.delnb('testfoo.ipynb', '/foo/')
111 r0 = self.delnb('test.ipynb')
112 r1 = self.delnb('testfoo.ipynb', '/foo/')
116 113 r2 = self.delnb('Untitled0.ipynb', '/foo/bar/')
117 self.assertEqual(r, 204)
114 self.assertEqual(r0, 204)
115 self.assertEqual(r1, 204)
118 116 self.assertEqual(r2, 204)
@@ -1,112 +1,114 b''
1 1 """Tornado handlers for the sessions web service.
2 2
3 3 Authors:
4 4
5 5 * Zach Sailer
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from tornado import web
20 20
21 21 from zmq.utils import jsonapi
22 22
23 23 from IPython.utils.jsonutil import date_default
24 24 from ...base.handlers import IPythonHandler
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Session web service handlers
28 28 #-----------------------------------------------------------------------------
29 29
30 30
31 31 class SessionRootHandler(IPythonHandler):
32 32
33 33 @web.authenticated
34 34 def get(self):
35 35 # Return a list of running sessions
36 36 sm = self.session_manager
37 37 nbm = self.notebook_manager
38 38 km = self.kernel_manager
39 39 sessions = sm.list_sessions()
40 40 self.finish(jsonapi.dumps(sessions))
41 41
42 42 @web.authenticated
43 43 def post(self):
44 44 # Creates a new session
45 45 #(unless a session already exists for the named nb)
46 46 sm = self.session_manager
47 47 nbm = self.notebook_manager
48 48 km = self.kernel_manager
49 49 notebook_path = self.get_argument('notebook_path', default=None)
50 50 name, path = nbm.named_notebook_path(notebook_path)
51 51 # Check to see if session exists
52 52 if sm.session_exists(name=name, path=path):
53 53 model = sm.get_session(name=name, path=path)
54 54 kernel_id = model['kernel']['id']
55 55 km.start_kernel(kernel_id, cwd=nbm.notebook_dir)
56 56 else:
57 57 session_id = sm.get_session_id()
58 58 sm.save_session(session_id=session_id, name=name, path=path)
59 59 kernel_id = km.start_kernel(cwd=nbm.notebook_dir)
60 60 kernel = km.kernel_model(kernel_id, self.ws_url)
61 61 sm.update_session(session_id, kernel=kernel_id)
62 62 model = sm.get_session(id=session_id)
63 63 self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
64 64 self.finish(jsonapi.dumps(model))
65 65
66 66 class SessionHandler(IPythonHandler):
67 67
68 68 SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
69 69
70 70 @web.authenticated
71 71 def get(self, session_id):
72 72 # Returns the JSON model for a single session
73 73 sm = self.session_manager
74 74 model = sm.get_session(id=session_id)
75 75 self.finish(jsonapi.dumps(model))
76 76
77 77 @web.authenticated
78 78 def patch(self, session_id):
79 79 # Currently, this handler is strictly for renaming notebooks
80 80 sm = self.session_manager
81 81 nbm = self.notebook_manager
82 82 km = self.kernel_manager
83 notebook_path = self.request.body
84 name, path = nbm.named_notebook_path(notebook_path)
83 data = jsonapi.loads(self.request.body)
84 name, path = nbm.named_notebook_path(data['notebook_path'])
85 85 sm.update_session(session_id, name=name)
86 86 model = sm.get_session(id=session_id)
87 87 self.finish(jsonapi.dumps(model))
88 88
89 89 @web.authenticated
90 90 def delete(self, session_id):
91 91 # Deletes the session with given session_id
92 92 sm = self.session_manager
93 93 nbm = self.notebook_manager
94 94 km = self.kernel_manager
95 95 session = sm.get_session(id=session_id)
96 96 sm.delete_session(session_id)
97 97 km.shutdown_kernel(session['kernel']['id'])
98 98 self.set_status(204)
99 99 self.finish()
100 100
101 101
102 102 #-----------------------------------------------------------------------------
103 103 # URL to handler mappings
104 104 #-----------------------------------------------------------------------------
105 105
106 106 _session_id_regex = r"(?P<session_id>\w+-\w+-\w+-\w+-\w+)"
107 107
108 108 default_handlers = [
109 (r"api/sessions/%s/" % _session_id_regex, SessionHandler),
109 110 (r"api/sessions/%s" % _session_id_regex, SessionHandler),
111 (r"api/sessions/", SessionRootHandler),
110 112 (r"api/sessions", SessionRootHandler)
111 113 ]
112 114
@@ -1,95 +1,96 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var Session = function(notebook_path, Notebook){
15 15 this.kernel = null;
16 16 this.kernel_id = null;
17 17 this.session_id = null;
18 18 this.notebook_path = notebook_path;
19 19 this.notebook = Notebook;
20 20 this._baseProjectUrl = Notebook.baseProjectUrl()
21 21 };
22 22
23 23 Session.prototype.start = function(){
24 24 var that = this
25 25 var qs = $.param({notebook_path:this.notebook_path});
26 26 var url = '/api/sessions' + '?' + qs;
27 27 $.post(url,
28 28 $.proxy(this.start_kernel, that),
29 29 'json'
30 30 );
31 31 };
32 32
33 33 Session.prototype.notebook_rename = function (notebook_path) {
34 34 this.notebook_path = notebook_path;
35 name = {'notebook_path': notebook_path}
35 36 var settings = {
36 37 processData : false,
37 38 cache : false,
38 39 type : "PATCH",
39 data: notebook_path,
40 data: JSON.stringify(name),
40 41 dataType : "json",
41 42 };
42 43 var url = this._baseProjectUrl + 'api/sessions/' + this.session_id;
43 44 $.ajax(url, settings);
44 45 }
45 46
46 47
47 48 Session.prototype.delete_session = function() {
48 49 var settings = {
49 50 processData : false,
50 51 cache : false,
51 52 type : "DELETE",
52 53 dataType : "json",
53 54 };
54 55 var url = this._baseProjectUrl + 'api/sessions/' + this.session_id;
55 56 $.ajax(url, settings);
56 57 };
57 58
58 59 // Kernel related things
59 60 /**
60 61 * Start a new kernel and set it on each code cell.
61 62 *
62 63 * @method start_kernel
63 64 */
64 65 Session.prototype.start_kernel = function (json) {
65 66 this.session_id = json.id;
66 67 this.kernel_content = json.kernel;
67 68 var base_url = $('body').data('baseKernelUrl') + "api/kernels";
68 69 this.kernel = new IPython.Kernel(base_url, this.session_id);
69 70 this.kernel._kernel_started(this.kernel_content);
70 71 };
71 72
72 73 /**
73 74 * Prompt the user to restart the IPython kernel.
74 75 *
75 76 * @method restart_kernel
76 77 */
77 78 Session.prototype.restart_kernel = function () {
78 79 this.kernel.restart();
79 80 };
80 81
81 82 Session.prototype.interrupt_kernel = function() {
82 83 this.kernel.interrupt();
83 84 };
84 85
85 86
86 87 Session.prototype.kill_kernel = function() {
87 88 this.kernel.kill();
88 89 };
89 90
90 91 IPython.Session = Session;
91 92
92 93
93 94 return IPython;
94 95
95 96 }(IPython));
@@ -1,41 +1,41 b''
1 1 """Base class for notebook tests."""
2 2
3 3 import sys
4 4 import time
5 5 from subprocess import Popen, PIPE
6 6 from unittest import TestCase
7 7
8 8 from IPython.utils.tempdir import TemporaryDirectory
9 9
10 10
11 11 class NotebookTestBase(TestCase):
12 12 """A base class for tests that need a running notebook.
13 13
14 14 This creates an empty profile in a temp ipython_dir
15 15 and then starts the notebook server with a separate temp notebook_dir.
16 16 """
17 17
18 port = 12342
18 port = 1234
19 19
20 20 def setUp(self):
21 21 self.ipython_dir = TemporaryDirectory()
22 22 self.notebook_dir = TemporaryDirectory()
23 23 notebook_args = [
24 24 sys.executable, '-c',
25 25 'from IPython.html.notebookapp import launch_new_instance; launch_new_instance()',
26 26 '--port=%d' % self.port,
27 27 '--no-browser',
28 28 '--ipython-dir=%s' % self.ipython_dir.name,
29 29 '--notebook-dir=%s' % self.notebook_dir.name
30 30 ]
31 #self.notebook = Popen(notebook_args)
32 31 self.notebook = Popen(notebook_args, stdout=PIPE, stderr=PIPE)
33 32 time.sleep(3.0)
34 33
35 34 def tearDown(self):
36 35 self.notebook.terminate()
37 36 self.ipython_dir.cleanup()
38 37 self.notebook_dir.cleanup()
38 time.sleep(3.0)
39 39
40 40 def base_url(self):
41 41 return 'http://localhost:%i/' % self.port
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now