##// END OF EJS Templates
normalize unicode notebook filenames...
MinRK -
Show More
@@ -1,346 +1,347 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 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import datetime
19 import datetime
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 from unicodedata import normalize
24
25
25 from tornado import web
26 from tornado import web
26
27
27 from .nbmanager import NotebookManager
28 from .nbmanager import NotebookManager
28 from IPython.nbformat import current
29 from IPython.nbformat import current
29 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
30 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
30
31
31 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
32 # Classes
33 # Classes
33 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
34
35
35 class FileNotebookManager(NotebookManager):
36 class FileNotebookManager(NotebookManager):
36
37
37 save_script = Bool(False, config=True,
38 save_script = Bool(False, config=True,
38 help="""Automatically create a Python script when saving the notebook.
39 help="""Automatically create a Python script when saving the notebook.
39
40
40 For easier use of import, %run and %load across notebooks, a
41 For easier use of import, %run and %load across notebooks, a
41 <notebook-name>.py script will be created next to any
42 <notebook-name>.py script will be created next to any
42 <notebook-name>.ipynb on each save. This can also be set with the
43 <notebook-name>.ipynb on each save. This can also be set with the
43 short `--script` flag.
44 short `--script` flag.
44 """
45 """
45 )
46 )
46
47
47 checkpoint_dir = Unicode(config=True,
48 checkpoint_dir = Unicode(config=True,
48 help="""The location in which to keep notebook checkpoints
49 help="""The location in which to keep notebook checkpoints
49
50
50 By default, it is notebook-dir/.ipynb_checkpoints
51 By default, it is notebook-dir/.ipynb_checkpoints
51 """
52 """
52 )
53 )
53 def _checkpoint_dir_default(self):
54 def _checkpoint_dir_default(self):
54 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
55 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
55
56
56 def _checkpoint_dir_changed(self, name, old, new):
57 def _checkpoint_dir_changed(self, name, old, new):
57 """do a bit of validation of the checkpoint dir"""
58 """do a bit of validation of the checkpoint dir"""
58 if not os.path.isabs(new):
59 if not os.path.isabs(new):
59 # If we receive a non-absolute path, make it absolute.
60 # If we receive a non-absolute path, make it absolute.
60 abs_new = os.path.abspath(new)
61 abs_new = os.path.abspath(new)
61 self.checkpoint_dir = abs_new
62 self.checkpoint_dir = abs_new
62 return
63 return
63 if os.path.exists(new) and not os.path.isdir(new):
64 if os.path.exists(new) and not os.path.isdir(new):
64 raise TraitError("checkpoint dir %r is not a directory" % new)
65 raise TraitError("checkpoint dir %r is not a directory" % new)
65 if not os.path.exists(new):
66 if not os.path.exists(new):
66 self.log.info("Creating checkpoint dir %s", new)
67 self.log.info("Creating checkpoint dir %s", new)
67 try:
68 try:
68 os.mkdir(new)
69 os.mkdir(new)
69 except:
70 except:
70 raise TraitError("Couldn't create checkpoint dir %r" % new)
71 raise TraitError("Couldn't create checkpoint dir %r" % new)
71
72
72 filename_ext = Unicode(u'.ipynb')
73 filename_ext = Unicode(u'.ipynb')
73
74
74 # Map notebook names to notebook_ids
75 # Map notebook names to notebook_ids
75 rev_mapping = Dict()
76 rev_mapping = Dict()
76
77
77 def get_notebook_names(self):
78 def get_notebook_names(self):
78 """List all notebook names in the notebook dir."""
79 """List all notebook names in the notebook dir."""
79 names = glob.glob(os.path.join(self.notebook_dir,
80 names = glob.glob(os.path.join(self.notebook_dir,
80 '*' + self.filename_ext))
81 '*' + self.filename_ext))
81 names = [os.path.splitext(os.path.basename(name))[0]
82 names = [normalize('NFC', os.path.splitext(os.path.basename(name))[0])
82 for name in names]
83 for name in names]
83 return names
84 return names
84
85
85 def list_notebooks(self):
86 def list_notebooks(self):
86 """List all notebooks in the notebook dir."""
87 """List all notebooks in the notebook dir."""
87 names = self.get_notebook_names()
88 names = self.get_notebook_names()
88
89
89 data = []
90 data = []
90 for name in names:
91 for name in names:
91 if name not in self.rev_mapping:
92 if name not in self.rev_mapping:
92 notebook_id = self.new_notebook_id(name)
93 notebook_id = self.new_notebook_id(name)
93 else:
94 else:
94 notebook_id = self.rev_mapping[name]
95 notebook_id = self.rev_mapping[name]
95 data.append(dict(notebook_id=notebook_id,name=name))
96 data.append(dict(notebook_id=notebook_id,name=name))
96 data = sorted(data, key=lambda item: item['name'])
97 data = sorted(data, key=lambda item: item['name'])
97 return data
98 return data
98
99
99 def new_notebook_id(self, name):
100 def new_notebook_id(self, name):
100 """Generate a new notebook_id for a name and store its mappings."""
101 """Generate a new notebook_id for a name and store its mappings."""
101 notebook_id = super(FileNotebookManager, self).new_notebook_id(name)
102 notebook_id = super(FileNotebookManager, self).new_notebook_id(name)
102 self.rev_mapping[name] = notebook_id
103 self.rev_mapping[name] = notebook_id
103 return notebook_id
104 return notebook_id
104
105
105 def delete_notebook_id(self, notebook_id):
106 def delete_notebook_id(self, notebook_id):
106 """Delete a notebook's id in the mapping."""
107 """Delete a notebook's id in the mapping."""
107 name = self.mapping[notebook_id]
108 name = self.mapping[notebook_id]
108 super(FileNotebookManager, self).delete_notebook_id(notebook_id)
109 super(FileNotebookManager, self).delete_notebook_id(notebook_id)
109 del self.rev_mapping[name]
110 del self.rev_mapping[name]
110
111
111 def notebook_exists(self, notebook_id):
112 def notebook_exists(self, notebook_id):
112 """Does a notebook exist?"""
113 """Does a notebook exist?"""
113 exists = super(FileNotebookManager, self).notebook_exists(notebook_id)
114 exists = super(FileNotebookManager, self).notebook_exists(notebook_id)
114 if not exists:
115 if not exists:
115 return False
116 return False
116 path = self.get_path_by_name(self.mapping[notebook_id])
117 path = self.get_path_by_name(self.mapping[notebook_id])
117 return os.path.isfile(path)
118 return os.path.isfile(path)
118
119
119 def get_name(self, notebook_id):
120 def get_name(self, notebook_id):
120 """get a notebook name, raising 404 if not found"""
121 """get a notebook name, raising 404 if not found"""
121 try:
122 try:
122 name = self.mapping[notebook_id]
123 name = self.mapping[notebook_id]
123 except KeyError:
124 except KeyError:
124 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
125 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
125 return name
126 return name
126
127
127 def get_path(self, notebook_id):
128 def get_path(self, notebook_id):
128 """Return a full path to a notebook given its notebook_id."""
129 """Return a full path to a notebook given its notebook_id."""
129 name = self.get_name(notebook_id)
130 name = self.get_name(notebook_id)
130 return self.get_path_by_name(name)
131 return self.get_path_by_name(name)
131
132
132 def get_path_by_name(self, name):
133 def get_path_by_name(self, name):
133 """Return a full path to a notebook given its name."""
134 """Return a full path to a notebook given its name."""
134 filename = name + self.filename_ext
135 filename = name + self.filename_ext
135 path = os.path.join(self.notebook_dir, filename)
136 path = os.path.join(self.notebook_dir, filename)
136 return path
137 return path
137
138
138 def read_notebook_object_from_path(self, path):
139 def read_notebook_object_from_path(self, path):
139 """read a notebook object from a path"""
140 """read a notebook object from a path"""
140 info = os.stat(path)
141 info = os.stat(path)
141 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
142 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
142 with open(path,'r') as f:
143 with open(path,'r') as f:
143 s = f.read()
144 s = f.read()
144 try:
145 try:
145 # v1 and v2 and json in the .ipynb files.
146 # v1 and v2 and json in the .ipynb files.
146 nb = current.reads(s, u'json')
147 nb = current.reads(s, u'json')
147 except Exception as e:
148 except Exception as e:
148 raise web.HTTPError(500, u'Unreadable JSON notebook: %s' % e)
149 raise web.HTTPError(500, u'Unreadable JSON notebook: %s' % e)
149 return last_modified, nb
150 return last_modified, nb
150
151
151 def read_notebook_object(self, notebook_id):
152 def read_notebook_object(self, notebook_id):
152 """Get the Notebook representation of a notebook by notebook_id."""
153 """Get the Notebook representation of a notebook by notebook_id."""
153 path = self.get_path(notebook_id)
154 path = self.get_path(notebook_id)
154 if not os.path.isfile(path):
155 if not os.path.isfile(path):
155 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
156 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
156 last_modified, nb = self.read_notebook_object_from_path(path)
157 last_modified, nb = self.read_notebook_object_from_path(path)
157 # Always use the filename as the notebook name.
158 # Always use the filename as the notebook name.
158 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
159 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
159 return last_modified, nb
160 return last_modified, nb
160
161
161 def write_notebook_object(self, nb, notebook_id=None):
162 def write_notebook_object(self, nb, notebook_id=None):
162 """Save an existing notebook object by notebook_id."""
163 """Save an existing notebook object by notebook_id."""
163 try:
164 try:
164 new_name = nb.metadata.name
165 new_name = normalize('NFC', nb.metadata.name)
165 except AttributeError:
166 except AttributeError:
166 raise web.HTTPError(400, u'Missing notebook name')
167 raise web.HTTPError(400, u'Missing notebook name')
167
168
168 if notebook_id is None:
169 if notebook_id is None:
169 notebook_id = self.new_notebook_id(new_name)
170 notebook_id = self.new_notebook_id(new_name)
170
171
171 if notebook_id not in self.mapping:
172 if notebook_id not in self.mapping:
172 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
173 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
173
174
174 old_name = self.mapping[notebook_id]
175 old_name = self.mapping[notebook_id]
175 old_checkpoints = self.list_checkpoints(notebook_id)
176 old_checkpoints = self.list_checkpoints(notebook_id)
176
177
177 path = self.get_path_by_name(new_name)
178 path = self.get_path_by_name(new_name)
178 try:
179 try:
179 self.log.debug("Autosaving notebook %s", path)
180 self.log.debug("Autosaving notebook %s", path)
180 with open(path,'w') as f:
181 with open(path,'w') as f:
181 current.write(nb, f, u'json')
182 current.write(nb, f, u'json')
182 except Exception as e:
183 except Exception as e:
183 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s' % e)
184 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s' % e)
184
185
185 # save .py script as well
186 # save .py script as well
186 if self.save_script:
187 if self.save_script:
187 pypath = os.path.splitext(path)[0] + '.py'
188 pypath = os.path.splitext(path)[0] + '.py'
188 self.log.debug("Writing script %s", pypath)
189 self.log.debug("Writing script %s", pypath)
189 try:
190 try:
190 with io.open(pypath,'w', encoding='utf-8') as f:
191 with io.open(pypath,'w', encoding='utf-8') as f:
191 current.write(nb, f, u'py')
192 current.write(nb, f, u'py')
192 except Exception as e:
193 except Exception as e:
193 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
194 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
194
195
195 # remove old files if the name changed
196 # remove old files if the name changed
196 if old_name != new_name:
197 if old_name != new_name:
197 # update mapping
198 # update mapping
198 self.mapping[notebook_id] = new_name
199 self.mapping[notebook_id] = new_name
199 self.rev_mapping[new_name] = notebook_id
200 self.rev_mapping[new_name] = notebook_id
200 del self.rev_mapping[old_name]
201 del self.rev_mapping[old_name]
201
202
202 # remove renamed original, if it exists
203 # remove renamed original, if it exists
203 old_path = self.get_path_by_name(old_name)
204 old_path = self.get_path_by_name(old_name)
204 if os.path.isfile(old_path):
205 if os.path.isfile(old_path):
205 self.log.debug("unlinking notebook %s", old_path)
206 self.log.debug("unlinking notebook %s", old_path)
206 os.unlink(old_path)
207 os.unlink(old_path)
207
208
208 # cleanup old script, if it exists
209 # cleanup old script, if it exists
209 if self.save_script:
210 if self.save_script:
210 old_pypath = os.path.splitext(old_path)[0] + '.py'
211 old_pypath = os.path.splitext(old_path)[0] + '.py'
211 if os.path.isfile(old_pypath):
212 if os.path.isfile(old_pypath):
212 self.log.debug("unlinking script %s", old_pypath)
213 self.log.debug("unlinking script %s", old_pypath)
213 os.unlink(old_pypath)
214 os.unlink(old_pypath)
214
215
215 # rename checkpoints to follow file
216 # rename checkpoints to follow file
216 for cp in old_checkpoints:
217 for cp in old_checkpoints:
217 checkpoint_id = cp['checkpoint_id']
218 checkpoint_id = cp['checkpoint_id']
218 old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
219 old_cp_path = self.get_checkpoint_path_by_name(old_name, checkpoint_id)
219 new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
220 new_cp_path = self.get_checkpoint_path_by_name(new_name, checkpoint_id)
220 if os.path.isfile(old_cp_path):
221 if os.path.isfile(old_cp_path):
221 self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
222 self.log.debug("renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
222 os.rename(old_cp_path, new_cp_path)
223 os.rename(old_cp_path, new_cp_path)
223
224
224 return notebook_id
225 return notebook_id
225
226
226 def delete_notebook(self, notebook_id):
227 def delete_notebook(self, notebook_id):
227 """Delete notebook by notebook_id."""
228 """Delete notebook by notebook_id."""
228 nb_path = self.get_path(notebook_id)
229 nb_path = self.get_path(notebook_id)
229 if not os.path.isfile(nb_path):
230 if not os.path.isfile(nb_path):
230 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
231 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
231
232
232 # clear checkpoints
233 # clear checkpoints
233 for checkpoint in self.list_checkpoints(notebook_id):
234 for checkpoint in self.list_checkpoints(notebook_id):
234 checkpoint_id = checkpoint['checkpoint_id']
235 checkpoint_id = checkpoint['checkpoint_id']
235 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
236 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
236 self.log.debug(path)
237 self.log.debug(path)
237 if os.path.isfile(path):
238 if os.path.isfile(path):
238 self.log.debug("unlinking checkpoint %s", path)
239 self.log.debug("unlinking checkpoint %s", path)
239 os.unlink(path)
240 os.unlink(path)
240
241
241 self.log.debug("unlinking notebook %s", nb_path)
242 self.log.debug("unlinking notebook %s", nb_path)
242 os.unlink(nb_path)
243 os.unlink(nb_path)
243 self.delete_notebook_id(notebook_id)
244 self.delete_notebook_id(notebook_id)
244
245
245 def increment_filename(self, basename):
246 def increment_filename(self, basename):
246 """Return a non-used filename of the form basename<int>.
247 """Return a non-used filename of the form basename<int>.
247
248
248 This searches through the filenames (basename0, basename1, ...)
249 This searches through the filenames (basename0, basename1, ...)
249 until is find one that is not already being used. It is used to
250 until is find one that is not already being used. It is used to
250 create Untitled and Copy names that are unique.
251 create Untitled and Copy names that are unique.
251 """
252 """
252 i = 0
253 i = 0
253 while True:
254 while True:
254 name = u'%s%i' % (basename,i)
255 name = u'%s%i' % (basename,i)
255 path = self.get_path_by_name(name)
256 path = self.get_path_by_name(name)
256 if not os.path.isfile(path):
257 if not os.path.isfile(path):
257 break
258 break
258 else:
259 else:
259 i = i+1
260 i = i+1
260 return name
261 return name
261
262
262 # Checkpoint-related utilities
263 # Checkpoint-related utilities
263
264
264 def get_checkpoint_path_by_name(self, name, checkpoint_id):
265 def get_checkpoint_path_by_name(self, name, checkpoint_id):
265 """Return a full path to a notebook checkpoint, given its name and checkpoint id."""
266 """Return a full path to a notebook checkpoint, given its name and checkpoint id."""
266 filename = "{name}-{checkpoint_id}{ext}".format(
267 filename = u"{name}-{checkpoint_id}{ext}".format(
267 name=name,
268 name=name,
268 checkpoint_id=checkpoint_id,
269 checkpoint_id=checkpoint_id,
269 ext=self.filename_ext,
270 ext=self.filename_ext,
270 )
271 )
271 path = os.path.join(self.checkpoint_dir, filename)
272 path = os.path.join(self.checkpoint_dir, filename)
272 return path
273 return path
273
274
274 def get_checkpoint_path(self, notebook_id, checkpoint_id):
275 def get_checkpoint_path(self, notebook_id, checkpoint_id):
275 """find the path to a checkpoint"""
276 """find the path to a checkpoint"""
276 name = self.get_name(notebook_id)
277 name = self.get_name(notebook_id)
277 return self.get_checkpoint_path_by_name(name, checkpoint_id)
278 return self.get_checkpoint_path_by_name(name, checkpoint_id)
278
279
279 def get_checkpoint_info(self, notebook_id, checkpoint_id):
280 def get_checkpoint_info(self, notebook_id, checkpoint_id):
280 """construct the info dict for a given checkpoint"""
281 """construct the info dict for a given checkpoint"""
281 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
282 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
282 stats = os.stat(path)
283 stats = os.stat(path)
283 last_modified = datetime.datetime.utcfromtimestamp(stats.st_mtime)
284 last_modified = datetime.datetime.utcfromtimestamp(stats.st_mtime)
284 info = dict(
285 info = dict(
285 checkpoint_id = checkpoint_id,
286 checkpoint_id = checkpoint_id,
286 last_modified = last_modified,
287 last_modified = last_modified,
287 )
288 )
288
289
289 return info
290 return info
290
291
291 # public checkpoint API
292 # public checkpoint API
292
293
293 def create_checkpoint(self, notebook_id):
294 def create_checkpoint(self, notebook_id):
294 """Create a checkpoint from the current state of a notebook"""
295 """Create a checkpoint from the current state of a notebook"""
295 nb_path = self.get_path(notebook_id)
296 nb_path = self.get_path(notebook_id)
296 # only the one checkpoint ID:
297 # only the one checkpoint ID:
297 checkpoint_id = "checkpoint"
298 checkpoint_id = u"checkpoint"
298 cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
299 cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
299 self.log.debug("creating checkpoint for notebook %s", notebook_id)
300 self.log.debug("creating checkpoint for notebook %s", notebook_id)
300 if not os.path.exists(self.checkpoint_dir):
301 if not os.path.exists(self.checkpoint_dir):
301 os.mkdir(self.checkpoint_dir)
302 os.mkdir(self.checkpoint_dir)
302 shutil.copy2(nb_path, cp_path)
303 shutil.copy2(nb_path, cp_path)
303
304
304 # return the checkpoint info
305 # return the checkpoint info
305 return self.get_checkpoint_info(notebook_id, checkpoint_id)
306 return self.get_checkpoint_info(notebook_id, checkpoint_id)
306
307
307 def list_checkpoints(self, notebook_id):
308 def list_checkpoints(self, notebook_id):
308 """list the checkpoints for a given notebook
309 """list the checkpoints for a given notebook
309
310
310 This notebook manager currently only supports one checkpoint per notebook.
311 This notebook manager currently only supports one checkpoint per notebook.
311 """
312 """
312 checkpoint_id = "checkpoint"
313 checkpoint_id = u"checkpoint"
313 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
314 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
314 if not os.path.exists(path):
315 if not os.path.exists(path):
315 return []
316 return []
316 else:
317 else:
317 return [self.get_checkpoint_info(notebook_id, checkpoint_id)]
318 return [self.get_checkpoint_info(notebook_id, checkpoint_id)]
318
319
319
320
320 def restore_checkpoint(self, notebook_id, checkpoint_id):
321 def restore_checkpoint(self, notebook_id, checkpoint_id):
321 """restore a notebook to a checkpointed state"""
322 """restore a notebook to a checkpointed state"""
322 self.log.info("restoring Notebook %s from checkpoint %s", notebook_id, checkpoint_id)
323 self.log.info("restoring Notebook %s from checkpoint %s", notebook_id, checkpoint_id)
323 nb_path = self.get_path(notebook_id)
324 nb_path = self.get_path(notebook_id)
324 cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
325 cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
325 if not os.path.isfile(cp_path):
326 if not os.path.isfile(cp_path):
326 self.log.debug("checkpoint file does not exist: %s", cp_path)
327 self.log.debug("checkpoint file does not exist: %s", cp_path)
327 raise web.HTTPError(404,
328 raise web.HTTPError(404,
328 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
329 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
329 )
330 )
330 # ensure notebook is readable (never restore from an unreadable notebook)
331 # ensure notebook is readable (never restore from an unreadable notebook)
331 last_modified, nb = self.read_notebook_object_from_path(cp_path)
332 last_modified, nb = self.read_notebook_object_from_path(cp_path)
332 shutil.copy2(cp_path, nb_path)
333 shutil.copy2(cp_path, nb_path)
333 self.log.debug("copying %s -> %s", cp_path, nb_path)
334 self.log.debug("copying %s -> %s", cp_path, nb_path)
334
335
335 def delete_checkpoint(self, notebook_id, checkpoint_id):
336 def delete_checkpoint(self, notebook_id, checkpoint_id):
336 """delete a notebook's checkpoint"""
337 """delete a notebook's checkpoint"""
337 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
338 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
338 if not os.path.isfile(path):
339 if not os.path.isfile(path):
339 raise web.HTTPError(404,
340 raise web.HTTPError(404,
340 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
341 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
341 )
342 )
342 self.log.debug("unlinking %s", path)
343 self.log.debug("unlinking %s", path)
343 os.unlink(path)
344 os.unlink(path)
344
345
345 def info_string(self):
346 def info_string(self):
346 return "Serving notebooks from local directory: %s" % self.notebook_dir
347 return "Serving notebooks from local directory: %s" % self.notebook_dir
General Comments 0
You need to be logged in to leave comments. Login now