##// END OF EJS Templates
Code review changes....
Zachary Sailer -
Show More
@@ -1,53 +1,54 b''
1 """Test the kernels service API."""
1 """Test the kernels service API."""
2
2
3
3
4 import os
4 import os
5 import sys
5 import sys
6 import json
6 import json
7
7
8 import requests
8 import requests
9
9
10 from IPython.html.utils import url_path_join
10 from IPython.html.tests.launchnotebook import NotebookTestBase
11 from IPython.html.tests.launchnotebook import NotebookTestBase
11
12
12
13
13 class KernelAPITest(NotebookTestBase):
14 class KernelAPITest(NotebookTestBase):
14 """Test the kernels web service API"""
15 """Test the kernels web service API"""
15
16
16 def base_url(self):
17 def base_url(self):
17 return super(KernelAPITest,self).base_url() + 'api/kernels'
18 return url_path_join(super(KernelAPITest,self).base_url(), 'api/kernels')
18
19
19 def mkkernel(self):
20 def mkkernel(self):
20 r = requests.post(self.base_url())
21 r = requests.post(self.base_url())
21 return r.json()
22 return r.json()
22
23
23 def test_no_kernels(self):
24 def test_no_kernels(self):
24 """Make sure there are no kernels running at the start"""
25 """Make sure there are no kernels running at the start"""
25 url = self.base_url()
26 url = self.base_url()
26 r = requests.get(url)
27 r = requests.get(url)
27 self.assertEqual(r.json(), [])
28 self.assertEqual(r.json(), [])
28
29
29 def test_main_kernel_handler(self):
30 def test_main_kernel_handler(self):
30 # POST request
31 # POST request
31 r = requests.post(self.base_url())
32 r = requests.post(self.base_url())
32 data = r.json()
33 data = r.json()
33 assert isinstance(data, dict)
34 assert isinstance(data, dict)
34
35
35 # GET request
36 # GET request
36 r = requests.get(self.base_url())
37 r = requests.get(self.base_url())
37 assert isinstance(r.json(), list)
38 assert isinstance(r.json(), list)
38 self.assertEqual(r.json()[0], data['id'])
39 self.assertEqual(r.json()[0], data['id'])
39
40
40 def test_kernel_handler(self):
41 def test_kernel_handler(self):
41 # GET kernel with id
42 # GET kernel with id
42 data = self.mkkernel()
43 data = self.mkkernel()
43 url = self.base_url() +'/' + data['id']
44 url = self.base_url() +'/' + data['id']
44 r = requests.get(url)
45 r = requests.get(url)
45 assert isinstance(r.json(), dict)
46 assert isinstance(r.json(), dict)
46 self.assertIn('id', r.json())
47 self.assertIn('id', r.json())
47 self.assertEqual(r.json()['id'], data['id'])
48 self.assertEqual(r.json()['id'], data['id'])
48
49
49 # DELETE kernel with id
50 # DELETE kernel with id
50 r = requests.delete(url)
51 r = requests.delete(url)
51 self.assertEqual(r.status_code, 204)
52 self.assertEqual(r.status_code, 204)
52 r = requests.get(self.base_url())
53 r = requests.get(self.base_url())
53 self.assertEqual(r.json(), []) No newline at end of file
54 self.assertEqual(r.json(), [])
@@ -1,333 +1,361 b''
1 """A notebook manager that uses the local file system for storage.
1 """A notebook manager that uses the local file system for storage.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 * Zach Sailer
6 * Zach Sailer
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import datetime
20 import datetime
21 import io
21 import io
22 import os
22 import os
23 import glob
23 import glob
24 import shutil
24 import shutil
25
25
26 from unicodedata import normalize
26 from unicodedata import normalize
27
27
28 from tornado import web
28 from tornado import web
29
29
30 from .nbmanager import NotebookManager
30 from .nbmanager import NotebookManager
31 from IPython.nbformat import current
31 from IPython.nbformat import current
32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
33 from IPython.utils import tz
33 from IPython.utils import tz
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Classes
36 # Classes
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 class FileNotebookManager(NotebookManager):
39 class FileNotebookManager(NotebookManager):
40
40
41 save_script = Bool(False, config=True,
41 save_script = Bool(False, config=True,
42 help="""Automatically create a Python script when saving the notebook.
42 help="""Automatically create a Python script when saving the notebook.
43
43
44 For easier use of import, %run and %load across notebooks, a
44 For easier use of import, %run and %load across notebooks, a
45 <notebook-name>.py script will be created next to any
45 <notebook-name>.py script will be created next to any
46 <notebook-name>.ipynb on each save. This can also be set with the
46 <notebook-name>.ipynb on each save. This can also be set with the
47 short `--script` flag.
47 short `--script` flag.
48 """
48 """
49 )
49 )
50
50
51 checkpoint_dir = Unicode(config=True,
51 checkpoint_dir = Unicode(config=True,
52 help="""The location in which to keep notebook checkpoints
52 help="""The location in which to keep notebook checkpoints
53
53
54 By default, it is notebook-dir/.ipynb_checkpoints
54 By default, it is notebook-dir/.ipynb_checkpoints
55 """
55 """
56 )
56 )
57 def _checkpoint_dir_default(self):
57 def _checkpoint_dir_default(self):
58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
59
59
60 def _checkpoint_dir_changed(self, name, old, new):
60 def _checkpoint_dir_changed(self, name, old, new):
61 """do a bit of validation of the checkpoint dir"""
61 """do a bit of validation of the checkpoint dir"""
62 if not os.path.isabs(new):
62 if not os.path.isabs(new):
63 # If we receive a non-absolute path, make it absolute.
63 # If we receive a non-absolute path, make it absolute.
64 abs_new = os.path.abspath(new)
64 abs_new = os.path.abspath(new)
65 self.checkpoint_dir = abs_new
65 self.checkpoint_dir = abs_new
66 return
66 return
67 if os.path.exists(new) and not os.path.isdir(new):
67 if os.path.exists(new) and not os.path.isdir(new):
68 raise TraitError("checkpoint dir %r is not a directory" % new)
68 raise TraitError("checkpoint dir %r is not a directory" % new)
69 if not os.path.exists(new):
69 if not os.path.exists(new):
70 self.log.info("Creating checkpoint dir %s", new)
70 self.log.info("Creating checkpoint dir %s", new)
71 try:
71 try:
72 os.mkdir(new)
72 os.mkdir(new)
73 except:
73 except:
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
75
75
76 filename_ext = Unicode(u'.ipynb')
76 filename_ext = Unicode(u'.ipynb')
77
77
78 def get_notebook_names(self, path):
78 def get_notebook_names(self, path):
79 """List all notebook names in the notebook dir."""
79 """List all notebook names in the notebook dir."""
80 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
80 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
81 names = [os.path.basename(name)
81 names = [os.path.basename(name)
82 for name in names]
82 for name in names]
83 return names
83 return names
84
84
85 def increment_filename(self, basename, path='/'):
85 def increment_filename(self, basename, path='/'):
86 """Return a non-used filename of the form basename<int>.
86 """Return a non-used filename of the form basename<int>.
87
87
88 This searches through the filenames (basename0, basename1, ...)
88 This searches through the filenames (basename0, basename1, ...)
89 until is find one that is not already being used. It is used to
89 until is find one that is not already being used. It is used to
90 create Untitled and Copy names that are unique.
90 create Untitled and Copy names that are unique.
91 """
91 """
92 i = 0
92 i = 0
93 while True:
93 while True:
94 name = u'%s%i.ipynb' % (basename,i)
94 name = u'%s%i.ipynb' % (basename,i)
95 os_path = self.get_os_path(name, path)
95 os_path = self.get_os_path(name, path)
96 if not os.path.isfile(os_path):
96 if not os.path.isfile(os_path):
97 break
97 break
98 else:
98 else:
99 i = i+1
99 i = i+1
100 return name
100 return name
101
101
102 def notebook_exists(self, name, path):
102 def notebook_exists(self, name, path):
103 """Returns a True if the notebook exists. Else, returns False.
103 """Returns a True if the notebook exists. Else, returns False.
104
104
105 Parameters
105 Parameters
106 ----------
106 ----------
107 name : string
107 name : string
108 The name of the notebook you are checking.
108 The name of the notebook you are checking.
109 path : string
109 path : string
110 The relative path to the notebook (with '/' as separator)
110 The relative path to the notebook (with '/' as separator)
111
111
112 Returns
112 Returns
113 -------
113 -------
114 bool
114 bool
115 """
115 """
116 path = self.get_os_path(name, path)
116 path = self.get_os_path(name, path)
117 return os.path.isfile(path)
117 return os.path.isfile(path)
118
118
119 def list_notebooks(self, path):
119 def list_notebooks(self, path):
120 """List all notebooks in the notebook dir."""
120 """Returns a list of dictionaries that are the standard model
121 for all notebooks in the relative 'path'.
122
123 Parameters
124 ----------
125 path : str
126 the URL path that describes the relative path for the
127 listed notebooks
128
129 Returns
130 -------
131 notebooks : list of dicts
132 a list of the notebook models without 'content'
133 """
121 notebook_names = self.get_notebook_names(path)
134 notebook_names = self.get_notebook_names(path)
122 notebooks = []
135 notebooks = []
123 for name in notebook_names:
136 for name in notebook_names:
124 model = self.get_notebook_model(name, path, content=False)
137 model = self.get_notebook_model(name, path, content=False)
125 notebooks.append(model)
138 notebooks.append(model)
126 notebooks = sorted(notebooks, key=lambda item: item['name'])
139 notebooks = sorted(notebooks, key=lambda item: item['name'])
127 return notebooks
140 return notebooks
128
141
129 def get_notebook_model(self, name, path='/', content=True):
142 def get_notebook_model(self, name, path='/', content=True):
130 """read a notebook object from a path"""
143 """ Takes a path and name for a notebook and returns it's model
144
145 Parameters
146 ----------
147 name : str
148 the name of the notebook
149 path : str
150 the URL path that describes the relative path for
151 the notebook
152
153 Returns
154 -------
155 model : dict
156 the notebook model. If contents=True, returns the 'contents'
157 dict in the model as well.
158 """
131 os_path = self.get_os_path(name, path)
159 os_path = self.get_os_path(name, path)
132 if not os.path.isfile(os_path):
160 if not os.path.isfile(os_path):
133 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
161 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
134 info = os.stat(os_path)
162 info = os.stat(os_path)
135 last_modified = tz.utcfromtimestamp(info.st_mtime)
163 last_modified = tz.utcfromtimestamp(info.st_mtime)
136 # Create the notebook model.
164 # Create the notebook model.
137 model ={}
165 model ={}
138 model['name'] = name
166 model['name'] = name
139 model['path'] = path
167 model['path'] = path
140 model['last_modified'] = last_modified.ctime()
168 model['last_modified'] = last_modified.ctime()
141 if content is True:
169 if content is True:
142 with open(os_path,'r') as f:
170 with open(os_path,'r') as f:
143 s = f.read()
171 s = f.read()
144 try:
172 try:
145 # v1 and v2 and json in the .ipynb files.
173 # v1 and v2 and json in the .ipynb files.
146 nb = current.reads(s, u'json')
174 nb = current.reads(s, u'json')
147 except ValueError as e:
175 except ValueError as e:
148 raise web.HTTPError(400, u"Unreadable Notebook: %s" % e)
176 raise web.HTTPError(400, u"Unreadable Notebook: %s" % e)
149 model['content'] = nb
177 model['content'] = nb
150 return model
178 return model
151
179
152 def save_notebook_model(self, model, name, path='/'):
180 def save_notebook_model(self, model, name, path='/'):
153 """Save the notebook model and return the model with no content."""
181 """Save the notebook model and return the model with no content."""
154
182
155 if 'content' not in model:
183 if 'content' not in model:
156 raise web.HTTPError(400, u'No notebook JSON data provided')
184 raise web.HTTPError(400, u'No notebook JSON data provided')
157
185
158 new_path = model.get('path', path)
186 new_path = model.get('path', path)
159 new_name = model.get('name', name)
187 new_name = model.get('name', name)
160
188
161 if path != new_path or name != new_name:
189 if path != new_path or name != new_name:
162 self.rename_notebook(name, path, new_name, new_path)
190 self.rename_notebook(name, path, new_name, new_path)
163
191
164 # Save the notebook file
192 # Save the notebook file
165 ospath = self.get_os_path(new_name, new_path)
193 ospath = self.get_os_path(new_name, new_path)
166 nb = model['content']
194 nb = model['content']
167 if 'name' in nb['metadata']:
195 if 'name' in nb['metadata']:
168 nb['metadata']['name'] = u''
196 nb['metadata']['name'] = u''
169 try:
197 try:
170 self.log.debug("Autosaving notebook %s", ospath)
198 self.log.debug("Autosaving notebook %s", ospath)
171 with open(ospath,'w') as f:
199 with open(ospath,'w') as f:
172 current.write(nb, f, u'json')
200 current.write(nb, f, u'json')
173 except Exception as e:
201 except Exception as e:
174 #raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s' % ospath)
202 #raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s' % ospath)
175 raise e
203 raise e
176
204
177 # Save .py script as well
205 # Save .py script as well
178 if self.save_script:
206 if self.save_script:
179 pypath = os.path.splitext(path)[0] + '.py'
207 pypath = os.path.splitext(path)[0] + '.py'
180 self.log.debug("Writing script %s", pypath)
208 self.log.debug("Writing script %s", pypath)
181 try:
209 try:
182 with io.open(pypath, 'w', encoding='utf-8') as f:
210 with io.open(pypath, 'w', encoding='utf-8') as f:
183 current.write(model, f, u'py')
211 current.write(model, f, u'py')
184 except Exception as e:
212 except Exception as e:
185 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % pypath)
213 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % pypath)
186
214
187 model = self.get_notebook_model(name, path, content=False)
215 model = self.get_notebook_model(name, path, content=False)
188 return model
216 return model
189
217
190 def update_notebook_model(self, model, name, path='/'):
218 def update_notebook_model(self, model, name, path='/'):
191 """Update the notebook's path and/or name"""
219 """Update the notebook's path and/or name"""
192 new_name = model.get('name', name)
220 new_name = model.get('name', name)
193 new_path = model.get('path', path)
221 new_path = model.get('path', path)
194 if path != new_path or name != new_name:
222 if path != new_path or name != new_name:
195 self.rename_notebook(name, path, new_name, new_path)
223 self.rename_notebook(name, path, new_name, new_path)
196 model = self.get_notebook_model(new_name, new_path, content=False)
224 model = self.get_notebook_model(new_name, new_path, content=False)
197 return model
225 return model
198
226
199 def delete_notebook_model(self, name, path='/'):
227 def delete_notebook_model(self, name, path='/'):
200 """Delete notebook by name and path."""
228 """Delete notebook by name and path."""
201 nb_path = self.get_os_path(name, path)
229 nb_path = self.get_os_path(name, path)
202 if not os.path.isfile(nb_path):
230 if not os.path.isfile(nb_path):
203 raise web.HTTPError(404, u'Notebook does not exist: %s' % nb_path)
231 raise web.HTTPError(404, u'Notebook does not exist: %s' % nb_path)
204
232
205 # clear checkpoints
233 # clear checkpoints
206 for checkpoint in self.list_checkpoints(name):
234 for checkpoint in self.list_checkpoints(name):
207 checkpoint_id = checkpoint['checkpoint_id']
235 checkpoint_id = checkpoint['checkpoint_id']
208 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
236 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
209 self.log.debug(cp_path)
237 self.log.debug(cp_path)
210 if os.path.isfile(cp_path):
238 if os.path.isfile(cp_path):
211 self.log.debug("Unlinking checkpoint %s", cp_path)
239 self.log.debug("Unlinking checkpoint %s", cp_path)
212 os.unlink(cp_path)
240 os.unlink(cp_path)
213
241
214 self.log.debug("Unlinking notebook %s", nb_path)
242 self.log.debug("Unlinking notebook %s", nb_path)
215 os.unlink(nb_path)
243 os.unlink(nb_path)
216
244
217 def rename_notebook(self, old_name, old_path, new_name, new_path):
245 def rename_notebook(self, old_name, old_path, new_name, new_path):
218 """Rename a notebook."""
246 """Rename a notebook."""
219 if new_name == old_name and new_path == old_path:
247 if new_name == old_name and new_path == old_path:
220 return
248 return
221
249
222 new_full_path = self.get_os_path(new_name, new_path)
250 new_full_path = self.get_os_path(new_name, new_path)
223 old_full_path = self.get_os_path(old_name, old_path)
251 old_full_path = self.get_os_path(old_name, old_path)
224
252
225 # Should we proceed with the move?
253 # Should we proceed with the move?
226 if os.path.isfile(new_full_path):
254 if os.path.isfile(new_full_path):
227 raise web.HTTPError(409, u'Notebook with name already exists: ' % new_full_path)
255 raise web.HTTPError(409, u'Notebook with name already exists: ' % new_full_path)
228 if self.save_script:
256 if self.save_script:
229 old_pypath = os.path.splitext(old_full_path)[0] + '.py'
257 old_pypath = os.path.splitext(old_full_path)[0] + '.py'
230 new_pypath = os.path.splitext(new_full_path)[0] + '.py'
258 new_pypath = os.path.splitext(new_full_path)[0] + '.py'
231 if os.path.isfile(new_pypath):
259 if os.path.isfile(new_pypath):
232 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_pypath)
260 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_pypath)
233
261
234 # Move the notebook file
262 # Move the notebook file
235 try:
263 try:
236 os.rename(old_full_path, new_full_path)
264 os.rename(old_full_path, new_full_path)
237 except:
265 except:
238 raise web.HTTPError(400, u'Unknown error renaming notebook: %s' % old_full_path)
266 raise web.HTTPError(400, u'Unknown error renaming notebook: %s' % old_full_path)
239
267
240 # Move the checkpoints
268 # Move the checkpoints
241 old_checkpoints = self.list_checkpoints(old_name, old_path)
269 old_checkpoints = self.list_checkpoints(old_name, old_path)
242 for cp in old_checkpoints:
270 for cp in old_checkpoints:
243 checkpoint_id = cp['checkpoint_id']
271 checkpoint_id = cp['checkpoint_id']
244 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, path)
272 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, path)
245 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, path)
273 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, path)
246 if os.path.isfile(old_cp_path):
274 if os.path.isfile(old_cp_path):
247 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
275 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
248 os.rename(old_cp_path, new_cp_path)
276 os.rename(old_cp_path, new_cp_path)
249
277
250 # Move the .py script
278 # Move the .py script
251 if self.save_script:
279 if self.save_script:
252 os.rename(old_pypath, new_pypath)
280 os.rename(old_pypath, new_pypath)
253
281
254 # Checkpoint-related utilities
282 # Checkpoint-related utilities
255
283
256 def get_checkpoint_path(self, checkpoint_id, name, path='/'):
284 def get_checkpoint_path(self, checkpoint_id, name, path='/'):
257 """find the path to a checkpoint"""
285 """find the path to a checkpoint"""
258 filename = u"{name}-{checkpoint_id}{ext}".format(
286 filename = u"{name}-{checkpoint_id}{ext}".format(
259 name=name,
287 name=name,
260 checkpoint_id=checkpoint_id,
288 checkpoint_id=checkpoint_id,
261 ext=self.filename_ext,
289 ext=self.filename_ext,
262 )
290 )
263 cp_path = os.path.join(path, self.checkpoint_dir, filename)
291 cp_path = os.path.join(path, self.checkpoint_dir, filename)
264 return cp_path
292 return cp_path
265
293
266 def get_checkpoint_model(self, checkpoint_id, name, path='/'):
294 def get_checkpoint_model(self, checkpoint_id, name, path='/'):
267 """construct the info dict for a given checkpoint"""
295 """construct the info dict for a given checkpoint"""
268 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
296 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
269 stats = os.stat(cp_path)
297 stats = os.stat(cp_path)
270 last_modified = tz.utcfromtimestamp(stats.st_mtime)
298 last_modified = tz.utcfromtimestamp(stats.st_mtime)
271 info = dict(
299 info = dict(
272 checkpoint_id = checkpoint_id,
300 checkpoint_id = checkpoint_id,
273 last_modified = last_modified,
301 last_modified = last_modified,
274 )
302 )
275 return info
303 return info
276
304
277 # public checkpoint API
305 # public checkpoint API
278
306
279 def create_checkpoint(self, name, path='/'):
307 def create_checkpoint(self, name, path='/'):
280 """Create a checkpoint from the current state of a notebook"""
308 """Create a checkpoint from the current state of a notebook"""
281 nb_path = self.get_os_path(name, path)
309 nb_path = self.get_os_path(name, path)
282 # only the one checkpoint ID:
310 # only the one checkpoint ID:
283 checkpoint_id = u"checkpoint"
311 checkpoint_id = u"checkpoint"
284 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
312 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
285 self.log.debug("creating checkpoint for notebook %s", name)
313 self.log.debug("creating checkpoint for notebook %s", name)
286 if not os.path.exists(self.checkpoint_dir):
314 if not os.path.exists(self.checkpoint_dir):
287 os.mkdir(self.checkpoint_dir)
315 os.mkdir(self.checkpoint_dir)
288 shutil.copy2(nb_path, cp_path)
316 shutil.copy2(nb_path, cp_path)
289
317
290 # return the checkpoint info
318 # return the checkpoint info
291 return self.get_checkpoint_model(checkpoint_id, name, path)
319 return self.get_checkpoint_model(checkpoint_id, name, path)
292
320
293 def list_checkpoints(self, name, path='/'):
321 def list_checkpoints(self, name, path='/'):
294 """list the checkpoints for a given notebook
322 """list the checkpoints for a given notebook
295
323
296 This notebook manager currently only supports one checkpoint per notebook.
324 This notebook manager currently only supports one checkpoint per notebook.
297 """
325 """
298 checkpoint_id = "checkpoint"
326 checkpoint_id = "checkpoint"
299 path = self.get_checkpoint_path(checkpoint_id, name, path)
327 path = self.get_checkpoint_path(checkpoint_id, name, path)
300 if not os.path.exists(path):
328 if not os.path.exists(path):
301 return []
329 return []
302 else:
330 else:
303 return [self.get_checkpoint_model(checkpoint_id, name, path)]
331 return [self.get_checkpoint_model(checkpoint_id, name, path)]
304
332
305
333
306 def restore_checkpoint(self, checkpoint_id, name, path='/'):
334 def restore_checkpoint(self, checkpoint_id, name, path='/'):
307 """restore a notebook to a checkpointed state"""
335 """restore a notebook to a checkpointed state"""
308 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
336 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
309 nb_path = self.get_os_path(name, path)
337 nb_path = self.get_os_path(name, path)
310 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
338 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
311 if not os.path.isfile(cp_path):
339 if not os.path.isfile(cp_path):
312 self.log.debug("checkpoint file does not exist: %s", cp_path)
340 self.log.debug("checkpoint file does not exist: %s", cp_path)
313 raise web.HTTPError(404,
341 raise web.HTTPError(404,
314 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
342 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
315 )
343 )
316 # ensure notebook is readable (never restore from an unreadable notebook)
344 # ensure notebook is readable (never restore from an unreadable notebook)
317 with file(cp_path, 'r') as f:
345 with file(cp_path, 'r') as f:
318 nb = current.read(f, u'json')
346 nb = current.read(f, u'json')
319 shutil.copy2(cp_path, nb_path)
347 shutil.copy2(cp_path, nb_path)
320 self.log.debug("copying %s -> %s", cp_path, nb_path)
348 self.log.debug("copying %s -> %s", cp_path, nb_path)
321
349
322 def delete_checkpoint(self, checkpoint_id, name, path='/'):
350 def delete_checkpoint(self, checkpoint_id, name, path='/'):
323 """delete a notebook's checkpoint"""
351 """delete a notebook's checkpoint"""
324 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
352 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
325 if not os.path.isfile(cp_path):
353 if not os.path.isfile(cp_path):
326 raise web.HTTPError(404,
354 raise web.HTTPError(404,
327 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
355 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
328 )
356 )
329 self.log.debug("unlinking %s", cp_path)
357 self.log.debug("unlinking %s", cp_path)
330 os.unlink(cp_path)
358 os.unlink(cp_path)
331
359
332 def info_string(self):
360 def info_string(self):
333 return "Serving notebooks from local directory: %s" % self.notebook_dir
361 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,112 +1,113 b''
1 """Test the notebooks webservice API."""
1 """Test the notebooks webservice API."""
2
2
3
3
4 import os
4 import os
5 import sys
5 import sys
6 import json
6 import json
7 from zmq.utils import jsonapi
7 from zmq.utils import jsonapi
8
8
9 import requests
9 import requests
10
10
11 from IPython.html.utils import url_path_join
11 from IPython.html.tests.launchnotebook import NotebookTestBase
12 from IPython.html.tests.launchnotebook import NotebookTestBase
12
13
13 class APITest(NotebookTestBase):
14 class APITest(NotebookTestBase):
14 """Test the kernels web service API"""
15 """Test the kernels web service API"""
15
16
16 def notebook_url(self):
17 def notebook_url(self):
17 return super(APITest,self).base_url() + 'api/notebooks'
18 return url_path_join(super(APITest,self).base_url(), 'api/notebooks')
18
19
19 def mknb(self, name='', path='/'):
20 def mknb(self, name='', path='/'):
20 url = self.notebook_url() + path
21 url = self.notebook_url() + path
21 return url, requests.post(url)
22 return url, requests.post(url)
22
23
23 def delnb(self, name, path='/'):
24 def delnb(self, name, path='/'):
24 url = self.notebook_url() + path + name
25 url = self.notebook_url() + path + name
25 r = requests.delete(url)
26 r = requests.delete(url)
26 return r.status_code
27 return r.status_code
27
28
28 def test_notebook_handler(self):
29 def test_notebook_handler(self):
29 # POST a notebook and test the dict thats returned.
30 # POST a notebook and test the dict thats returned.
30 #url, nb = self.mknb()
31 #url, nb = self.mknb()
31 url = self.notebook_url()
32 url = self.notebook_url()
32 nb = requests.post(url+'/')
33 nb = requests.post(url+'/')
33 print nb.text
34 print nb.text
34 data = nb.json()
35 data = nb.json()
35 assert isinstance(data, dict)
36 assert isinstance(data, dict)
36 self.assertIn('name', data)
37 self.assertIn('name', data)
37 self.assertIn('path', data)
38 self.assertIn('path', data)
38 self.assertEqual(data['name'], u'Untitled0.ipynb')
39 self.assertEqual(data['name'], u'Untitled0.ipynb')
39 self.assertEqual(data['path'], u'/')
40 self.assertEqual(data['path'], u'/')
40
41
41 # GET list of notebooks in directory.
42 # GET list of notebooks in directory.
42 r = requests.get(url)
43 r = requests.get(url)
43 assert isinstance(r.json(), list)
44 assert isinstance(r.json(), list)
44 assert isinstance(r.json()[0], dict)
45 assert isinstance(r.json()[0], dict)
45
46
46 self.delnb('Untitled0.ipynb')
47 self.delnb('Untitled0.ipynb')
47
48
48 # GET with a notebook name.
49 # GET with a notebook name.
49 url, nb = self.mknb()
50 url, nb = self.mknb()
50 data = nb.json()
51 data = nb.json()
51 url = self.notebook_url() + '/Untitled0.ipynb'
52 url = self.notebook_url() + '/Untitled0.ipynb'
52 r = requests.get(url)
53 r = requests.get(url)
53 assert isinstance(data, dict)
54 assert isinstance(data, dict)
54
55
55 # PATCH (rename) request.
56 # PATCH (rename) request.
56 new_name = {'name':'test.ipynb'}
57 new_name = {'name':'test.ipynb'}
57 r = requests.patch(url, data=jsonapi.dumps(new_name))
58 r = requests.patch(url, data=jsonapi.dumps(new_name))
58 data = r.json()
59 data = r.json()
59 assert isinstance(data, dict)
60 assert isinstance(data, dict)
60
61
61 # make sure the patch worked.
62 # make sure the patch worked.
62 new_url = self.notebook_url() + '/test.ipynb'
63 new_url = self.notebook_url() + '/test.ipynb'
63 r = requests.get(new_url)
64 r = requests.get(new_url)
64 assert isinstance(r.json(), dict)
65 assert isinstance(r.json(), dict)
65
66
66 # GET bad (old) notebook name.
67 # GET bad (old) notebook name.
67 r = requests.get(url)
68 r = requests.get(url)
68 self.assertEqual(r.status_code, 404)
69 self.assertEqual(r.status_code, 404)
69
70
70 # POST notebooks to folders one and two levels down.
71 # POST notebooks to folders one and two levels down.
71 os.makedirs(os.path.join(self.notebook_dir.name, 'foo'))
72 os.makedirs(os.path.join(self.notebook_dir.name, 'foo'))
72 os.makedirs(os.path.join(self.notebook_dir.name, 'foo','bar'))
73 os.makedirs(os.path.join(self.notebook_dir.name, 'foo','bar'))
73 assert os.path.isdir(os.path.join(self.notebook_dir.name, 'foo'))
74 assert os.path.isdir(os.path.join(self.notebook_dir.name, 'foo'))
74 url, nb = self.mknb(path='/foo/')
75 url, nb = self.mknb(path='/foo/')
75 url2, nb2 = self.mknb(path='/foo/bar/')
76 url2, nb2 = self.mknb(path='/foo/bar/')
76 data = nb.json()
77 data = nb.json()
77 data2 = nb2.json()
78 data2 = nb2.json()
78 assert isinstance(data, dict)
79 assert isinstance(data, dict)
79 assert isinstance(data2, dict)
80 assert isinstance(data2, dict)
80 self.assertIn('name', data)
81 self.assertIn('name', data)
81 self.assertIn('path', data)
82 self.assertIn('path', data)
82 self.assertEqual(data['name'], u'Untitled0.ipynb')
83 self.assertEqual(data['name'], u'Untitled0.ipynb')
83 self.assertEqual(data['path'], u'/foo/')
84 self.assertEqual(data['path'], u'/foo/')
84 self.assertIn('name', data2)
85 self.assertIn('name', data2)
85 self.assertIn('path', data2)
86 self.assertIn('path', data2)
86 self.assertEqual(data2['name'], u'Untitled0.ipynb')
87 self.assertEqual(data2['name'], u'Untitled0.ipynb')
87 self.assertEqual(data2['path'], u'/foo/bar/')
88 self.assertEqual(data2['path'], u'/foo/bar/')
88
89
89 # GET request on notebooks one and two levels down.
90 # GET request on notebooks one and two levels down.
90 r = requests.get(url+'/Untitled0.ipynb')
91 r = requests.get(url+'/Untitled0.ipynb')
91 r2 = requests.get(url2+'/Untitled0.ipynb')
92 r2 = requests.get(url2+'/Untitled0.ipynb')
92 assert isinstance(r.json(), dict)
93 assert isinstance(r.json(), dict)
93 assert isinstance(r2.json(), dict)
94 assert isinstance(r2.json(), dict)
94
95
95 # PATCH notebooks that are one and two levels down.
96 # PATCH notebooks that are one and two levels down.
96 new_name = {'name': 'testfoo.ipynb'}
97 new_name = {'name': 'testfoo.ipynb'}
97 r = requests.patch(url+'/Untitled0.ipynb', data=jsonapi.dumps(new_name))
98 r = requests.patch(url+'/Untitled0.ipynb', data=jsonapi.dumps(new_name))
98 r = requests.get(url+'/testfoo.ipynb')
99 r = requests.get(url+'/testfoo.ipynb')
99 data = r.json()
100 data = r.json()
100 assert isinstance(data, dict)
101 assert isinstance(data, dict)
101 self.assertIn('name', data)
102 self.assertIn('name', data)
102 self.assertEqual(data['name'], 'testfoo.ipynb')
103 self.assertEqual(data['name'], 'testfoo.ipynb')
103 r = requests.get(url+'/Untitled0.ipynb')
104 r = requests.get(url+'/Untitled0.ipynb')
104 self.assertEqual(r.status_code, 404)
105 self.assertEqual(r.status_code, 404)
105
106
106 # DELETE notebooks
107 # DELETE notebooks
107 r0 = self.delnb('test.ipynb')
108 r0 = self.delnb('test.ipynb')
108 r1 = self.delnb('testfoo.ipynb', '/foo/')
109 r1 = self.delnb('testfoo.ipynb', '/foo/')
109 r2 = self.delnb('Untitled0.ipynb', '/foo/bar/')
110 r2 = self.delnb('Untitled0.ipynb', '/foo/bar/')
110 self.assertEqual(r0, 204)
111 self.assertEqual(r0, 204)
111 self.assertEqual(r1, 204)
112 self.assertEqual(r1, 204)
112 self.assertEqual(r2, 204)
113 self.assertEqual(r2, 204)
@@ -1,95 +1,95 b''
1 """Test the sessions web service API."""
1 """Test the sessions web service API."""
2
2
3
3
4 import os
4 import os
5 import sys
5 import sys
6 import json
6 import json
7 from zmq.utils import jsonapi
7 from zmq.utils import jsonapi
8
8
9 import requests
9 import requests
10
10
11 from IPython.html.utils import url_path_join
11 from IPython.html.tests.launchnotebook import NotebookTestBase
12 from IPython.html.tests.launchnotebook import NotebookTestBase
12
13
13
14 class SessionAPITest(NotebookTestBase):
14 class SessionAPITest(NotebookTestBase):
15 """Test the sessions web service API"""
15 """Test the sessions web service API"""
16
16
17 def notebook_url(self):
17 def notebook_url(self):
18 return super(SessionAPITest,self).base_url() + 'api/notebooks'
18 return url_path_join(super(SessionAPITest,self).base_url(), 'api/notebooks')
19
19
20 def session_url(self):
20 def session_url(self):
21 return super(SessionAPITest,self).base_url() + 'api/sessions'
21 return super(SessionAPITest,self).base_url() + 'api/sessions'
22
22
23 def mknb(self, name='', path='/'):
23 def mknb(self, name='', path='/'):
24 url = self.notebook_url() + path
24 url = self.notebook_url() + path
25 return url, requests.post(url)
25 return url, requests.post(url)
26
26
27 def delnb(self, name, path='/'):
27 def delnb(self, name, path='/'):
28 url = self.notebook_url() + path + name
28 url = self.notebook_url() + path + name
29 r = requests.delete(url)
29 r = requests.delete(url)
30 return r.status_code
30 return r.status_code
31
31
32 def test_no_sessions(self):
32 def test_no_sessions(self):
33 """Make sure there are no sessions running at the start"""
33 """Make sure there are no sessions running at the start"""
34 url = self.session_url()
34 url = self.session_url()
35 r = requests.get(url)
35 r = requests.get(url)
36 self.assertEqual(r.json(), [])
36 self.assertEqual(r.json(), [])
37
37
38 def test_session_root_handler(self):
38 def test_session_root_handler(self):
39 # POST a session
39 # POST a session
40 url, nb = self.mknb()
40 url, nb = self.mknb()
41 notebook = nb.json()
41 notebook = nb.json()
42 param = {'notebook_path': notebook['path'] + notebook['name']}
42 param = {'notebook_path': notebook['path'] + notebook['name']}
43 r = requests.post(self.session_url(), params=param)
43 r = requests.post(self.session_url(), params=param)
44 data = r.json()
44 data = r.json()
45 assert isinstance(data, dict)
45 assert isinstance(data, dict)
46 self.assertIn('name', data)
46 self.assertIn('name', data)
47 self.assertEqual(data['name'], notebook['name'])
47 self.assertEqual(data['name'], notebook['name'])
48
48
49 # GET sessions
49 # GET sessions
50 r = requests.get(self.session_url())
50 r = requests.get(self.session_url())
51 assert isinstance(r.json(), list)
51 assert isinstance(r.json(), list)
52 assert isinstance(r.json()[0], dict)
52 assert isinstance(r.json()[0], dict)
53 self.assertEqual(r.json()[0]['id'], data['id'])
53 self.assertEqual(r.json()[0]['id'], data['id'])
54
54
55 # Clean up
55 # Clean up
56 self.delnb('Untitled0.ipynb')
56 self.delnb('Untitled0.ipynb')
57 sess_url = self.session_url() +'/'+data['id']
57 sess_url = self.session_url() +'/'+data['id']
58 r = requests.delete(sess_url)
58 r = requests.delete(sess_url)
59 self.assertEqual(r.status_code, 204)
59 self.assertEqual(r.status_code, 204)
60
60
61 def test_session_handler(self):
61 def test_session_handler(self):
62 # Create a session
62 # Create a session
63 url, nb = self.mknb()
63 url, nb = self.mknb()
64 notebook = nb.json()
64 notebook = nb.json()
65 param = {'notebook_path': notebook['path'] + notebook['name']}
65 param = {'notebook_path': notebook['path'] + notebook['name']}
66 r = requests.post(self.session_url(), params=param)
66 r = requests.post(self.session_url(), params=param)
67 session = r.json()
67 session = r.json()
68
68
69 # GET a session
69 # GET a session
70 sess_url = self.session_url() + '/' + session['id']
70 sess_url = self.session_url() + '/' + session['id']
71 r = requests.get(sess_url)
71 r = requests.get(sess_url)
72 assert isinstance(r.json(), dict)
72 assert isinstance(r.json(), dict)
73 self.assertEqual(r.json(), session)
73 self.assertEqual(r.json(), session)
74
74
75 # PATCH a session
75 # PATCH a session
76 data = {'notebook_path': 'test.ipynb'}
76 data = {'notebook_path': 'test.ipynb'}
77 r = requests.patch(sess_url, data=jsonapi.dumps(data))
77 r = requests.patch(sess_url, data=jsonapi.dumps(data))
78 # Patching the notebook webservice too (just for consistency)
78 # Patching the notebook webservice too (just for consistency)
79 requests.patch(self.notebook_url() + '/Untitled0.ipynb',
79 requests.patch(self.notebook_url() + '/Untitled0.ipynb',
80 data=jsonapi.dumps({'name':'test.ipynb'}))
80 data=jsonapi.dumps({'name':'test.ipynb'}))
81 assert isinstance(r.json(), dict)
81 assert isinstance(r.json(), dict)
82 self.assertIn('name', r.json())
82 self.assertIn('name', r.json())
83 self.assertIn('id', r.json())
83 self.assertIn('id', r.json())
84 self.assertEqual(r.json()['name'], 'test.ipynb')
84 self.assertEqual(r.json()['name'], 'test.ipynb')
85 self.assertEqual(r.json()['id'], session['id'])
85 self.assertEqual(r.json()['id'], session['id'])
86
86
87 # DELETE a session
87 # DELETE a session
88 r = requests.delete(sess_url)
88 r = requests.delete(sess_url)
89 self.assertEqual(r.status_code, 204)
89 self.assertEqual(r.status_code, 204)
90 r = requests.get(self.session_url())
90 r = requests.get(self.session_url())
91 self.assertEqual(r.json(), [])
91 self.assertEqual(r.json(), [])
92
92
93 # Clean up
93 # Clean up
94 r = self.delnb('test.ipynb')
94 r = self.delnb('test.ipynb')
95 self.assertEqual(r, 204) No newline at end of file
95 self.assertEqual(r, 204)
General Comments 0
You need to be logged in to leave comments. Login now