##// END OF EJS Templates
add checkpoint API to FileNBManager
MinRK -
Show More
@@ -20,6 +20,7 b' import datetime'
20 import io
20 import io
21 import os
21 import os
22 import glob
22 import glob
23 import shutil
23
24
24 from tornado import web
25 from tornado import web
25
26
@@ -43,11 +44,36 b' class FileNotebookManager(NotebookManager):'
43 """
44 """
44 )
45 )
45
46
47 checkpoint_dir = Unicode(config=True,
48 help="""The location in which to keep notebook checkpoints
49
50 By default, it is notebook-dir/.ipynb_checkpoints
51 """
52 )
53 def _checkpoint_dir_default(self):
54 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
55
56 def _checkpoint_dir_changed(self, name, old, new):
57 """do a bit of validation of the checkpoint dir"""
58 if not os.path.isabs(new):
59 # If we receive a non-absolute path, make it absolute.
60 abs_new = os.path.abspath(new)
61 self.checkpoint_dir = abs_new
62 return
63 if os.path.exists(new) and not os.path.isdir(new):
64 raise TraitError("checkpoint dir %r is not a directory" % new)
65 if not os.path.exists(new):
66 self.log.info("Creating checkpoint dir %s", new)
67 try:
68 os.mkdir(new)
69 except:
70 raise TraitError("Couldn't create checkpoint dir %r" % new)
71
46 filename_ext = Unicode(u'.ipynb')
72 filename_ext = Unicode(u'.ipynb')
47
73
48 # Map notebook names to notebook_ids
74 # Map notebook names to notebook_ids
49 rev_mapping = Dict()
75 rev_mapping = Dict()
50
76
51 def get_notebook_names(self):
77 def get_notebook_names(self):
52 """List all notebook names in the notebook dir."""
78 """List all notebook names in the notebook dir."""
53 names = glob.glob(os.path.join(self.notebook_dir,
79 names = glob.glob(os.path.join(self.notebook_dir,
@@ -89,26 +115,28 b' class FileNotebookManager(NotebookManager):'
89 return False
115 return False
90 path = self.get_path_by_name(self.mapping[notebook_id])
116 path = self.get_path_by_name(self.mapping[notebook_id])
91 return os.path.isfile(path)
117 return os.path.isfile(path)
92
118
93 def find_path(self, notebook_id):
119 def get_name(self, notebook_id):
94 """Return a full path to a notebook given its notebook_id."""
120 """get a notebook name, raising 404 if not found"""
95 try:
121 try:
96 name = self.mapping[notebook_id]
122 name = self.mapping[notebook_id]
97 except KeyError:
123 except KeyError:
98 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
124 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
125 return name
126
127 def get_path(self, notebook_id):
128 """Return a full path to a notebook given its notebook_id."""
129 name = self.get_name(notebook_id)
99 return self.get_path_by_name(name)
130 return self.get_path_by_name(name)
100
131
101 def get_path_by_name(self, name):
132 def get_path_by_name(self, name):
102 """Return a full path to a notebook given its name."""
133 """Return a full path to a notebook given its name."""
103 filename = name + self.filename_ext
134 filename = name + self.filename_ext
104 path = os.path.join(self.notebook_dir, filename)
135 path = os.path.join(self.notebook_dir, filename)
105 return path
136 return path
106
137
107 def read_notebook_object(self, notebook_id):
138 def read_notebook_object_from_path(self, path):
108 """Get the NotebookNode representation of a notebook by notebook_id."""
139 """read a notebook object from a path"""
109 path = self.find_path(notebook_id)
110 if not os.path.isfile(path):
111 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
112 info = os.stat(path)
140 info = os.stat(path)
113 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
141 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
114 with open(path,'r') as f:
142 with open(path,'r') as f:
@@ -116,12 +144,20 b' class FileNotebookManager(NotebookManager):'
116 try:
144 try:
117 # v1 and v2 and json in the .ipynb files.
145 # v1 and v2 and json in the .ipynb files.
118 nb = current.reads(s, u'json')
146 nb = current.reads(s, u'json')
119 except:
147 except Exception as e:
120 raise web.HTTPError(500, u'Unreadable JSON notebook.')
148 raise web.HTTPError(500, u'Unreadable JSON notebook: %s' % e)
149 return last_modified, nb
150
151 def read_notebook_object(self, notebook_id):
152 """Get the Notebook representation of a notebook by notebook_id."""
153 path = self.get_path(notebook_id)
154 if not os.path.isfile(path):
155 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
156 last_modified, nb = self.read_notebook_object_from_path(path)
121 # Always use the filename as the notebook name.
157 # Always use the filename as the notebook name.
122 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
158 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
123 return last_modified, nb
159 return last_modified, nb
124
160
125 def write_notebook_object(self, nb, notebook_id=None):
161 def write_notebook_object(self, nb, notebook_id=None):
126 """Save an existing notebook object by notebook_id."""
162 """Save an existing notebook object by notebook_id."""
127 try:
163 try:
@@ -136,8 +172,11 b' class FileNotebookManager(NotebookManager):'
136 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
172 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
137
173
138 old_name = self.mapping[notebook_id]
174 old_name = self.mapping[notebook_id]
175 old_checkpoints = self.list_checkpoints(notebook_id)
176
139 path = self.get_path_by_name(new_name)
177 path = self.get_path_by_name(new_name)
140 try:
178 try:
179 self.log.debug("Writing notebook %s", path)
141 with open(path,'w') as f:
180 with open(path,'w') as f:
142 current.write(nb, f, u'json')
181 current.write(nb, f, u'json')
143 except Exception as e:
182 except Exception as e:
@@ -146,6 +185,7 b' class FileNotebookManager(NotebookManager):'
146 # save .py script as well
185 # save .py script as well
147 if self.save_script:
186 if self.save_script:
148 pypath = os.path.splitext(path)[0] + '.py'
187 pypath = os.path.splitext(path)[0] + '.py'
188 self.log.debug("Writing script %s", pypath)
149 try:
189 try:
150 with io.open(pypath,'w', encoding='utf-8') as f:
190 with io.open(pypath,'w', encoding='utf-8') as f:
151 current.write(nb, f, u'py')
191 current.write(nb, f, u'py')
@@ -154,25 +194,49 b' class FileNotebookManager(NotebookManager):'
154
194
155 # remove old files if the name changed
195 # remove old files if the name changed
156 if old_name != new_name:
196 if old_name != new_name:
197 # update mapping
198 self.mapping[notebook_id] = new_name
199 self.rev_mapping[new_name] = notebook_id
200 del self.rev_mapping[old_name]
201
202 # remove renamed original, if it exists
157 old_path = self.get_path_by_name(old_name)
203 old_path = self.get_path_by_name(old_name)
158 if os.path.isfile(old_path):
204 if os.path.isfile(old_path):
205 self.log.debug("unlinking %s", old_path)
159 os.unlink(old_path)
206 os.unlink(old_path)
207
208 # cleanup old script, if it exists
160 if self.save_script:
209 if self.save_script:
161 old_pypath = os.path.splitext(old_path)[0] + '.py'
210 old_pypath = os.path.splitext(old_path)[0] + '.py'
162 if os.path.isfile(old_pypath):
211 if os.path.isfile(old_pypath):
212 self.log.debug("unlinking %s", old_pypath)
163 os.unlink(old_pypath)
213 os.unlink(old_pypath)
164 self.mapping[notebook_id] = new_name
214
165 self.rev_mapping[new_name] = notebook_id
215 # rename checkpoints to follow file
166 del self.rev_mapping[old_name]
216 self.log.debug("%s", old_checkpoints)
167
217 for cp in old_checkpoints:
218 old_cp_path = self.get_checkpoint_path_by_name(old_name, cp)
219 new_cp_path = self.get_checkpoint_path_by_name(new_name, cp)
220 if os.path.isfile(old_cp_path):
221 self.log.debug("renaming %s -> %s", old_cp_path, new_cp_path)
222 os.rename(old_cp_path, new_cp_path)
223
168 return notebook_id
224 return notebook_id
169
225
170 def delete_notebook(self, notebook_id):
226 def delete_notebook(self, notebook_id):
171 """Delete notebook by notebook_id."""
227 """Delete notebook by notebook_id."""
172 path = self.find_path(notebook_id)
228 path = self.get_path(notebook_id)
173 if not os.path.isfile(path):
229 if not os.path.isfile(path):
174 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
230 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
231 self.log.debug("unlinking %s", path)
175 os.unlink(path)
232 os.unlink(path)
233
234 # clear checkpoints
235 for checkpoint_id in self.list_checkpoints(notebook_id):
236 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
237 if os.path.isfile(path):
238 self.log.debug("unlinking %s", path)
239 os.unlink(path)
176 self.delete_notebook_id(notebook_id)
240 self.delete_notebook_id(notebook_id)
177
241
178 def increment_filename(self, basename):
242 def increment_filename(self, basename):
@@ -191,6 +255,69 b' class FileNotebookManager(NotebookManager):'
191 else:
255 else:
192 i = i+1
256 i = i+1
193 return name
257 return name
258
259 # Checkpoint-related utilities
260
261 def get_checkpoint_path_by_name(self, name, checkpoint_id):
262 """Return a full path to a notebook checkpoint, given its name and checkpoint id."""
263 filename = "{name}-{checkpoint_id}{ext}".format(
264 name=name,
265 checkpoint_id=checkpoint_id,
266 ext=self.filename_ext,
267 )
268 path = os.path.join(self.checkpoint_dir, filename)
269 return path
270
271 def get_checkpoint_path(self, notebook_id, checkpoint_id):
272 """find the path to a checkpoint"""
273 name = self.get_name(notebook_id)
274 return self.get_checkpoint_path_by_name(name, checkpoint_id)
275
276 # public checkpoint API
277
278 def create_checkpoint(self, notebook_id):
279 """Create a checkpoint from the current state of a notebook"""
280 nb_path = self.get_path(notebook_id)
281 cp_path = self.get_checkpoint_path(notebook_id, "checkpoint")
282 self.log.debug("creating checkpoint for notebook %s", notebook_id)
283 if not os.path.exists(self.checkpoint_dir):
284 os.mkdir(self.checkpoint_dir)
285 shutil.copy2(nb_path, cp_path)
286
287 def list_checkpoints(self, notebook_id):
288 """list the checkpoints for a given notebook
194
289
290 This notebook manager currently only supports one checkpoint per notebook.
291 """
292 path = self.get_checkpoint_path(notebook_id, "checkpoint")
293 if os.path.exists(path):
294 return ["checkpoint"]
295 else:
296 return []
297
298 def restore_checkpoint(self, notebook_id, checkpoint_id):
299 """restore a notebook to a checkpointed state"""
300 self.log.info("restoring Notebook %s from checkpoint %s", notebook_id, checkpoint_id)
301 nb_path = self.get_path(notebook_id)
302 cp_path = self.get_checkpoint_path(notebook_id, checkpoint_id)
303 if not os.path.isfile(cp_path):
304 raise web.HTTPError(404,
305 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
306 )
307 # ensure notebook is readable (never restore from an unreadable notebook)
308 last_modified, nb = self.read_notebook_object_from_path(cp_path)
309 shutil.copy2(cp_path, nb_path)
310 self.log.debug("copying %s -> %s", cp_path, nb_path)
311
312 def delete_checkpoint(self, notebook_id, checkpoint_id):
313 """delete a notebook's checkpoint"""
314 path = self.get_checkpoint_path(notebook_id, checkpoint_id)
315 if not os.path.isfile(path):
316 raise web.HTTPError(404,
317 u'Notebook checkpoint does not exist: %s-%s' % (notebook_id, checkpoint_id)
318 )
319 self.log.debug("unlinking %s", path)
320 os.unlink(path)
321
195 def info_string(self):
322 def info_string(self):
196 return "Serving notebooks from local directory: %s" % self.notebook_dir
323 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -36,7 +36,7 b' class NotebookManager(LoggingConfigurable):'
36 # 1. Where the notebooks are stored if FileNotebookManager is used.
36 # 1. Where the notebooks are stored if FileNotebookManager is used.
37 # 2. The cwd of the kernel for a project.
37 # 2. The cwd of the kernel for a project.
38 # Right now we use this attribute in a number of different places and
38 # Right now we use this attribute in a number of different places and
39 # we are going to have to disentagle all of this.
39 # we are going to have to disentangle all of this.
40 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
40 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
41 The directory to use for notebooks.
41 The directory to use for notebooks.
42 """)
42 """)
@@ -205,7 +205,28 b' class NotebookManager(LoggingConfigurable):'
205 nb.metadata.name = name
205 nb.metadata.name = name
206 notebook_id = self.write_notebook_object(nb)
206 notebook_id = self.write_notebook_object(nb)
207 return notebook_id
207 return notebook_id
208
209 # Checkpoint-related
210
211 def create_checkpoint(self, notebook_id):
212 """Create a checkpoint of the current state of a notebook
213
214 Returns a checkpoint_id for the new checkpoint.
215 """
216 raise NotImplementedError("must be implemented in a subclass")
217
218 def list_checkpoints(self, notebook_id):
219 """Return a list of checkpoints for a given notebook"""
220 return []
221
222 def restore_checkpoint(self, notebook_id, checkpoint_id):
223 """Restore a notebook from one of its checkpoints"""
224 raise NotImplementedError("must be implemented in a subclass")
208
225
226 def delete_checkpoint(self, notebook_id, checkpoint_id):
227 """delete a checkpoint for a notebook"""
228 raise NotImplementedError("must be implemented in a subclass")
229
209 def log_info(self):
230 def log_info(self):
210 self.log.info(self.info_string())
231 self.log.info(self.info_string())
211
232
General Comments 0
You need to be logged in to leave comments. Login now