##// END OF EJS Templates
Specify encoding for loading and saving notebooks.
Thomas Kluyver -
Show More
@@ -1,414 +1,414 b''
1 """A notebook manager that uses the local file system for storage.
1 """A notebook manager that uses the local file system for storage.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 * Zach Sailer
6 * Zach Sailer
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import datetime
20 import datetime
21 import io
21 import io
22 import os
22 import os
23 import glob
23 import glob
24 import shutil
24 import shutil
25
25
26 from unicodedata import normalize
26 from unicodedata import normalize
27
27
28 from tornado import web
28 from tornado import web
29
29
30 from .nbmanager import NotebookManager
30 from .nbmanager import NotebookManager
31 from IPython.nbformat import current
31 from IPython.nbformat import current
32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
32 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
33 from IPython.utils import tz
33 from IPython.utils import tz
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Classes
36 # Classes
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 class FileNotebookManager(NotebookManager):
39 class FileNotebookManager(NotebookManager):
40
40
41 save_script = Bool(False, config=True,
41 save_script = Bool(False, config=True,
42 help="""Automatically create a Python script when saving the notebook.
42 help="""Automatically create a Python script when saving the notebook.
43
43
44 For easier use of import, %run and %load across notebooks, a
44 For easier use of import, %run and %load across notebooks, a
45 <notebook-name>.py script will be created next to any
45 <notebook-name>.py script will be created next to any
46 <notebook-name>.ipynb on each save. This can also be set with the
46 <notebook-name>.ipynb on each save. This can also be set with the
47 short `--script` flag.
47 short `--script` flag.
48 """
48 """
49 )
49 )
50
50
51 checkpoint_dir = Unicode(config=True,
51 checkpoint_dir = Unicode(config=True,
52 help="""The location in which to keep notebook checkpoints
52 help="""The location in which to keep notebook checkpoints
53
53
54 By default, it is notebook-dir/.ipynb_checkpoints
54 By default, it is notebook-dir/.ipynb_checkpoints
55 """
55 """
56 )
56 )
57 def _checkpoint_dir_default(self):
57 def _checkpoint_dir_default(self):
58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
58 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
59
59
60 def _checkpoint_dir_changed(self, name, old, new):
60 def _checkpoint_dir_changed(self, name, old, new):
61 """do a bit of validation of the checkpoint dir"""
61 """do a bit of validation of the checkpoint dir"""
62 if not os.path.isabs(new):
62 if not os.path.isabs(new):
63 # If we receive a non-absolute path, make it absolute.
63 # If we receive a non-absolute path, make it absolute.
64 abs_new = os.path.abspath(new)
64 abs_new = os.path.abspath(new)
65 self.checkpoint_dir = abs_new
65 self.checkpoint_dir = abs_new
66 return
66 return
67 if os.path.exists(new) and not os.path.isdir(new):
67 if os.path.exists(new) and not os.path.isdir(new):
68 raise TraitError("checkpoint dir %r is not a directory" % new)
68 raise TraitError("checkpoint dir %r is not a directory" % new)
69 if not os.path.exists(new):
69 if not os.path.exists(new):
70 self.log.info("Creating checkpoint dir %s", new)
70 self.log.info("Creating checkpoint dir %s", new)
71 try:
71 try:
72 os.mkdir(new)
72 os.mkdir(new)
73 except:
73 except:
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
75
75
76 def get_notebook_names(self, path=''):
76 def get_notebook_names(self, path=''):
77 """List all notebook names in the notebook dir and path."""
77 """List all notebook names in the notebook dir and path."""
78 path = path.strip('/')
78 path = path.strip('/')
79 if not os.path.isdir(self.get_os_path(path=path)):
79 if not os.path.isdir(self.get_os_path(path=path)):
80 raise web.HTTPError(404, 'Directory not found: ' + path)
80 raise web.HTTPError(404, 'Directory not found: ' + path)
81 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
81 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
82 names = [os.path.basename(name)
82 names = [os.path.basename(name)
83 for name in names]
83 for name in names]
84 return names
84 return names
85
85
86 def increment_filename(self, basename, path=''):
86 def increment_filename(self, basename, path=''):
87 """Return a non-used filename of the form basename<int>."""
87 """Return a non-used filename of the form basename<int>."""
88 path = path.strip('/')
88 path = path.strip('/')
89 i = 0
89 i = 0
90 while True:
90 while True:
91 name = u'%s%i.ipynb' % (basename,i)
91 name = u'%s%i.ipynb' % (basename,i)
92 os_path = self.get_os_path(name, path)
92 os_path = self.get_os_path(name, path)
93 if not os.path.isfile(os_path):
93 if not os.path.isfile(os_path):
94 break
94 break
95 else:
95 else:
96 i = i+1
96 i = i+1
97 return name
97 return name
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 get_os_path(self, name=None, path=''):
117 def get_os_path(self, name=None, path=''):
118 """Given a notebook name and a URL path, return its file system
118 """Given a notebook name and a URL path, return its file system
119 path.
119 path.
120
120
121 Parameters
121 Parameters
122 ----------
122 ----------
123 name : string
123 name : string
124 The name of a notebook file with the .ipynb extension
124 The name of a notebook file with the .ipynb extension
125 path : string
125 path : string
126 The relative URL path (with '/' as separator) to the named
126 The relative URL path (with '/' as separator) to the named
127 notebook.
127 notebook.
128
128
129 Returns
129 Returns
130 -------
130 -------
131 path : string
131 path : string
132 A file system path that combines notebook_dir (location where
132 A file system path that combines notebook_dir (location where
133 server started), the relative path, and the filename with the
133 server started), the relative path, and the filename with the
134 current operating system's url.
134 current operating system's url.
135 """
135 """
136 parts = path.strip('/').split('/')
136 parts = path.strip('/').split('/')
137 parts = [p for p in parts if p != ''] # remove duplicate splits
137 parts = [p for p in parts if p != ''] # remove duplicate splits
138 if name is not None:
138 if name is not None:
139 parts.append(name)
139 parts.append(name)
140 path = os.path.join(self.notebook_dir, *parts)
140 path = os.path.join(self.notebook_dir, *parts)
141 return path
141 return path
142
142
143 def notebook_exists(self, name, path=''):
143 def notebook_exists(self, name, path=''):
144 """Returns a True if the notebook exists. Else, returns False.
144 """Returns a True if the notebook exists. Else, returns False.
145
145
146 Parameters
146 Parameters
147 ----------
147 ----------
148 name : string
148 name : string
149 The name of the notebook you are checking.
149 The name of the notebook you are checking.
150 path : string
150 path : string
151 The relative path to the notebook (with '/' as separator)
151 The relative path to the notebook (with '/' as separator)
152
152
153 Returns
153 Returns
154 -------
154 -------
155 bool
155 bool
156 """
156 """
157 path = path.strip('/')
157 path = path.strip('/')
158 nbpath = self.get_os_path(name, path=path)
158 nbpath = self.get_os_path(name, path=path)
159 return os.path.isfile(nbpath)
159 return os.path.isfile(nbpath)
160
160
161 def list_notebooks(self, path):
161 def list_notebooks(self, path):
162 """Returns a list of dictionaries that are the standard model
162 """Returns a list of dictionaries that are the standard model
163 for all notebooks in the relative 'path'.
163 for all notebooks in the relative 'path'.
164
164
165 Parameters
165 Parameters
166 ----------
166 ----------
167 path : str
167 path : str
168 the URL path that describes the relative path for the
168 the URL path that describes the relative path for the
169 listed notebooks
169 listed notebooks
170
170
171 Returns
171 Returns
172 -------
172 -------
173 notebooks : list of dicts
173 notebooks : list of dicts
174 a list of the notebook models without 'content'
174 a list of the notebook models without 'content'
175 """
175 """
176 path = path.strip('/')
176 path = path.strip('/')
177 notebook_names = self.get_notebook_names(path)
177 notebook_names = self.get_notebook_names(path)
178 notebooks = []
178 notebooks = []
179 for name in notebook_names:
179 for name in notebook_names:
180 model = self.get_notebook_model(name, path, content=False)
180 model = self.get_notebook_model(name, path, content=False)
181 notebooks.append(model)
181 notebooks.append(model)
182 notebooks = sorted(notebooks, key=lambda item: item['name'])
182 notebooks = sorted(notebooks, key=lambda item: item['name'])
183 return notebooks
183 return notebooks
184
184
185 def get_notebook_model(self, name, path='', content=True):
185 def get_notebook_model(self, name, path='', content=True):
186 """ Takes a path and name for a notebook and returns it's model
186 """ Takes a path and name for a notebook and returns it's model
187
187
188 Parameters
188 Parameters
189 ----------
189 ----------
190 name : str
190 name : str
191 the name of the notebook
191 the name of the notebook
192 path : str
192 path : str
193 the URL path that describes the relative path for
193 the URL path that describes the relative path for
194 the notebook
194 the notebook
195
195
196 Returns
196 Returns
197 -------
197 -------
198 model : dict
198 model : dict
199 the notebook model. If contents=True, returns the 'contents'
199 the notebook model. If contents=True, returns the 'contents'
200 dict in the model as well.
200 dict in the model as well.
201 """
201 """
202 path = path.strip('/')
202 path = path.strip('/')
203 if not self.notebook_exists(name=name, path=path):
203 if not self.notebook_exists(name=name, path=path):
204 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
204 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
205 os_path = self.get_os_path(name, path)
205 os_path = self.get_os_path(name, path)
206 info = os.stat(os_path)
206 info = os.stat(os_path)
207 last_modified = tz.utcfromtimestamp(info.st_mtime)
207 last_modified = tz.utcfromtimestamp(info.st_mtime)
208 created = tz.utcfromtimestamp(info.st_ctime)
208 created = tz.utcfromtimestamp(info.st_ctime)
209 # Create the notebook model.
209 # Create the notebook model.
210 model ={}
210 model ={}
211 model['name'] = name
211 model['name'] = name
212 model['path'] = path
212 model['path'] = path
213 model['last_modified'] = last_modified
213 model['last_modified'] = last_modified
214 model['created'] = last_modified
214 model['created'] = last_modified
215 if content is True:
215 if content is True:
216 with open(os_path, 'r') as f:
216 with io.open(os_path, 'r', encoding='utf-8') as f:
217 try:
217 try:
218 nb = current.read(f, u'json')
218 nb = current.read(f, u'json')
219 except Exception as e:
219 except Exception as e:
220 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
220 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
221 model['content'] = nb
221 model['content'] = nb
222 return model
222 return model
223
223
224 def save_notebook_model(self, model, name='', path=''):
224 def save_notebook_model(self, model, name='', path=''):
225 """Save the notebook model and return the model with no content."""
225 """Save the notebook model and return the model with no content."""
226 path = path.strip('/')
226 path = path.strip('/')
227
227
228 if 'content' not in model:
228 if 'content' not in model:
229 raise web.HTTPError(400, u'No notebook JSON data provided')
229 raise web.HTTPError(400, u'No notebook JSON data provided')
230
230
231 new_path = model.get('path', path).strip('/')
231 new_path = model.get('path', path).strip('/')
232 new_name = model.get('name', name)
232 new_name = model.get('name', name)
233
233
234 if path != new_path or name != new_name:
234 if path != new_path or name != new_name:
235 self.rename_notebook(name, path, new_name, new_path)
235 self.rename_notebook(name, path, new_name, new_path)
236
236
237 # Save the notebook file
237 # Save the notebook file
238 os_path = self.get_os_path(new_name, new_path)
238 os_path = self.get_os_path(new_name, new_path)
239 nb = current.to_notebook_json(model['content'])
239 nb = current.to_notebook_json(model['content'])
240 if 'name' in nb['metadata']:
240 if 'name' in nb['metadata']:
241 nb['metadata']['name'] = u''
241 nb['metadata']['name'] = u''
242 try:
242 try:
243 self.log.debug("Autosaving notebook %s", os_path)
243 self.log.debug("Autosaving notebook %s", os_path)
244 with open(os_path, 'w') as f:
244 with io.open(os_path, 'w', encoding='utf-8') as f:
245 current.write(nb, f, u'json')
245 current.write(nb, f, u'json')
246 except Exception as e:
246 except Exception as e:
247 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
247 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
248
248
249 # Save .py script as well
249 # Save .py script as well
250 if self.save_script:
250 if self.save_script:
251 py_path = os.path.splitext(os_path)[0] + '.py'
251 py_path = os.path.splitext(os_path)[0] + '.py'
252 self.log.debug("Writing script %s", py_path)
252 self.log.debug("Writing script %s", py_path)
253 try:
253 try:
254 with io.open(py_path, 'w', encoding='utf-8') as f:
254 with io.open(py_path, 'w', encoding='utf-8') as f:
255 current.write(model, f, u'py')
255 current.write(model, f, u'py')
256 except Exception as e:
256 except Exception as e:
257 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
257 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
258
258
259 model = self.get_notebook_model(new_name, new_path, content=False)
259 model = self.get_notebook_model(new_name, new_path, content=False)
260 return model
260 return model
261
261
262 def update_notebook_model(self, model, name, path=''):
262 def update_notebook_model(self, model, name, path=''):
263 """Update the notebook's path and/or name"""
263 """Update the notebook's path and/or name"""
264 path = path.strip('/')
264 path = path.strip('/')
265 new_name = model.get('name', name)
265 new_name = model.get('name', name)
266 new_path = model.get('path', path).strip('/')
266 new_path = model.get('path', path).strip('/')
267 if path != new_path or name != new_name:
267 if path != new_path or name != new_name:
268 self.rename_notebook(name, path, new_name, new_path)
268 self.rename_notebook(name, path, new_name, new_path)
269 model = self.get_notebook_model(new_name, new_path, content=False)
269 model = self.get_notebook_model(new_name, new_path, content=False)
270 return model
270 return model
271
271
272 def delete_notebook_model(self, name, path=''):
272 def delete_notebook_model(self, name, path=''):
273 """Delete notebook by name and path."""
273 """Delete notebook by name and path."""
274 path = path.strip('/')
274 path = path.strip('/')
275 os_path = self.get_os_path(name, path)
275 os_path = self.get_os_path(name, path)
276 if not os.path.isfile(os_path):
276 if not os.path.isfile(os_path):
277 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
277 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
278
278
279 # clear checkpoints
279 # clear checkpoints
280 for checkpoint in self.list_checkpoints(name, path):
280 for checkpoint in self.list_checkpoints(name, path):
281 checkpoint_id = checkpoint['checkpoint_id']
281 checkpoint_id = checkpoint['checkpoint_id']
282 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
282 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
283 if os.path.isfile(cp_path):
283 if os.path.isfile(cp_path):
284 self.log.debug("Unlinking checkpoint %s", cp_path)
284 self.log.debug("Unlinking checkpoint %s", cp_path)
285 os.unlink(cp_path)
285 os.unlink(cp_path)
286
286
287 self.log.debug("Unlinking notebook %s", os_path)
287 self.log.debug("Unlinking notebook %s", os_path)
288 os.unlink(os_path)
288 os.unlink(os_path)
289
289
290 def rename_notebook(self, old_name, old_path, new_name, new_path):
290 def rename_notebook(self, old_name, old_path, new_name, new_path):
291 """Rename a notebook."""
291 """Rename a notebook."""
292 old_path = old_path.strip('/')
292 old_path = old_path.strip('/')
293 new_path = new_path.strip('/')
293 new_path = new_path.strip('/')
294 if new_name == old_name and new_path == old_path:
294 if new_name == old_name and new_path == old_path:
295 return
295 return
296
296
297 new_os_path = self.get_os_path(new_name, new_path)
297 new_os_path = self.get_os_path(new_name, new_path)
298 old_os_path = self.get_os_path(old_name, old_path)
298 old_os_path = self.get_os_path(old_name, old_path)
299
299
300 # Should we proceed with the move?
300 # Should we proceed with the move?
301 if os.path.isfile(new_os_path):
301 if os.path.isfile(new_os_path):
302 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
302 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
303 if self.save_script:
303 if self.save_script:
304 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
304 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
305 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
305 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
306 if os.path.isfile(new_py_path):
306 if os.path.isfile(new_py_path):
307 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
307 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
308
308
309 # Move the notebook file
309 # Move the notebook file
310 try:
310 try:
311 os.rename(old_os_path, new_os_path)
311 os.rename(old_os_path, new_os_path)
312 except Exception as e:
312 except Exception as e:
313 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
313 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
314
314
315 # Move the checkpoints
315 # Move the checkpoints
316 old_checkpoints = self.list_checkpoints(old_name, old_path)
316 old_checkpoints = self.list_checkpoints(old_name, old_path)
317 for cp in old_checkpoints:
317 for cp in old_checkpoints:
318 checkpoint_id = cp['checkpoint_id']
318 checkpoint_id = cp['checkpoint_id']
319 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
319 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
320 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
320 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
321 if os.path.isfile(old_cp_path):
321 if os.path.isfile(old_cp_path):
322 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
322 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
323 os.rename(old_cp_path, new_cp_path)
323 os.rename(old_cp_path, new_cp_path)
324
324
325 # Move the .py script
325 # Move the .py script
326 if self.save_script:
326 if self.save_script:
327 os.rename(old_py_path, new_py_path)
327 os.rename(old_py_path, new_py_path)
328
328
329 # Checkpoint-related utilities
329 # Checkpoint-related utilities
330
330
331 def get_checkpoint_path(self, checkpoint_id, name, path=''):
331 def get_checkpoint_path(self, checkpoint_id, name, path=''):
332 """find the path to a checkpoint"""
332 """find the path to a checkpoint"""
333 path = path.strip('/')
333 path = path.strip('/')
334 filename = u"{name}-{checkpoint_id}{ext}".format(
334 filename = u"{name}-{checkpoint_id}{ext}".format(
335 name=name,
335 name=name,
336 checkpoint_id=checkpoint_id,
336 checkpoint_id=checkpoint_id,
337 ext=self.filename_ext,
337 ext=self.filename_ext,
338 )
338 )
339 cp_path = os.path.join(path, self.checkpoint_dir, filename)
339 cp_path = os.path.join(path, self.checkpoint_dir, filename)
340 return cp_path
340 return cp_path
341
341
342 def get_checkpoint_model(self, checkpoint_id, name, path=''):
342 def get_checkpoint_model(self, checkpoint_id, name, path=''):
343 """construct the info dict for a given checkpoint"""
343 """construct the info dict for a given checkpoint"""
344 path = path.strip('/')
344 path = path.strip('/')
345 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
345 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
346 stats = os.stat(cp_path)
346 stats = os.stat(cp_path)
347 last_modified = tz.utcfromtimestamp(stats.st_mtime)
347 last_modified = tz.utcfromtimestamp(stats.st_mtime)
348 info = dict(
348 info = dict(
349 checkpoint_id = checkpoint_id,
349 checkpoint_id = checkpoint_id,
350 last_modified = last_modified,
350 last_modified = last_modified,
351 )
351 )
352 return info
352 return info
353
353
354 # public checkpoint API
354 # public checkpoint API
355
355
356 def create_checkpoint(self, name, path=''):
356 def create_checkpoint(self, name, path=''):
357 """Create a checkpoint from the current state of a notebook"""
357 """Create a checkpoint from the current state of a notebook"""
358 path = path.strip('/')
358 path = path.strip('/')
359 nb_path = self.get_os_path(name, path)
359 nb_path = self.get_os_path(name, path)
360 # only the one checkpoint ID:
360 # only the one checkpoint ID:
361 checkpoint_id = u"checkpoint"
361 checkpoint_id = u"checkpoint"
362 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
362 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
363 self.log.debug("creating checkpoint for notebook %s", name)
363 self.log.debug("creating checkpoint for notebook %s", name)
364 if not os.path.exists(self.checkpoint_dir):
364 if not os.path.exists(self.checkpoint_dir):
365 os.mkdir(self.checkpoint_dir)
365 os.mkdir(self.checkpoint_dir)
366 shutil.copy2(nb_path, cp_path)
366 shutil.copy2(nb_path, cp_path)
367
367
368 # return the checkpoint info
368 # return the checkpoint info
369 return self.get_checkpoint_model(checkpoint_id, name, path)
369 return self.get_checkpoint_model(checkpoint_id, name, path)
370
370
371 def list_checkpoints(self, name, path=''):
371 def list_checkpoints(self, name, path=''):
372 """list the checkpoints for a given notebook
372 """list the checkpoints for a given notebook
373
373
374 This notebook manager currently only supports one checkpoint per notebook.
374 This notebook manager currently only supports one checkpoint per notebook.
375 """
375 """
376 path = path.strip('/')
376 path = path.strip('/')
377 checkpoint_id = "checkpoint"
377 checkpoint_id = "checkpoint"
378 path = self.get_checkpoint_path(checkpoint_id, name, path)
378 path = self.get_checkpoint_path(checkpoint_id, name, path)
379 if not os.path.exists(path):
379 if not os.path.exists(path):
380 return []
380 return []
381 else:
381 else:
382 return [self.get_checkpoint_model(checkpoint_id, name, path)]
382 return [self.get_checkpoint_model(checkpoint_id, name, path)]
383
383
384
384
385 def restore_checkpoint(self, checkpoint_id, name, path=''):
385 def restore_checkpoint(self, checkpoint_id, name, path=''):
386 """restore a notebook to a checkpointed state"""
386 """restore a notebook to a checkpointed state"""
387 path = path.strip('/')
387 path = path.strip('/')
388 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
388 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
389 nb_path = self.get_os_path(name, path)
389 nb_path = self.get_os_path(name, path)
390 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
390 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
391 if not os.path.isfile(cp_path):
391 if not os.path.isfile(cp_path):
392 self.log.debug("checkpoint file does not exist: %s", cp_path)
392 self.log.debug("checkpoint file does not exist: %s", cp_path)
393 raise web.HTTPError(404,
393 raise web.HTTPError(404,
394 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
394 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
395 )
395 )
396 # ensure notebook is readable (never restore from an unreadable notebook)
396 # ensure notebook is readable (never restore from an unreadable notebook)
397 with open(cp_path, 'r') as f:
397 with io.open(cp_path, 'r', encoding='utf-8') as f:
398 nb = current.read(f, u'json')
398 nb = current.read(f, u'json')
399 shutil.copy2(cp_path, nb_path)
399 shutil.copy2(cp_path, nb_path)
400 self.log.debug("copying %s -> %s", cp_path, nb_path)
400 self.log.debug("copying %s -> %s", cp_path, nb_path)
401
401
402 def delete_checkpoint(self, checkpoint_id, name, path=''):
402 def delete_checkpoint(self, checkpoint_id, name, path=''):
403 """delete a notebook's checkpoint"""
403 """delete a notebook's checkpoint"""
404 path = path.strip('/')
404 path = path.strip('/')
405 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
405 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
406 if not os.path.isfile(cp_path):
406 if not os.path.isfile(cp_path):
407 raise web.HTTPError(404,
407 raise web.HTTPError(404,
408 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
408 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
409 )
409 )
410 self.log.debug("unlinking %s", cp_path)
410 self.log.debug("unlinking %s", cp_path)
411 os.unlink(cp_path)
411 os.unlink(cp_path)
412
412
413 def info_string(self):
413 def info_string(self):
414 return "Serving notebooks from local directory: %s" % self.notebook_dir
414 return "Serving notebooks from local directory: %s" % self.notebook_dir
General Comments 0
You need to be logged in to leave comments. Login now