##// END OF EJS Templates
Fix a couple of static analysis warnings
Thomas Kluyver -
Show More
@@ -1,480 +1,480 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, Dict, 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(config=True,
65 help="""The location in which to keep notebook checkpoints
65 help="""The location in which to keep notebook checkpoints
66
66
67 By default, it is notebook-dir/.ipynb_checkpoints
67 By default, it is notebook-dir/.ipynb_checkpoints
68 """
68 """
69 )
69 )
70 def _checkpoint_dir_default(self):
70 def _checkpoint_dir_default(self):
71 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
71 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
72
72
73 def _checkpoint_dir_changed(self, name, old, new):
73 def _checkpoint_dir_changed(self, name, old, new):
74 """do a bit of validation of the checkpoint dir"""
74 """do a bit of validation of the checkpoint dir"""
75 if not os.path.isabs(new):
75 if not os.path.isabs(new):
76 # If we receive a non-absolute path, make it absolute.
76 # If we receive a non-absolute path, make it absolute.
77 abs_new = os.path.abspath(new)
77 abs_new = os.path.abspath(new)
78 self.checkpoint_dir = abs_new
78 self.checkpoint_dir = abs_new
79 return
79 return
80 if os.path.exists(new) and not os.path.isdir(new):
80 if os.path.exists(new) and not os.path.isdir(new):
81 raise TraitError("checkpoint dir %r is not a directory" % new)
81 raise TraitError("checkpoint dir %r is not a directory" % new)
82 if not os.path.exists(new):
82 if not os.path.exists(new):
83 self.log.info("Creating checkpoint dir %s", new)
83 self.log.info("Creating checkpoint dir %s", new)
84 try:
84 try:
85 os.mkdir(new)
85 os.mkdir(new)
86 except:
86 except:
87 raise TraitError("Couldn't create checkpoint dir %r" % new)
87 raise TraitError("Couldn't create checkpoint dir %r" % new)
88
88
89 def get_notebook_names(self, path=''):
89 def get_notebook_names(self, path=''):
90 """List all notebook names in the notebook dir and path."""
90 """List all notebook names in the notebook dir and path."""
91 path = path.strip('/')
91 path = path.strip('/')
92 if not os.path.isdir(self._get_os_path(path=path)):
92 if not os.path.isdir(self._get_os_path(path=path)):
93 raise web.HTTPError(404, 'Directory not found: ' + path)
93 raise web.HTTPError(404, 'Directory not found: ' + path)
94 names = glob.glob(self._get_os_path('*'+self.filename_ext, path))
94 names = glob.glob(self._get_os_path('*'+self.filename_ext, path))
95 names = [os.path.basename(name)
95 names = [os.path.basename(name)
96 for name in names]
96 for name in names]
97 return names
97 return names
98
98
99 def path_exists(self, path):
99 def path_exists(self, path):
100 """Does the API-style path (directory) actually exist?
100 """Does the API-style path (directory) actually exist?
101
101
102 Parameters
102 Parameters
103 ----------
103 ----------
104 path : string
104 path : string
105 The path to check. This is an API path (`/` separated,
105 The path to check. This is an API path (`/` separated,
106 relative to base notebook-dir).
106 relative to base notebook-dir).
107
107
108 Returns
108 Returns
109 -------
109 -------
110 exists : bool
110 exists : bool
111 Whether the path is indeed a directory.
111 Whether the path is indeed a directory.
112 """
112 """
113 path = path.strip('/')
113 path = path.strip('/')
114 os_path = self._get_os_path(path=path)
114 os_path = self._get_os_path(path=path)
115 return os.path.isdir(os_path)
115 return os.path.isdir(os_path)
116
116
117 def is_hidden(self, path):
117 def is_hidden(self, path):
118 """Does the API style path correspond to a hidden directory or file?
118 """Does the API style path correspond to a hidden directory or file?
119
119
120 Parameters
120 Parameters
121 ----------
121 ----------
122 path : string
122 path : string
123 The path to check. This is an API path (`/` separated,
123 The path to check. This is an API path (`/` separated,
124 relative to base notebook-dir).
124 relative to base notebook-dir).
125
125
126 Returns
126 Returns
127 -------
127 -------
128 exists : bool
128 exists : bool
129 Whether the path is hidden.
129 Whether the path is hidden.
130
130
131 """
131 """
132 path = path.strip('/')
132 path = path.strip('/')
133 os_path = self._get_os_path(path=path)
133 os_path = self._get_os_path(path=path)
134 return is_hidden(os_path, self.notebook_dir)
134 return is_hidden(os_path, self.notebook_dir)
135
135
136 def _get_os_path(self, name=None, path=''):
136 def _get_os_path(self, name=None, path=''):
137 """Given a notebook name and a URL path, return its file system
137 """Given a notebook name and a URL path, return its file system
138 path.
138 path.
139
139
140 Parameters
140 Parameters
141 ----------
141 ----------
142 name : string
142 name : string
143 The name of a notebook file with the .ipynb extension
143 The name of a notebook file with the .ipynb extension
144 path : string
144 path : string
145 The relative URL path (with '/' as separator) to the named
145 The relative URL path (with '/' as separator) to the named
146 notebook.
146 notebook.
147
147
148 Returns
148 Returns
149 -------
149 -------
150 path : string
150 path : string
151 A file system path that combines notebook_dir (location where
151 A file system path that combines notebook_dir (location where
152 server started), the relative path, and the filename with the
152 server started), the relative path, and the filename with the
153 current operating system's url.
153 current operating system's url.
154 """
154 """
155 if name is not None:
155 if name is not None:
156 path = path + '/' + name
156 path = path + '/' + name
157 return to_os_path(path, self.notebook_dir)
157 return to_os_path(path, self.notebook_dir)
158
158
159 def notebook_exists(self, name, path=''):
159 def notebook_exists(self, name, path=''):
160 """Returns a True if the notebook exists. Else, returns False.
160 """Returns a True if the notebook exists. Else, returns False.
161
161
162 Parameters
162 Parameters
163 ----------
163 ----------
164 name : string
164 name : string
165 The name of the notebook you are checking.
165 The name of the notebook you are checking.
166 path : string
166 path : string
167 The relative path to the notebook (with '/' as separator)
167 The relative path to the notebook (with '/' as separator)
168
168
169 Returns
169 Returns
170 -------
170 -------
171 bool
171 bool
172 """
172 """
173 path = path.strip('/')
173 path = path.strip('/')
174 nbpath = self._get_os_path(name, path=path)
174 nbpath = self._get_os_path(name, path=path)
175 return os.path.isfile(nbpath)
175 return os.path.isfile(nbpath)
176
176
177 # TODO: Remove this after we create the contents web service and directories are
177 # TODO: Remove this after we create the contents web service and directories are
178 # no longer listed by the notebook web service.
178 # no longer listed by the notebook web service.
179 def list_dirs(self, path):
179 def list_dirs(self, path):
180 """List the directories for a given API style path."""
180 """List the directories for a given API style path."""
181 path = path.strip('/')
181 path = path.strip('/')
182 os_path = self._get_os_path('', path)
182 os_path = self._get_os_path('', path)
183 if not os.path.isdir(os_path) or is_hidden(os_path, self.notebook_dir):
183 if not os.path.isdir(os_path) or is_hidden(os_path, self.notebook_dir):
184 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
184 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
185 dir_names = os.listdir(os_path)
185 dir_names = os.listdir(os_path)
186 dirs = []
186 dirs = []
187 for name in dir_names:
187 for name in dir_names:
188 os_path = self._get_os_path(name, path)
188 os_path = self._get_os_path(name, path)
189 if os.path.isdir(os_path) and not is_hidden(os_path, self.notebook_dir)\
189 if os.path.isdir(os_path) and not is_hidden(os_path, self.notebook_dir)\
190 and self.should_list(name):
190 and self.should_list(name):
191 try:
191 try:
192 model = self.get_dir_model(name, path)
192 model = self.get_dir_model(name, path)
193 except IOError:
193 except IOError:
194 pass
194 pass
195 dirs.append(model)
195 dirs.append(model)
196 dirs = sorted(dirs, key=sort_key)
196 dirs = sorted(dirs, key=sort_key)
197 return dirs
197 return dirs
198
198
199 # TODO: Remove this after we create the contents web service and directories are
199 # TODO: Remove this after we create the contents web service and directories are
200 # no longer listed by the notebook web service.
200 # no longer listed by the notebook web service.
201 def get_dir_model(self, name, path=''):
201 def get_dir_model(self, name, path=''):
202 """Get the directory model given a directory name and its API style path"""
202 """Get the directory model given a directory name and its API style path"""
203 path = path.strip('/')
203 path = path.strip('/')
204 os_path = self._get_os_path(name, path)
204 os_path = self._get_os_path(name, path)
205 if not os.path.isdir(os_path):
205 if not os.path.isdir(os_path):
206 raise IOError('directory does not exist: %r' % os_path)
206 raise IOError('directory does not exist: %r' % os_path)
207 info = os.stat(os_path)
207 info = os.stat(os_path)
208 last_modified = tz.utcfromtimestamp(info.st_mtime)
208 last_modified = tz.utcfromtimestamp(info.st_mtime)
209 created = tz.utcfromtimestamp(info.st_ctime)
209 created = tz.utcfromtimestamp(info.st_ctime)
210 # Create the notebook model.
210 # Create the notebook model.
211 model ={}
211 model ={}
212 model['name'] = name
212 model['name'] = name
213 model['path'] = path
213 model['path'] = path
214 model['last_modified'] = last_modified
214 model['last_modified'] = last_modified
215 model['created'] = created
215 model['created'] = created
216 model['type'] = 'directory'
216 model['type'] = 'directory'
217 return model
217 return model
218
218
219 def list_notebooks(self, path):
219 def list_notebooks(self, path):
220 """Returns a list of dictionaries that are the standard model
220 """Returns a list of dictionaries that are the standard model
221 for all notebooks in the relative 'path'.
221 for all notebooks in the relative 'path'.
222
222
223 Parameters
223 Parameters
224 ----------
224 ----------
225 path : str
225 path : str
226 the URL path that describes the relative path for the
226 the URL path that describes the relative path for the
227 listed notebooks
227 listed notebooks
228
228
229 Returns
229 Returns
230 -------
230 -------
231 notebooks : list of dicts
231 notebooks : list of dicts
232 a list of the notebook models without 'content'
232 a list of the notebook models without 'content'
233 """
233 """
234 path = path.strip('/')
234 path = path.strip('/')
235 notebook_names = self.get_notebook_names(path)
235 notebook_names = self.get_notebook_names(path)
236 notebooks = [self.get_notebook(name, path, content=False)
236 notebooks = [self.get_notebook(name, path, content=False)
237 for name in notebook_names if self.should_list(name)]
237 for name in notebook_names if self.should_list(name)]
238 notebooks = sorted(notebooks, key=sort_key)
238 notebooks = sorted(notebooks, key=sort_key)
239 return notebooks
239 return notebooks
240
240
241 def get_notebook(self, name, path='', content=True):
241 def get_notebook(self, name, path='', content=True):
242 """ Takes a path and name for a notebook and returns its model
242 """ Takes a path and name for a notebook and returns its model
243
243
244 Parameters
244 Parameters
245 ----------
245 ----------
246 name : str
246 name : str
247 the name of the notebook
247 the name of the notebook
248 path : str
248 path : str
249 the URL path that describes the relative path for
249 the URL path that describes the relative path for
250 the notebook
250 the notebook
251
251
252 Returns
252 Returns
253 -------
253 -------
254 model : dict
254 model : dict
255 the notebook model. If contents=True, returns the 'contents'
255 the notebook model. If contents=True, returns the 'contents'
256 dict in the model as well.
256 dict in the model as well.
257 """
257 """
258 path = path.strip('/')
258 path = path.strip('/')
259 if not self.notebook_exists(name=name, path=path):
259 if not self.notebook_exists(name=name, path=path):
260 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
260 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
261 os_path = self._get_os_path(name, path)
261 os_path = self._get_os_path(name, path)
262 info = os.stat(os_path)
262 info = os.stat(os_path)
263 last_modified = tz.utcfromtimestamp(info.st_mtime)
263 last_modified = tz.utcfromtimestamp(info.st_mtime)
264 created = tz.utcfromtimestamp(info.st_ctime)
264 created = tz.utcfromtimestamp(info.st_ctime)
265 # Create the notebook model.
265 # Create the notebook model.
266 model ={}
266 model ={}
267 model['name'] = name
267 model['name'] = name
268 model['path'] = path
268 model['path'] = path
269 model['last_modified'] = last_modified
269 model['last_modified'] = last_modified
270 model['created'] = created
270 model['created'] = created
271 model['type'] = 'notebook'
271 model['type'] = 'notebook'
272 if content:
272 if content:
273 with io.open(os_path, 'r', encoding='utf-8') as f:
273 with io.open(os_path, 'r', encoding='utf-8') as f:
274 try:
274 try:
275 nb = current.read(f, u'json')
275 nb = current.read(f, u'json')
276 except Exception as e:
276 except Exception as e:
277 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
277 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
278 self.mark_trusted_cells(nb, path, name)
278 self.mark_trusted_cells(nb, path, name)
279 model['content'] = nb
279 model['content'] = nb
280 return model
280 return model
281
281
282 def save_notebook(self, model, name='', path=''):
282 def save_notebook(self, model, name='', path=''):
283 """Save the notebook model and return the model with no content."""
283 """Save the notebook model and return the model with no content."""
284 path = path.strip('/')
284 path = path.strip('/')
285
285
286 if 'content' not in model:
286 if 'content' not in model:
287 raise web.HTTPError(400, u'No notebook JSON data provided')
287 raise web.HTTPError(400, u'No notebook JSON data provided')
288
288
289 # One checkpoint should always exist
289 # One checkpoint should always exist
290 if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
290 if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
291 self.create_checkpoint(name, path)
291 self.create_checkpoint(name, path)
292
292
293 new_path = model.get('path', path).strip('/')
293 new_path = model.get('path', path).strip('/')
294 new_name = model.get('name', name)
294 new_name = model.get('name', name)
295
295
296 if path != new_path or name != new_name:
296 if path != new_path or name != new_name:
297 self.rename_notebook(name, path, new_name, new_path)
297 self.rename_notebook(name, path, new_name, new_path)
298
298
299 # Save the notebook file
299 # Save the notebook file
300 os_path = self._get_os_path(new_name, new_path)
300 os_path = self._get_os_path(new_name, new_path)
301 nb = current.to_notebook_json(model['content'])
301 nb = current.to_notebook_json(model['content'])
302
302
303 self.check_and_sign(nb, new_path, new_name)
303 self.check_and_sign(nb, new_path, new_name)
304
304
305 if 'name' in nb['metadata']:
305 if 'name' in nb['metadata']:
306 nb['metadata']['name'] = u''
306 nb['metadata']['name'] = u''
307 try:
307 try:
308 self.log.debug("Autosaving notebook %s", os_path)
308 self.log.debug("Autosaving notebook %s", os_path)
309 with io.open(os_path, 'w', encoding='utf-8') as f:
309 with io.open(os_path, 'w', encoding='utf-8') as f:
310 current.write(nb, f, u'json')
310 current.write(nb, f, u'json')
311 except Exception as e:
311 except Exception as e:
312 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
312 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
313
313
314 # Save .py script as well
314 # Save .py script as well
315 if self.save_script:
315 if self.save_script:
316 py_path = os.path.splitext(os_path)[0] + '.py'
316 py_path = os.path.splitext(os_path)[0] + '.py'
317 self.log.debug("Writing script %s", py_path)
317 self.log.debug("Writing script %s", py_path)
318 try:
318 try:
319 with io.open(py_path, 'w', encoding='utf-8') as f:
319 with io.open(py_path, 'w', encoding='utf-8') as f:
320 current.write(nb, f, u'py')
320 current.write(nb, f, u'py')
321 except Exception as e:
321 except Exception as e:
322 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
322 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
323
323
324 model = self.get_notebook(new_name, new_path, content=False)
324 model = self.get_notebook(new_name, new_path, content=False)
325 return model
325 return model
326
326
327 def update_notebook(self, model, name, path=''):
327 def update_notebook(self, model, name, path=''):
328 """Update the notebook's path and/or name"""
328 """Update the notebook's path and/or name"""
329 path = path.strip('/')
329 path = path.strip('/')
330 new_name = model.get('name', name)
330 new_name = model.get('name', name)
331 new_path = model.get('path', path).strip('/')
331 new_path = model.get('path', path).strip('/')
332 if path != new_path or name != new_name:
332 if path != new_path or name != new_name:
333 self.rename_notebook(name, path, new_name, new_path)
333 self.rename_notebook(name, path, new_name, new_path)
334 model = self.get_notebook(new_name, new_path, content=False)
334 model = self.get_notebook(new_name, new_path, content=False)
335 return model
335 return model
336
336
337 def delete_notebook(self, name, path=''):
337 def delete_notebook(self, name, path=''):
338 """Delete notebook by name and path."""
338 """Delete notebook by name and path."""
339 path = path.strip('/')
339 path = path.strip('/')
340 os_path = self._get_os_path(name, path)
340 os_path = self._get_os_path(name, path)
341 if not os.path.isfile(os_path):
341 if not os.path.isfile(os_path):
342 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
342 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
343
343
344 # clear checkpoints
344 # clear checkpoints
345 for checkpoint in self.list_checkpoints(name, path):
345 for checkpoint in self.list_checkpoints(name, path):
346 checkpoint_id = checkpoint['id']
346 checkpoint_id = checkpoint['id']
347 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
347 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
348 if os.path.isfile(cp_path):
348 if os.path.isfile(cp_path):
349 self.log.debug("Unlinking checkpoint %s", cp_path)
349 self.log.debug("Unlinking checkpoint %s", cp_path)
350 os.unlink(cp_path)
350 os.unlink(cp_path)
351
351
352 self.log.debug("Unlinking notebook %s", os_path)
352 self.log.debug("Unlinking notebook %s", os_path)
353 os.unlink(os_path)
353 os.unlink(os_path)
354
354
355 def rename_notebook(self, old_name, old_path, new_name, new_path):
355 def rename_notebook(self, old_name, old_path, new_name, new_path):
356 """Rename a notebook."""
356 """Rename a notebook."""
357 old_path = old_path.strip('/')
357 old_path = old_path.strip('/')
358 new_path = new_path.strip('/')
358 new_path = new_path.strip('/')
359 if new_name == old_name and new_path == old_path:
359 if new_name == old_name and new_path == old_path:
360 return
360 return
361
361
362 new_os_path = self._get_os_path(new_name, new_path)
362 new_os_path = self._get_os_path(new_name, new_path)
363 old_os_path = self._get_os_path(old_name, old_path)
363 old_os_path = self._get_os_path(old_name, old_path)
364
364
365 # Should we proceed with the move?
365 # Should we proceed with the move?
366 if os.path.isfile(new_os_path):
366 if os.path.isfile(new_os_path):
367 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
367 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
368 if self.save_script:
368 if self.save_script:
369 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
369 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
370 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
370 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
371 if os.path.isfile(new_py_path):
371 if os.path.isfile(new_py_path):
372 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
372 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
373
373
374 # Move the notebook file
374 # Move the notebook file
375 try:
375 try:
376 os.rename(old_os_path, new_os_path)
376 os.rename(old_os_path, new_os_path)
377 except Exception as e:
377 except Exception as e:
378 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
378 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
379
379
380 # Move the checkpoints
380 # Move the checkpoints
381 old_checkpoints = self.list_checkpoints(old_name, old_path)
381 old_checkpoints = self.list_checkpoints(old_name, old_path)
382 for cp in old_checkpoints:
382 for cp in old_checkpoints:
383 checkpoint_id = cp['id']
383 checkpoint_id = cp['id']
384 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
384 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
385 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
385 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
386 if os.path.isfile(old_cp_path):
386 if os.path.isfile(old_cp_path):
387 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
387 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
388 os.rename(old_cp_path, new_cp_path)
388 os.rename(old_cp_path, new_cp_path)
389
389
390 # Move the .py script
390 # Move the .py script
391 if self.save_script:
391 if self.save_script:
392 os.rename(old_py_path, new_py_path)
392 os.rename(old_py_path, new_py_path)
393
393
394 # Checkpoint-related utilities
394 # Checkpoint-related utilities
395
395
396 def get_checkpoint_path(self, checkpoint_id, name, path=''):
396 def get_checkpoint_path(self, checkpoint_id, name, path=''):
397 """find the path to a checkpoint"""
397 """find the path to a checkpoint"""
398 path = path.strip('/')
398 path = path.strip('/')
399 basename, _ = os.path.splitext(name)
399 basename, _ = os.path.splitext(name)
400 filename = u"{name}-{checkpoint_id}{ext}".format(
400 filename = u"{name}-{checkpoint_id}{ext}".format(
401 name=basename,
401 name=basename,
402 checkpoint_id=checkpoint_id,
402 checkpoint_id=checkpoint_id,
403 ext=self.filename_ext,
403 ext=self.filename_ext,
404 )
404 )
405 cp_path = os.path.join(path, self.checkpoint_dir, filename)
405 cp_path = os.path.join(path, self.checkpoint_dir, filename)
406 return cp_path
406 return cp_path
407
407
408 def get_checkpoint_model(self, checkpoint_id, name, path=''):
408 def get_checkpoint_model(self, checkpoint_id, name, path=''):
409 """construct the info dict for a given checkpoint"""
409 """construct the info dict for a given checkpoint"""
410 path = path.strip('/')
410 path = path.strip('/')
411 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
411 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
412 stats = os.stat(cp_path)
412 stats = os.stat(cp_path)
413 last_modified = tz.utcfromtimestamp(stats.st_mtime)
413 last_modified = tz.utcfromtimestamp(stats.st_mtime)
414 info = dict(
414 info = dict(
415 id = checkpoint_id,
415 id = checkpoint_id,
416 last_modified = last_modified,
416 last_modified = last_modified,
417 )
417 )
418 return info
418 return info
419
419
420 # public checkpoint API
420 # public checkpoint API
421
421
422 def create_checkpoint(self, name, path=''):
422 def create_checkpoint(self, name, path=''):
423 """Create a checkpoint from the current state of a notebook"""
423 """Create a checkpoint from the current state of a notebook"""
424 path = path.strip('/')
424 path = path.strip('/')
425 nb_path = self._get_os_path(name, path)
425 nb_path = self._get_os_path(name, path)
426 # only the one checkpoint ID:
426 # only the one checkpoint ID:
427 checkpoint_id = u"checkpoint"
427 checkpoint_id = u"checkpoint"
428 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
428 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
429 self.log.debug("creating checkpoint for notebook %s", name)
429 self.log.debug("creating checkpoint for notebook %s", name)
430 if not os.path.exists(self.checkpoint_dir):
430 if not os.path.exists(self.checkpoint_dir):
431 os.mkdir(self.checkpoint_dir)
431 os.mkdir(self.checkpoint_dir)
432 shutil.copy2(nb_path, cp_path)
432 shutil.copy2(nb_path, cp_path)
433
433
434 # return the checkpoint info
434 # return the checkpoint info
435 return self.get_checkpoint_model(checkpoint_id, name, path)
435 return self.get_checkpoint_model(checkpoint_id, name, path)
436
436
437 def list_checkpoints(self, name, path=''):
437 def list_checkpoints(self, name, path=''):
438 """list the checkpoints for a given notebook
438 """list the checkpoints for a given notebook
439
439
440 This notebook manager currently only supports one checkpoint per notebook.
440 This notebook manager currently only supports one checkpoint per notebook.
441 """
441 """
442 path = path.strip('/')
442 path = path.strip('/')
443 checkpoint_id = "checkpoint"
443 checkpoint_id = "checkpoint"
444 path = self.get_checkpoint_path(checkpoint_id, name, path)
444 path = self.get_checkpoint_path(checkpoint_id, name, path)
445 if not os.path.exists(path):
445 if not os.path.exists(path):
446 return []
446 return []
447 else:
447 else:
448 return [self.get_checkpoint_model(checkpoint_id, name, path)]
448 return [self.get_checkpoint_model(checkpoint_id, name, path)]
449
449
450
450
451 def restore_checkpoint(self, checkpoint_id, name, path=''):
451 def restore_checkpoint(self, checkpoint_id, name, path=''):
452 """restore a notebook to a checkpointed state"""
452 """restore a notebook to a checkpointed state"""
453 path = path.strip('/')
453 path = path.strip('/')
454 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
454 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
455 nb_path = self._get_os_path(name, path)
455 nb_path = self._get_os_path(name, path)
456 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
456 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
457 if not os.path.isfile(cp_path):
457 if not os.path.isfile(cp_path):
458 self.log.debug("checkpoint file does not exist: %s", cp_path)
458 self.log.debug("checkpoint file does not exist: %s", cp_path)
459 raise web.HTTPError(404,
459 raise web.HTTPError(404,
460 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
460 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
461 )
461 )
462 # ensure notebook is readable (never restore from an unreadable notebook)
462 # ensure notebook is readable (never restore from an unreadable notebook)
463 with io.open(cp_path, 'r', encoding='utf-8') as f:
463 with io.open(cp_path, 'r', encoding='utf-8') as f:
464 nb = current.read(f, u'json')
464 current.read(f, u'json')
465 shutil.copy2(cp_path, nb_path)
465 shutil.copy2(cp_path, nb_path)
466 self.log.debug("copying %s -> %s", cp_path, nb_path)
466 self.log.debug("copying %s -> %s", cp_path, nb_path)
467
467
468 def delete_checkpoint(self, checkpoint_id, name, path=''):
468 def delete_checkpoint(self, checkpoint_id, name, path=''):
469 """delete a notebook's checkpoint"""
469 """delete a notebook's checkpoint"""
470 path = path.strip('/')
470 path = path.strip('/')
471 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
471 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
472 if not os.path.isfile(cp_path):
472 if not os.path.isfile(cp_path):
473 raise web.HTTPError(404,
473 raise web.HTTPError(404,
474 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
474 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
475 )
475 )
476 self.log.debug("unlinking %s", cp_path)
476 self.log.debug("unlinking %s", cp_path)
477 os.unlink(cp_path)
477 os.unlink(cp_path)
478
478
479 def info_string(self):
479 def info_string(self):
480 return "Serving notebooks from local directory: %s" % self.notebook_dir
480 return "Serving notebooks from local directory: %s" % self.notebook_dir
General Comments 0
You need to be logged in to leave comments. Login now