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