##// END OF EJS Templates
Backport PR #5708: create checkpoints dir in notebook subdirectories...
Thomas Kluyver -
Show More
@@ -1,494 +1,482 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 io
20 import io
21 import os
21 import os
22 import glob
22 import glob
23 import shutil
23 import shutil
24
24
25 from tornado import web
25 from tornado import web
26
26
27 from .nbmanager import NotebookManager
27 from .nbmanager import NotebookManager
28 from IPython.nbformat import current
28 from IPython.nbformat import current
29 from IPython.utils.traitlets import Unicode, Bool, TraitError
29 from IPython.utils.traitlets import Unicode, Bool, TraitError
30 from IPython.utils.py3compat import getcwd
30 from IPython.utils.py3compat import getcwd
31 from IPython.utils import tz
31 from IPython.utils import tz
32 from IPython.html.utils import is_hidden, to_os_path
32 from IPython.html.utils import is_hidden, to_os_path
33
33
34 def sort_key(item):
34 def sort_key(item):
35 """Case-insensitive sorting."""
35 """Case-insensitive sorting."""
36 return item['name'].lower()
36 return item['name'].lower()
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Classes
39 # Classes
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 class FileNotebookManager(NotebookManager):
42 class FileNotebookManager(NotebookManager):
43
43
44 save_script = Bool(False, config=True,
44 save_script = Bool(False, config=True,
45 help="""Automatically create a Python script when saving the notebook.
45 help="""Automatically create a Python script when saving the notebook.
46
46
47 For easier use of import, %run and %load across notebooks, a
47 For easier use of import, %run and %load across notebooks, a
48 <notebook-name>.py script will be created next to any
48 <notebook-name>.py script will be created next to any
49 <notebook-name>.ipynb on each save. This can also be set with the
49 <notebook-name>.ipynb on each save. This can also be set with the
50 short `--script` flag.
50 short `--script` flag.
51 """
51 """
52 )
52 )
53 notebook_dir = Unicode(getcwd(), config=True)
53 notebook_dir = Unicode(getcwd(), config=True)
54
54
55 def _notebook_dir_changed(self, name, old, new):
55 def _notebook_dir_changed(self, name, old, new):
56 """Do a bit of validation of the notebook dir."""
56 """Do a bit of validation of the notebook dir."""
57 if not os.path.isabs(new):
57 if not os.path.isabs(new):
58 # If we receive a non-absolute path, make it absolute.
58 # If we receive a non-absolute path, make it absolute.
59 self.notebook_dir = os.path.abspath(new)
59 self.notebook_dir = os.path.abspath(new)
60 return
60 return
61 if not os.path.exists(new) or not os.path.isdir(new):
61 if not os.path.exists(new) or not os.path.isdir(new):
62 raise TraitError("notebook dir %r is not a directory" % new)
62 raise TraitError("notebook dir %r is not a directory" % new)
63
63
64 checkpoint_dir = Unicode(config=True,
64 checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
65 help="""The location in which to keep notebook checkpoints
65 help="""The directory name in which to keep notebook checkpoints
66
67 This is a path relative to the notebook's own directory.
66
68
67 By default, it is notebook-dir/.ipynb_checkpoints
69 By default, it is .ipynb_checkpoints
68 """
70 """
69 )
71 )
70 def _checkpoint_dir_default(self):
71 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
72
73 def _checkpoint_dir_changed(self, name, old, new):
74 """do a bit of validation of the checkpoint dir"""
75 if not os.path.isabs(new):
76 # If we receive a non-absolute path, make it absolute.
77 abs_new = os.path.abspath(new)
78 self.checkpoint_dir = abs_new
79 return
80 if os.path.exists(new) and not os.path.isdir(new):
81 raise TraitError("checkpoint dir %r is not a directory" % new)
82 if not os.path.exists(new):
83 self.log.info("Creating checkpoint dir %s", new)
84 try:
85 os.mkdir(new)
86 except:
87 raise TraitError("Couldn't create checkpoint dir %r" % new)
88
72
89 def _copy(self, src, dest):
73 def _copy(self, src, dest):
90 """copy src to dest
74 """copy src to dest
91
75
92 like shutil.copy2, but log errors in copystat
76 like shutil.copy2, but log errors in copystat
93 """
77 """
94 shutil.copyfile(src, dest)
78 shutil.copyfile(src, dest)
95 try:
79 try:
96 shutil.copystat(src, dest)
80 shutil.copystat(src, dest)
97 except OSError as e:
81 except OSError as e:
98 self.log.debug("copystat on %s failed", dest, exc_info=True)
82 self.log.debug("copystat on %s failed", dest, exc_info=True)
99
83
100 def get_notebook_names(self, path=''):
84 def get_notebook_names(self, path=''):
101 """List all notebook names in the notebook dir and path."""
85 """List all notebook names in the notebook dir and path."""
102 path = path.strip('/')
86 path = path.strip('/')
103 if not os.path.isdir(self._get_os_path(path=path)):
87 if not os.path.isdir(self._get_os_path(path=path)):
104 raise web.HTTPError(404, 'Directory not found: ' + path)
88 raise web.HTTPError(404, 'Directory not found: ' + path)
105 names = glob.glob(self._get_os_path('*'+self.filename_ext, path))
89 names = glob.glob(self._get_os_path('*'+self.filename_ext, path))
106 names = [os.path.basename(name)
90 names = [os.path.basename(name)
107 for name in names]
91 for name in names]
108 return names
92 return names
109
93
110 def path_exists(self, path):
94 def path_exists(self, path):
111 """Does the API-style path (directory) actually exist?
95 """Does the API-style path (directory) actually exist?
112
96
113 Parameters
97 Parameters
114 ----------
98 ----------
115 path : string
99 path : string
116 The path to check. This is an API path (`/` separated,
100 The path to check. This is an API path (`/` separated,
117 relative to base notebook-dir).
101 relative to base notebook-dir).
118
102
119 Returns
103 Returns
120 -------
104 -------
121 exists : bool
105 exists : bool
122 Whether the path is indeed a directory.
106 Whether the path is indeed a directory.
123 """
107 """
124 path = path.strip('/')
108 path = path.strip('/')
125 os_path = self._get_os_path(path=path)
109 os_path = self._get_os_path(path=path)
126 return os.path.isdir(os_path)
110 return os.path.isdir(os_path)
127
111
128 def is_hidden(self, path):
112 def is_hidden(self, path):
129 """Does the API style path correspond to a hidden directory or file?
113 """Does the API style path correspond to a hidden directory or file?
130
114
131 Parameters
115 Parameters
132 ----------
116 ----------
133 path : string
117 path : string
134 The path to check. This is an API path (`/` separated,
118 The path to check. This is an API path (`/` separated,
135 relative to base notebook-dir).
119 relative to base notebook-dir).
136
120
137 Returns
121 Returns
138 -------
122 -------
139 exists : bool
123 exists : bool
140 Whether the path is hidden.
124 Whether the path is hidden.
141
125
142 """
126 """
143 path = path.strip('/')
127 path = path.strip('/')
144 os_path = self._get_os_path(path=path)
128 os_path = self._get_os_path(path=path)
145 return is_hidden(os_path, self.notebook_dir)
129 return is_hidden(os_path, self.notebook_dir)
146
130
147 def _get_os_path(self, name=None, path=''):
131 def _get_os_path(self, name=None, path=''):
148 """Given a notebook name and a URL path, return its file system
132 """Given a notebook name and a URL path, return its file system
149 path.
133 path.
150
134
151 Parameters
135 Parameters
152 ----------
136 ----------
153 name : string
137 name : string
154 The name of a notebook file with the .ipynb extension
138 The name of a notebook file with the .ipynb extension
155 path : string
139 path : string
156 The relative URL path (with '/' as separator) to the named
140 The relative URL path (with '/' as separator) to the named
157 notebook.
141 notebook.
158
142
159 Returns
143 Returns
160 -------
144 -------
161 path : string
145 path : string
162 A file system path that combines notebook_dir (location where
146 A file system path that combines notebook_dir (location where
163 server started), the relative path, and the filename with the
147 server started), the relative path, and the filename with the
164 current operating system's url.
148 current operating system's url.
165 """
149 """
166 if name is not None:
150 if name is not None:
167 path = path + '/' + name
151 path = path + '/' + name
168 return to_os_path(path, self.notebook_dir)
152 return to_os_path(path, self.notebook_dir)
169
153
170 def notebook_exists(self, name, path=''):
154 def notebook_exists(self, name, path=''):
171 """Returns a True if the notebook exists. Else, returns False.
155 """Returns a True if the notebook exists. Else, returns False.
172
156
173 Parameters
157 Parameters
174 ----------
158 ----------
175 name : string
159 name : string
176 The name of the notebook you are checking.
160 The name of the notebook you are checking.
177 path : string
161 path : string
178 The relative path to the notebook (with '/' as separator)
162 The relative path to the notebook (with '/' as separator)
179
163
180 Returns
164 Returns
181 -------
165 -------
182 bool
166 bool
183 """
167 """
184 path = path.strip('/')
168 path = path.strip('/')
185 nbpath = self._get_os_path(name, path=path)
169 nbpath = self._get_os_path(name, path=path)
186 return os.path.isfile(nbpath)
170 return os.path.isfile(nbpath)
187
171
188 # TODO: Remove this after we create the contents web service and directories are
172 # TODO: Remove this after we create the contents web service and directories are
189 # no longer listed by the notebook web service.
173 # no longer listed by the notebook web service.
190 def list_dirs(self, path):
174 def list_dirs(self, path):
191 """List the directories for a given API style path."""
175 """List the directories for a given API style path."""
192 path = path.strip('/')
176 path = path.strip('/')
193 os_path = self._get_os_path('', path)
177 os_path = self._get_os_path('', path)
194 if not os.path.isdir(os_path):
178 if not os.path.isdir(os_path):
195 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
179 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
196 elif is_hidden(os_path, self.notebook_dir):
180 elif is_hidden(os_path, self.notebook_dir):
197 self.log.info("Refusing to serve hidden directory, via 404 Error")
181 self.log.info("Refusing to serve hidden directory, via 404 Error")
198 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
182 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
199 dir_names = os.listdir(os_path)
183 dir_names = os.listdir(os_path)
200 dirs = []
184 dirs = []
201 for name in dir_names:
185 for name in dir_names:
202 os_path = self._get_os_path(name, path)
186 os_path = self._get_os_path(name, path)
203 if os.path.isdir(os_path) and not is_hidden(os_path, self.notebook_dir)\
187 if os.path.isdir(os_path) and not is_hidden(os_path, self.notebook_dir)\
204 and self.should_list(name):
188 and self.should_list(name):
205 try:
189 try:
206 model = self.get_dir_model(name, path)
190 model = self.get_dir_model(name, path)
207 except IOError:
191 except IOError:
208 pass
192 pass
209 dirs.append(model)
193 dirs.append(model)
210 dirs = sorted(dirs, key=sort_key)
194 dirs = sorted(dirs, key=sort_key)
211 return dirs
195 return dirs
212
196
213 # TODO: Remove this after we create the contents web service and directories are
197 # TODO: Remove this after we create the contents web service and directories are
214 # no longer listed by the notebook web service.
198 # no longer listed by the notebook web service.
215 def get_dir_model(self, name, path=''):
199 def get_dir_model(self, name, path=''):
216 """Get the directory model given a directory name and its API style path"""
200 """Get the directory model given a directory name and its API style path"""
217 path = path.strip('/')
201 path = path.strip('/')
218 os_path = self._get_os_path(name, path)
202 os_path = self._get_os_path(name, path)
219 if not os.path.isdir(os_path):
203 if not os.path.isdir(os_path):
220 raise IOError('directory does not exist: %r' % os_path)
204 raise IOError('directory does not exist: %r' % os_path)
221 info = os.stat(os_path)
205 info = os.stat(os_path)
222 last_modified = tz.utcfromtimestamp(info.st_mtime)
206 last_modified = tz.utcfromtimestamp(info.st_mtime)
223 created = tz.utcfromtimestamp(info.st_ctime)
207 created = tz.utcfromtimestamp(info.st_ctime)
224 # Create the notebook model.
208 # Create the notebook model.
225 model ={}
209 model ={}
226 model['name'] = name
210 model['name'] = name
227 model['path'] = path
211 model['path'] = path
228 model['last_modified'] = last_modified
212 model['last_modified'] = last_modified
229 model['created'] = created
213 model['created'] = created
230 model['type'] = 'directory'
214 model['type'] = 'directory'
231 return model
215 return model
232
216
233 def list_notebooks(self, path):
217 def list_notebooks(self, path):
234 """Returns a list of dictionaries that are the standard model
218 """Returns a list of dictionaries that are the standard model
235 for all notebooks in the relative 'path'.
219 for all notebooks in the relative 'path'.
236
220
237 Parameters
221 Parameters
238 ----------
222 ----------
239 path : str
223 path : str
240 the URL path that describes the relative path for the
224 the URL path that describes the relative path for the
241 listed notebooks
225 listed notebooks
242
226
243 Returns
227 Returns
244 -------
228 -------
245 notebooks : list of dicts
229 notebooks : list of dicts
246 a list of the notebook models without 'content'
230 a list of the notebook models without 'content'
247 """
231 """
248 path = path.strip('/')
232 path = path.strip('/')
249 notebook_names = self.get_notebook_names(path)
233 notebook_names = self.get_notebook_names(path)
250 notebooks = [self.get_notebook(name, path, content=False)
234 notebooks = [self.get_notebook(name, path, content=False)
251 for name in notebook_names if self.should_list(name)]
235 for name in notebook_names if self.should_list(name)]
252 notebooks = sorted(notebooks, key=sort_key)
236 notebooks = sorted(notebooks, key=sort_key)
253 return notebooks
237 return notebooks
254
238
255 def get_notebook(self, name, path='', content=True):
239 def get_notebook(self, name, path='', content=True):
256 """ Takes a path and name for a notebook and returns its model
240 """ Takes a path and name for a notebook and returns its model
257
241
258 Parameters
242 Parameters
259 ----------
243 ----------
260 name : str
244 name : str
261 the name of the notebook
245 the name of the notebook
262 path : str
246 path : str
263 the URL path that describes the relative path for
247 the URL path that describes the relative path for
264 the notebook
248 the notebook
265
249
266 Returns
250 Returns
267 -------
251 -------
268 model : dict
252 model : dict
269 the notebook model. If contents=True, returns the 'contents'
253 the notebook model. If contents=True, returns the 'contents'
270 dict in the model as well.
254 dict in the model as well.
271 """
255 """
272 path = path.strip('/')
256 path = path.strip('/')
273 if not self.notebook_exists(name=name, path=path):
257 if not self.notebook_exists(name=name, path=path):
274 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
258 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
275 os_path = self._get_os_path(name, path)
259 os_path = self._get_os_path(name, path)
276 info = os.stat(os_path)
260 info = os.stat(os_path)
277 last_modified = tz.utcfromtimestamp(info.st_mtime)
261 last_modified = tz.utcfromtimestamp(info.st_mtime)
278 created = tz.utcfromtimestamp(info.st_ctime)
262 created = tz.utcfromtimestamp(info.st_ctime)
279 # Create the notebook model.
263 # Create the notebook model.
280 model ={}
264 model ={}
281 model['name'] = name
265 model['name'] = name
282 model['path'] = path
266 model['path'] = path
283 model['last_modified'] = last_modified
267 model['last_modified'] = last_modified
284 model['created'] = created
268 model['created'] = created
285 model['type'] = 'notebook'
269 model['type'] = 'notebook'
286 if content:
270 if content:
287 with io.open(os_path, 'r', encoding='utf-8') as f:
271 with io.open(os_path, 'r', encoding='utf-8') as f:
288 try:
272 try:
289 nb = current.read(f, u'json')
273 nb = current.read(f, u'json')
290 except Exception as e:
274 except Exception as e:
291 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
275 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
292 self.mark_trusted_cells(nb, name, path)
276 self.mark_trusted_cells(nb, name, path)
293 model['content'] = nb
277 model['content'] = nb
294 return model
278 return model
295
279
296 def save_notebook(self, model, name='', path=''):
280 def save_notebook(self, model, name='', path=''):
297 """Save the notebook model and return the model with no content."""
281 """Save the notebook model and return the model with no content."""
298 path = path.strip('/')
282 path = path.strip('/')
299
283
300 if 'content' not in model:
284 if 'content' not in model:
301 raise web.HTTPError(400, u'No notebook JSON data provided')
285 raise web.HTTPError(400, u'No notebook JSON data provided')
302
286
303 # One checkpoint should always exist
287 # One checkpoint should always exist
304 if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
288 if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
305 self.create_checkpoint(name, path)
289 self.create_checkpoint(name, path)
306
290
307 new_path = model.get('path', path).strip('/')
291 new_path = model.get('path', path).strip('/')
308 new_name = model.get('name', name)
292 new_name = model.get('name', name)
309
293
310 if path != new_path or name != new_name:
294 if path != new_path or name != new_name:
311 self.rename_notebook(name, path, new_name, new_path)
295 self.rename_notebook(name, path, new_name, new_path)
312
296
313 # Save the notebook file
297 # Save the notebook file
314 os_path = self._get_os_path(new_name, new_path)
298 os_path = self._get_os_path(new_name, new_path)
315 nb = current.to_notebook_json(model['content'])
299 nb = current.to_notebook_json(model['content'])
316
300
317 self.check_and_sign(nb, new_name, new_path)
301 self.check_and_sign(nb, new_name, new_path)
318
302
319 if 'name' in nb['metadata']:
303 if 'name' in nb['metadata']:
320 nb['metadata']['name'] = u''
304 nb['metadata']['name'] = u''
321 try:
305 try:
322 self.log.debug("Autosaving notebook %s", os_path)
306 self.log.debug("Autosaving notebook %s", os_path)
323 with io.open(os_path, 'w', encoding='utf-8') as f:
307 with io.open(os_path, 'w', encoding='utf-8') as f:
324 current.write(nb, f, u'json')
308 current.write(nb, f, u'json')
325 except Exception as e:
309 except Exception as e:
326 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
310 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
327
311
328 # Save .py script as well
312 # Save .py script as well
329 if self.save_script:
313 if self.save_script:
330 py_path = os.path.splitext(os_path)[0] + '.py'
314 py_path = os.path.splitext(os_path)[0] + '.py'
331 self.log.debug("Writing script %s", py_path)
315 self.log.debug("Writing script %s", py_path)
332 try:
316 try:
333 with io.open(py_path, 'w', encoding='utf-8') as f:
317 with io.open(py_path, 'w', encoding='utf-8') as f:
334 current.write(nb, f, u'py')
318 current.write(nb, f, u'py')
335 except Exception as e:
319 except Exception as e:
336 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
320 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
337
321
338 model = self.get_notebook(new_name, new_path, content=False)
322 model = self.get_notebook(new_name, new_path, content=False)
339 return model
323 return model
340
324
341 def update_notebook(self, model, name, path=''):
325 def update_notebook(self, model, name, path=''):
342 """Update the notebook's path and/or name"""
326 """Update the notebook's path and/or name"""
343 path = path.strip('/')
327 path = path.strip('/')
344 new_name = model.get('name', name)
328 new_name = model.get('name', name)
345 new_path = model.get('path', path).strip('/')
329 new_path = model.get('path', path).strip('/')
346 if path != new_path or name != new_name:
330 if path != new_path or name != new_name:
347 self.rename_notebook(name, path, new_name, new_path)
331 self.rename_notebook(name, path, new_name, new_path)
348 model = self.get_notebook(new_name, new_path, content=False)
332 model = self.get_notebook(new_name, new_path, content=False)
349 return model
333 return model
350
334
351 def delete_notebook(self, name, path=''):
335 def delete_notebook(self, name, path=''):
352 """Delete notebook by name and path."""
336 """Delete notebook by name and path."""
353 path = path.strip('/')
337 path = path.strip('/')
354 os_path = self._get_os_path(name, path)
338 os_path = self._get_os_path(name, path)
355 if not os.path.isfile(os_path):
339 if not os.path.isfile(os_path):
356 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
340 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
357
341
358 # clear checkpoints
342 # clear checkpoints
359 for checkpoint in self.list_checkpoints(name, path):
343 for checkpoint in self.list_checkpoints(name, path):
360 checkpoint_id = checkpoint['id']
344 checkpoint_id = checkpoint['id']
361 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
345 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
362 if os.path.isfile(cp_path):
346 if os.path.isfile(cp_path):
363 self.log.debug("Unlinking checkpoint %s", cp_path)
347 self.log.debug("Unlinking checkpoint %s", cp_path)
364 os.unlink(cp_path)
348 os.unlink(cp_path)
365
349
366 self.log.debug("Unlinking notebook %s", os_path)
350 self.log.debug("Unlinking notebook %s", os_path)
367 os.unlink(os_path)
351 os.unlink(os_path)
368
352
369 def rename_notebook(self, old_name, old_path, new_name, new_path):
353 def rename_notebook(self, old_name, old_path, new_name, new_path):
370 """Rename a notebook."""
354 """Rename a notebook."""
371 old_path = old_path.strip('/')
355 old_path = old_path.strip('/')
372 new_path = new_path.strip('/')
356 new_path = new_path.strip('/')
373 if new_name == old_name and new_path == old_path:
357 if new_name == old_name and new_path == old_path:
374 return
358 return
375
359
376 new_os_path = self._get_os_path(new_name, new_path)
360 new_os_path = self._get_os_path(new_name, new_path)
377 old_os_path = self._get_os_path(old_name, old_path)
361 old_os_path = self._get_os_path(old_name, old_path)
378
362
379 # Should we proceed with the move?
363 # Should we proceed with the move?
380 if os.path.isfile(new_os_path):
364 if os.path.isfile(new_os_path):
381 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
365 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
382 if self.save_script:
366 if self.save_script:
383 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
367 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
384 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
368 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
385 if os.path.isfile(new_py_path):
369 if os.path.isfile(new_py_path):
386 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
370 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
387
371
388 # Move the notebook file
372 # Move the notebook file
389 try:
373 try:
390 shutil.move(old_os_path, new_os_path)
374 shutil.move(old_os_path, new_os_path)
391 except Exception as e:
375 except Exception as e:
392 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
376 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
393
377
394 # Move the checkpoints
378 # Move the checkpoints
395 old_checkpoints = self.list_checkpoints(old_name, old_path)
379 old_checkpoints = self.list_checkpoints(old_name, old_path)
396 for cp in old_checkpoints:
380 for cp in old_checkpoints:
397 checkpoint_id = cp['id']
381 checkpoint_id = cp['id']
398 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
382 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
399 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
383 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
400 if os.path.isfile(old_cp_path):
384 if os.path.isfile(old_cp_path):
401 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
385 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
402 shutil.move(old_cp_path, new_cp_path)
386 shutil.move(old_cp_path, new_cp_path)
403
387
404 # Move the .py script
388 # Move the .py script
405 if self.save_script:
389 if self.save_script:
406 shutil.move(old_py_path, new_py_path)
390 shutil.move(old_py_path, new_py_path)
407
391
408 # Checkpoint-related utilities
392 # Checkpoint-related utilities
409
393
410 def get_checkpoint_path(self, checkpoint_id, name, path=''):
394 def get_checkpoint_path(self, checkpoint_id, name, path=''):
411 """find the path to a checkpoint"""
395 """find the path to a checkpoint"""
412 path = path.strip('/')
396 path = path.strip('/')
413 basename, _ = os.path.splitext(name)
397 basename, _ = os.path.splitext(name)
414 filename = u"{name}-{checkpoint_id}{ext}".format(
398 filename = u"{name}-{checkpoint_id}{ext}".format(
415 name=basename,
399 name=basename,
416 checkpoint_id=checkpoint_id,
400 checkpoint_id=checkpoint_id,
417 ext=self.filename_ext,
401 ext=self.filename_ext,
418 )
402 )
419 cp_path = os.path.join(path, self.checkpoint_dir, filename)
403 os_path = self._get_os_path(path=path)
404 cp_dir = os.path.join(os_path, self.checkpoint_dir)
405 if not os.path.exists(cp_dir):
406 os.mkdir(cp_dir)
407 cp_path = os.path.join(cp_dir, filename)
420 return cp_path
408 return cp_path
421
409
422 def get_checkpoint_model(self, checkpoint_id, name, path=''):
410 def get_checkpoint_model(self, checkpoint_id, name, path=''):
423 """construct the info dict for a given checkpoint"""
411 """construct the info dict for a given checkpoint"""
424 path = path.strip('/')
412 path = path.strip('/')
425 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
413 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
426 stats = os.stat(cp_path)
414 stats = os.stat(cp_path)
427 last_modified = tz.utcfromtimestamp(stats.st_mtime)
415 last_modified = tz.utcfromtimestamp(stats.st_mtime)
428 info = dict(
416 info = dict(
429 id = checkpoint_id,
417 id = checkpoint_id,
430 last_modified = last_modified,
418 last_modified = last_modified,
431 )
419 )
432 return info
420 return info
433
421
434 # public checkpoint API
422 # public checkpoint API
435
423
436 def create_checkpoint(self, name, path=''):
424 def create_checkpoint(self, name, path=''):
437 """Create a checkpoint from the current state of a notebook"""
425 """Create a checkpoint from the current state of a notebook"""
438 path = path.strip('/')
426 path = path.strip('/')
439 nb_path = self._get_os_path(name, path)
427 nb_path = self._get_os_path(name, path)
440 # only the one checkpoint ID:
428 # only the one checkpoint ID:
441 checkpoint_id = u"checkpoint"
429 checkpoint_id = u"checkpoint"
442 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
430 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
443 self.log.debug("creating checkpoint for notebook %s", name)
431 self.log.debug("creating checkpoint for notebook %s", name)
444 if not os.path.exists(self.checkpoint_dir):
432 if not os.path.exists(self.checkpoint_dir):
445 os.mkdir(self.checkpoint_dir)
433 os.mkdir(self.checkpoint_dir)
446 self._copy(nb_path, cp_path)
434 self._copy(nb_path, cp_path)
447
435
448 # return the checkpoint info
436 # return the checkpoint info
449 return self.get_checkpoint_model(checkpoint_id, name, path)
437 return self.get_checkpoint_model(checkpoint_id, name, path)
450
438
451 def list_checkpoints(self, name, path=''):
439 def list_checkpoints(self, name, path=''):
452 """list the checkpoints for a given notebook
440 """list the checkpoints for a given notebook
453
441
454 This notebook manager currently only supports one checkpoint per notebook.
442 This notebook manager currently only supports one checkpoint per notebook.
455 """
443 """
456 path = path.strip('/')
444 path = path.strip('/')
457 checkpoint_id = "checkpoint"
445 checkpoint_id = "checkpoint"
458 path = self.get_checkpoint_path(checkpoint_id, name, path)
446 os_path = self.get_checkpoint_path(checkpoint_id, name, path)
459 if not os.path.exists(path):
447 if not os.path.exists(os_path):
460 return []
448 return []
461 else:
449 else:
462 return [self.get_checkpoint_model(checkpoint_id, name, path)]
450 return [self.get_checkpoint_model(checkpoint_id, name, path)]
463
451
464
452
465 def restore_checkpoint(self, checkpoint_id, name, path=''):
453 def restore_checkpoint(self, checkpoint_id, name, path=''):
466 """restore a notebook to a checkpointed state"""
454 """restore a notebook to a checkpointed state"""
467 path = path.strip('/')
455 path = path.strip('/')
468 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
456 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
469 nb_path = self._get_os_path(name, path)
457 nb_path = self._get_os_path(name, path)
470 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
458 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
471 if not os.path.isfile(cp_path):
459 if not os.path.isfile(cp_path):
472 self.log.debug("checkpoint file does not exist: %s", cp_path)
460 self.log.debug("checkpoint file does not exist: %s", cp_path)
473 raise web.HTTPError(404,
461 raise web.HTTPError(404,
474 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
462 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
475 )
463 )
476 # ensure notebook is readable (never restore from an unreadable notebook)
464 # ensure notebook is readable (never restore from an unreadable notebook)
477 with io.open(cp_path, 'r', encoding='utf-8') as f:
465 with io.open(cp_path, 'r', encoding='utf-8') as f:
478 current.read(f, u'json')
466 current.read(f, u'json')
479 self._copy(cp_path, nb_path)
467 self._copy(cp_path, nb_path)
480 self.log.debug("copying %s -> %s", cp_path, nb_path)
468 self.log.debug("copying %s -> %s", cp_path, nb_path)
481
469
482 def delete_checkpoint(self, checkpoint_id, name, path=''):
470 def delete_checkpoint(self, checkpoint_id, name, path=''):
483 """delete a notebook's checkpoint"""
471 """delete a notebook's checkpoint"""
484 path = path.strip('/')
472 path = path.strip('/')
485 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
473 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
486 if not os.path.isfile(cp_path):
474 if not os.path.isfile(cp_path):
487 raise web.HTTPError(404,
475 raise web.HTTPError(404,
488 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
476 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
489 )
477 )
490 self.log.debug("unlinking %s", cp_path)
478 self.log.debug("unlinking %s", cp_path)
491 os.unlink(cp_path)
479 os.unlink(cp_path)
492
480
493 def info_string(self):
481 def info_string(self):
494 return "Serving notebooks from local directory: %s" % self.notebook_dir
482 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,306 +1,320 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for the notebook manager."""
2 """Tests for the notebook manager."""
3 from __future__ import print_function
3 from __future__ import print_function
4
4
5 import logging
5 import logging
6 import os
6 import os
7
7
8 from tornado.web import HTTPError
8 from tornado.web import HTTPError
9 from unittest import TestCase
9 from unittest import TestCase
10 from tempfile import NamedTemporaryFile
10 from tempfile import NamedTemporaryFile
11
11
12 from IPython.nbformat import current
12 from IPython.nbformat import current
13
13
14 from IPython.utils.tempdir import TemporaryDirectory
14 from IPython.utils.tempdir import TemporaryDirectory
15 from IPython.utils.traitlets import TraitError
15 from IPython.utils.traitlets import TraitError
16 from IPython.html.utils import url_path_join
16 from IPython.html.utils import url_path_join
17
17
18 from ..filenbmanager import FileNotebookManager
18 from ..filenbmanager import FileNotebookManager
19 from ..nbmanager import NotebookManager
19 from ..nbmanager import NotebookManager
20
20
21
21
22 class TestFileNotebookManager(TestCase):
22 class TestFileNotebookManager(TestCase):
23
23
24 def test_nb_dir(self):
24 def test_nb_dir(self):
25 with TemporaryDirectory() as td:
25 with TemporaryDirectory() as td:
26 fm = FileNotebookManager(notebook_dir=td)
26 fm = FileNotebookManager(notebook_dir=td)
27 self.assertEqual(fm.notebook_dir, td)
27 self.assertEqual(fm.notebook_dir, td)
28
28
29 def test_missing_nb_dir(self):
29 def test_missing_nb_dir(self):
30 with TemporaryDirectory() as td:
30 with TemporaryDirectory() as td:
31 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
31 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
32 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir)
32 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir)
33
33
34 def test_invalid_nb_dir(self):
34 def test_invalid_nb_dir(self):
35 with NamedTemporaryFile() as tf:
35 with NamedTemporaryFile() as tf:
36 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name)
36 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name)
37
37
38 def test_get_os_path(self):
38 def test_get_os_path(self):
39 # full filesystem path should be returned with correct operating system
39 # full filesystem path should be returned with correct operating system
40 # separators.
40 # separators.
41 with TemporaryDirectory() as td:
41 with TemporaryDirectory() as td:
42 nbdir = td
42 nbdir = td
43 fm = FileNotebookManager(notebook_dir=nbdir)
43 fm = FileNotebookManager(notebook_dir=nbdir)
44 path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
44 path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
45 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
45 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
46 fs_path = os.path.join(fm.notebook_dir, *rel_path_list)
46 fs_path = os.path.join(fm.notebook_dir, *rel_path_list)
47 self.assertEqual(path, fs_path)
47 self.assertEqual(path, fs_path)
48
48
49 fm = FileNotebookManager(notebook_dir=nbdir)
49 fm = FileNotebookManager(notebook_dir=nbdir)
50 path = fm._get_os_path('test.ipynb')
50 path = fm._get_os_path('test.ipynb')
51 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
51 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
52 self.assertEqual(path, fs_path)
52 self.assertEqual(path, fs_path)
53
53
54 fm = FileNotebookManager(notebook_dir=nbdir)
54 fm = FileNotebookManager(notebook_dir=nbdir)
55 path = fm._get_os_path('test.ipynb', '////')
55 path = fm._get_os_path('test.ipynb', '////')
56 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
56 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
57 self.assertEqual(path, fs_path)
57 self.assertEqual(path, fs_path)
58
59 def test_checkpoint_subdir(self):
60 subd = u'sub βˆ‚ir'
61 cp_name = 'test-cp.ipynb'
62 with TemporaryDirectory() as td:
63 nbdir = td
64 os.mkdir(os.path.join(td, subd))
65 fm = FileNotebookManager(notebook_dir=nbdir)
66 cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/')
67 cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd)
68 self.assertNotEqual(cp_dir, cp_subdir)
69 self.assertEqual(cp_dir, os.path.join(nbdir, fm.checkpoint_dir, cp_name))
70 self.assertEqual(cp_subdir, os.path.join(nbdir, subd, fm.checkpoint_dir, cp_name))
71
58
72
59 class TestNotebookManager(TestCase):
73 class TestNotebookManager(TestCase):
60
74
61 def setUp(self):
75 def setUp(self):
62 self._temp_dir = TemporaryDirectory()
76 self._temp_dir = TemporaryDirectory()
63 self.td = self._temp_dir.name
77 self.td = self._temp_dir.name
64 self.notebook_manager = FileNotebookManager(
78 self.notebook_manager = FileNotebookManager(
65 notebook_dir=self.td,
79 notebook_dir=self.td,
66 log=logging.getLogger()
80 log=logging.getLogger()
67 )
81 )
68
82
69 def tearDown(self):
83 def tearDown(self):
70 self._temp_dir.cleanup()
84 self._temp_dir.cleanup()
71
85
72 def make_dir(self, abs_path, rel_path):
86 def make_dir(self, abs_path, rel_path):
73 """make subdirectory, rel_path is the relative path
87 """make subdirectory, rel_path is the relative path
74 to that directory from the location where the server started"""
88 to that directory from the location where the server started"""
75 os_path = os.path.join(abs_path, rel_path)
89 os_path = os.path.join(abs_path, rel_path)
76 try:
90 try:
77 os.makedirs(os_path)
91 os.makedirs(os_path)
78 except OSError:
92 except OSError:
79 print("Directory already exists: %r" % os_path)
93 print("Directory already exists: %r" % os_path)
80
94
81 def add_code_cell(self, nb):
95 def add_code_cell(self, nb):
82 output = current.new_output("display_data", output_javascript="alert('hi');")
96 output = current.new_output("display_data", output_javascript="alert('hi');")
83 cell = current.new_code_cell("print('hi')", outputs=[output])
97 cell = current.new_code_cell("print('hi')", outputs=[output])
84 if not nb.worksheets:
98 if not nb.worksheets:
85 nb.worksheets.append(current.new_worksheet())
99 nb.worksheets.append(current.new_worksheet())
86 nb.worksheets[0].cells.append(cell)
100 nb.worksheets[0].cells.append(cell)
87
101
88 def new_notebook(self):
102 def new_notebook(self):
89 nbm = self.notebook_manager
103 nbm = self.notebook_manager
90 model = nbm.create_notebook()
104 model = nbm.create_notebook()
91 name = model['name']
105 name = model['name']
92 path = model['path']
106 path = model['path']
93
107
94 full_model = nbm.get_notebook(name, path)
108 full_model = nbm.get_notebook(name, path)
95 nb = full_model['content']
109 nb = full_model['content']
96 self.add_code_cell(nb)
110 self.add_code_cell(nb)
97
111
98 nbm.save_notebook(full_model, name, path)
112 nbm.save_notebook(full_model, name, path)
99 return nb, name, path
113 return nb, name, path
100
114
101 def test_create_notebook(self):
115 def test_create_notebook(self):
102 nm = self.notebook_manager
116 nm = self.notebook_manager
103 # Test in root directory
117 # Test in root directory
104 model = nm.create_notebook()
118 model = nm.create_notebook()
105 assert isinstance(model, dict)
119 assert isinstance(model, dict)
106 self.assertIn('name', model)
120 self.assertIn('name', model)
107 self.assertIn('path', model)
121 self.assertIn('path', model)
108 self.assertEqual(model['name'], 'Untitled0.ipynb')
122 self.assertEqual(model['name'], 'Untitled0.ipynb')
109 self.assertEqual(model['path'], '')
123 self.assertEqual(model['path'], '')
110
124
111 # Test in sub-directory
125 # Test in sub-directory
112 sub_dir = '/foo/'
126 sub_dir = '/foo/'
113 self.make_dir(nm.notebook_dir, 'foo')
127 self.make_dir(nm.notebook_dir, 'foo')
114 model = nm.create_notebook(None, sub_dir)
128 model = nm.create_notebook(None, sub_dir)
115 assert isinstance(model, dict)
129 assert isinstance(model, dict)
116 self.assertIn('name', model)
130 self.assertIn('name', model)
117 self.assertIn('path', model)
131 self.assertIn('path', model)
118 self.assertEqual(model['name'], 'Untitled0.ipynb')
132 self.assertEqual(model['name'], 'Untitled0.ipynb')
119 self.assertEqual(model['path'], sub_dir.strip('/'))
133 self.assertEqual(model['path'], sub_dir.strip('/'))
120
134
121 def test_get_notebook(self):
135 def test_get_notebook(self):
122 nm = self.notebook_manager
136 nm = self.notebook_manager
123 # Create a notebook
137 # Create a notebook
124 model = nm.create_notebook()
138 model = nm.create_notebook()
125 name = model['name']
139 name = model['name']
126 path = model['path']
140 path = model['path']
127
141
128 # Check that we 'get' on the notebook we just created
142 # Check that we 'get' on the notebook we just created
129 model2 = nm.get_notebook(name, path)
143 model2 = nm.get_notebook(name, path)
130 assert isinstance(model2, dict)
144 assert isinstance(model2, dict)
131 self.assertIn('name', model2)
145 self.assertIn('name', model2)
132 self.assertIn('path', model2)
146 self.assertIn('path', model2)
133 self.assertEqual(model['name'], name)
147 self.assertEqual(model['name'], name)
134 self.assertEqual(model['path'], path)
148 self.assertEqual(model['path'], path)
135
149
136 # Test in sub-directory
150 # Test in sub-directory
137 sub_dir = '/foo/'
151 sub_dir = '/foo/'
138 self.make_dir(nm.notebook_dir, 'foo')
152 self.make_dir(nm.notebook_dir, 'foo')
139 model = nm.create_notebook(None, sub_dir)
153 model = nm.create_notebook(None, sub_dir)
140 model2 = nm.get_notebook(name, sub_dir)
154 model2 = nm.get_notebook(name, sub_dir)
141 assert isinstance(model2, dict)
155 assert isinstance(model2, dict)
142 self.assertIn('name', model2)
156 self.assertIn('name', model2)
143 self.assertIn('path', model2)
157 self.assertIn('path', model2)
144 self.assertIn('content', model2)
158 self.assertIn('content', model2)
145 self.assertEqual(model2['name'], 'Untitled0.ipynb')
159 self.assertEqual(model2['name'], 'Untitled0.ipynb')
146 self.assertEqual(model2['path'], sub_dir.strip('/'))
160 self.assertEqual(model2['path'], sub_dir.strip('/'))
147
161
148 def test_update_notebook(self):
162 def test_update_notebook(self):
149 nm = self.notebook_manager
163 nm = self.notebook_manager
150 # Create a notebook
164 # Create a notebook
151 model = nm.create_notebook()
165 model = nm.create_notebook()
152 name = model['name']
166 name = model['name']
153 path = model['path']
167 path = model['path']
154
168
155 # Change the name in the model for rename
169 # Change the name in the model for rename
156 model['name'] = 'test.ipynb'
170 model['name'] = 'test.ipynb'
157 model = nm.update_notebook(model, name, path)
171 model = nm.update_notebook(model, name, path)
158 assert isinstance(model, dict)
172 assert isinstance(model, dict)
159 self.assertIn('name', model)
173 self.assertIn('name', model)
160 self.assertIn('path', model)
174 self.assertIn('path', model)
161 self.assertEqual(model['name'], 'test.ipynb')
175 self.assertEqual(model['name'], 'test.ipynb')
162
176
163 # Make sure the old name is gone
177 # Make sure the old name is gone
164 self.assertRaises(HTTPError, nm.get_notebook, name, path)
178 self.assertRaises(HTTPError, nm.get_notebook, name, path)
165
179
166 # Test in sub-directory
180 # Test in sub-directory
167 # Create a directory and notebook in that directory
181 # Create a directory and notebook in that directory
168 sub_dir = '/foo/'
182 sub_dir = '/foo/'
169 self.make_dir(nm.notebook_dir, 'foo')
183 self.make_dir(nm.notebook_dir, 'foo')
170 model = nm.create_notebook(None, sub_dir)
184 model = nm.create_notebook(None, sub_dir)
171 name = model['name']
185 name = model['name']
172 path = model['path']
186 path = model['path']
173
187
174 # Change the name in the model for rename
188 # Change the name in the model for rename
175 model['name'] = 'test_in_sub.ipynb'
189 model['name'] = 'test_in_sub.ipynb'
176 model = nm.update_notebook(model, name, path)
190 model = nm.update_notebook(model, name, path)
177 assert isinstance(model, dict)
191 assert isinstance(model, dict)
178 self.assertIn('name', model)
192 self.assertIn('name', model)
179 self.assertIn('path', model)
193 self.assertIn('path', model)
180 self.assertEqual(model['name'], 'test_in_sub.ipynb')
194 self.assertEqual(model['name'], 'test_in_sub.ipynb')
181 self.assertEqual(model['path'], sub_dir.strip('/'))
195 self.assertEqual(model['path'], sub_dir.strip('/'))
182
196
183 # Make sure the old name is gone
197 # Make sure the old name is gone
184 self.assertRaises(HTTPError, nm.get_notebook, name, path)
198 self.assertRaises(HTTPError, nm.get_notebook, name, path)
185
199
186 def test_save_notebook(self):
200 def test_save_notebook(self):
187 nm = self.notebook_manager
201 nm = self.notebook_manager
188 # Create a notebook
202 # Create a notebook
189 model = nm.create_notebook()
203 model = nm.create_notebook()
190 name = model['name']
204 name = model['name']
191 path = model['path']
205 path = model['path']
192
206
193 # Get the model with 'content'
207 # Get the model with 'content'
194 full_model = nm.get_notebook(name, path)
208 full_model = nm.get_notebook(name, path)
195
209
196 # Save the notebook
210 # Save the notebook
197 model = nm.save_notebook(full_model, name, path)
211 model = nm.save_notebook(full_model, name, path)
198 assert isinstance(model, dict)
212 assert isinstance(model, dict)
199 self.assertIn('name', model)
213 self.assertIn('name', model)
200 self.assertIn('path', model)
214 self.assertIn('path', model)
201 self.assertEqual(model['name'], name)
215 self.assertEqual(model['name'], name)
202 self.assertEqual(model['path'], path)
216 self.assertEqual(model['path'], path)
203
217
204 # Test in sub-directory
218 # Test in sub-directory
205 # Create a directory and notebook in that directory
219 # Create a directory and notebook in that directory
206 sub_dir = '/foo/'
220 sub_dir = '/foo/'
207 self.make_dir(nm.notebook_dir, 'foo')
221 self.make_dir(nm.notebook_dir, 'foo')
208 model = nm.create_notebook(None, sub_dir)
222 model = nm.create_notebook(None, sub_dir)
209 name = model['name']
223 name = model['name']
210 path = model['path']
224 path = model['path']
211 model = nm.get_notebook(name, path)
225 model = nm.get_notebook(name, path)
212
226
213 # Change the name in the model for rename
227 # Change the name in the model for rename
214 model = nm.save_notebook(model, name, path)
228 model = nm.save_notebook(model, name, path)
215 assert isinstance(model, dict)
229 assert isinstance(model, dict)
216 self.assertIn('name', model)
230 self.assertIn('name', model)
217 self.assertIn('path', model)
231 self.assertIn('path', model)
218 self.assertEqual(model['name'], 'Untitled0.ipynb')
232 self.assertEqual(model['name'], 'Untitled0.ipynb')
219 self.assertEqual(model['path'], sub_dir.strip('/'))
233 self.assertEqual(model['path'], sub_dir.strip('/'))
220
234
221 def test_save_notebook_with_script(self):
235 def test_save_notebook_with_script(self):
222 nm = self.notebook_manager
236 nm = self.notebook_manager
223 # Create a notebook
237 # Create a notebook
224 model = nm.create_notebook()
238 model = nm.create_notebook()
225 nm.save_script = True
239 nm.save_script = True
226 model = nm.create_notebook()
240 model = nm.create_notebook()
227 name = model['name']
241 name = model['name']
228 path = model['path']
242 path = model['path']
229
243
230 # Get the model with 'content'
244 # Get the model with 'content'
231 full_model = nm.get_notebook(name, path)
245 full_model = nm.get_notebook(name, path)
232
246
233 # Save the notebook
247 # Save the notebook
234 model = nm.save_notebook(full_model, name, path)
248 model = nm.save_notebook(full_model, name, path)
235
249
236 # Check that the script was created
250 # Check that the script was created
237 py_path = os.path.join(nm.notebook_dir, os.path.splitext(name)[0]+'.py')
251 py_path = os.path.join(nm.notebook_dir, os.path.splitext(name)[0]+'.py')
238 assert os.path.exists(py_path), py_path
252 assert os.path.exists(py_path), py_path
239
253
240 def test_delete_notebook(self):
254 def test_delete_notebook(self):
241 nm = self.notebook_manager
255 nm = self.notebook_manager
242 # Create a notebook
256 # Create a notebook
243 nb, name, path = self.new_notebook()
257 nb, name, path = self.new_notebook()
244
258
245 # Delete the notebook
259 # Delete the notebook
246 nm.delete_notebook(name, path)
260 nm.delete_notebook(name, path)
247
261
248 # Check that a 'get' on the deleted notebook raises and error
262 # Check that a 'get' on the deleted notebook raises and error
249 self.assertRaises(HTTPError, nm.get_notebook, name, path)
263 self.assertRaises(HTTPError, nm.get_notebook, name, path)
250
264
251 def test_copy_notebook(self):
265 def test_copy_notebook(self):
252 nm = self.notebook_manager
266 nm = self.notebook_manager
253 path = u'Γ₯ b'
267 path = u'Γ₯ b'
254 name = u'nb √.ipynb'
268 name = u'nb √.ipynb'
255 os.mkdir(os.path.join(nm.notebook_dir, path))
269 os.mkdir(os.path.join(nm.notebook_dir, path))
256 orig = nm.create_notebook({'name' : name}, path=path)
270 orig = nm.create_notebook({'name' : name}, path=path)
257
271
258 # copy with unspecified name
272 # copy with unspecified name
259 copy = nm.copy_notebook(name, path=path)
273 copy = nm.copy_notebook(name, path=path)
260 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
274 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
261
275
262 # copy with specified name
276 # copy with specified name
263 copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path)
277 copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path)
264 self.assertEqual(copy2['name'], u'copy 2.ipynb')
278 self.assertEqual(copy2['name'], u'copy 2.ipynb')
265
279
266 def test_trust_notebook(self):
280 def test_trust_notebook(self):
267 nbm = self.notebook_manager
281 nbm = self.notebook_manager
268 nb, name, path = self.new_notebook()
282 nb, name, path = self.new_notebook()
269
283
270 untrusted = nbm.get_notebook(name, path)['content']
284 untrusted = nbm.get_notebook(name, path)['content']
271 assert not nbm.notary.check_cells(untrusted)
285 assert not nbm.notary.check_cells(untrusted)
272
286
273 # print(untrusted)
287 # print(untrusted)
274 nbm.trust_notebook(name, path)
288 nbm.trust_notebook(name, path)
275 trusted = nbm.get_notebook(name, path)['content']
289 trusted = nbm.get_notebook(name, path)['content']
276 # print(trusted)
290 # print(trusted)
277 assert nbm.notary.check_cells(trusted)
291 assert nbm.notary.check_cells(trusted)
278
292
279 def test_mark_trusted_cells(self):
293 def test_mark_trusted_cells(self):
280 nbm = self.notebook_manager
294 nbm = self.notebook_manager
281 nb, name, path = self.new_notebook()
295 nb, name, path = self.new_notebook()
282
296
283 nbm.mark_trusted_cells(nb, name, path)
297 nbm.mark_trusted_cells(nb, name, path)
284 for cell in nb.worksheets[0].cells:
298 for cell in nb.worksheets[0].cells:
285 if cell.cell_type == 'code':
299 if cell.cell_type == 'code':
286 assert not cell.trusted
300 assert not cell.trusted
287
301
288 nbm.trust_notebook(name, path)
302 nbm.trust_notebook(name, path)
289 nb = nbm.get_notebook(name, path)['content']
303 nb = nbm.get_notebook(name, path)['content']
290 for cell in nb.worksheets[0].cells:
304 for cell in nb.worksheets[0].cells:
291 if cell.cell_type == 'code':
305 if cell.cell_type == 'code':
292 assert cell.trusted
306 assert cell.trusted
293
307
294 def test_check_and_sign(self):
308 def test_check_and_sign(self):
295 nbm = self.notebook_manager
309 nbm = self.notebook_manager
296 nb, name, path = self.new_notebook()
310 nb, name, path = self.new_notebook()
297
311
298 nbm.mark_trusted_cells(nb, name, path)
312 nbm.mark_trusted_cells(nb, name, path)
299 nbm.check_and_sign(nb, name, path)
313 nbm.check_and_sign(nb, name, path)
300 assert not nbm.notary.check_signature(nb)
314 assert not nbm.notary.check_signature(nb)
301
315
302 nbm.trust_notebook(name, path)
316 nbm.trust_notebook(name, path)
303 nb = nbm.get_notebook(name, path)['content']
317 nb = nbm.get_notebook(name, path)['content']
304 nbm.mark_trusted_cells(nb, name, path)
318 nbm.mark_trusted_cells(nb, name, path)
305 nbm.check_and_sign(nb, name, path)
319 nbm.check_and_sign(nb, name, path)
306 assert nbm.notary.check_signature(nb)
320 assert nbm.notary.check_signature(nb)
General Comments 0
You need to be logged in to leave comments. Login now