##// END OF EJS Templates
add checkpoint API to FileNBManager
MinRK -
Show More
@@ -20,6 +20,7 b' import datetime'
20 20 import io
21 21 import os
22 22 import glob
23 import shutil
23 24
24 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 72 filename_ext = Unicode(u'.ipynb')
47 73
48 74 # Map notebook names to notebook_ids
49 75 rev_mapping = Dict()
50
76
51 77 def get_notebook_names(self):
52 78 """List all notebook names in the notebook dir."""
53 79 names = glob.glob(os.path.join(self.notebook_dir,
@@ -89,26 +115,28 b' class FileNotebookManager(NotebookManager):'
89 115 return False
90 116 path = self.get_path_by_name(self.mapping[notebook_id])
91 117 return os.path.isfile(path)
92
93 def find_path(self, notebook_id):
94 """Return a full path to a notebook given its notebook_id."""
118
119 def get_name(self, notebook_id):
120 """get a notebook name, raising 404 if not found"""
95 121 try:
96 122 name = self.mapping[notebook_id]
97 123 except KeyError:
98 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 130 return self.get_path_by_name(name)
100 131
101 132 def get_path_by_name(self, name):
102 133 """Return a full path to a notebook given its name."""
103 134 filename = name + self.filename_ext
104 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):
108 """Get the NotebookNode representation of a notebook by notebook_id."""
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)
138 def read_notebook_object_from_path(self, path):
139 """read a notebook object from a path"""
112 140 info = os.stat(path)
113 141 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
114 142 with open(path,'r') as f:
@@ -116,12 +144,20 b' class FileNotebookManager(NotebookManager):'
116 144 try:
117 145 # v1 and v2 and json in the .ipynb files.
118 146 nb = current.reads(s, u'json')
119 except:
120 raise web.HTTPError(500, u'Unreadable JSON notebook.')
147 except Exception as e:
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 157 # Always use the filename as the notebook name.
122 158 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
123 159 return last_modified, nb
124
160
125 161 def write_notebook_object(self, nb, notebook_id=None):
126 162 """Save an existing notebook object by notebook_id."""
127 163 try:
@@ -136,8 +172,11 b' class FileNotebookManager(NotebookManager):'
136 172 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
137 173
138 174 old_name = self.mapping[notebook_id]
175 old_checkpoints = self.list_checkpoints(notebook_id)
176
139 177 path = self.get_path_by_name(new_name)
140 178 try:
179 self.log.debug("Writing notebook %s", path)
141 180 with open(path,'w') as f:
142 181 current.write(nb, f, u'json')
143 182 except Exception as e:
@@ -146,6 +185,7 b' class FileNotebookManager(NotebookManager):'
146 185 # save .py script as well
147 186 if self.save_script:
148 187 pypath = os.path.splitext(path)[0] + '.py'
188 self.log.debug("Writing script %s", pypath)
149 189 try:
150 190 with io.open(pypath,'w', encoding='utf-8') as f:
151 191 current.write(nb, f, u'py')
@@ -154,25 +194,49 b' class FileNotebookManager(NotebookManager):'
154 194
155 195 # remove old files if the name changed
156 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 203 old_path = self.get_path_by_name(old_name)
158 204 if os.path.isfile(old_path):
205 self.log.debug("unlinking %s", old_path)
159 206 os.unlink(old_path)
207
208 # cleanup old script, if it exists
160 209 if self.save_script:
161 210 old_pypath = os.path.splitext(old_path)[0] + '.py'
162 211 if os.path.isfile(old_pypath):
212 self.log.debug("unlinking %s", old_pypath)
163 213 os.unlink(old_pypath)
164 self.mapping[notebook_id] = new_name
165 self.rev_mapping[new_name] = notebook_id
166 del self.rev_mapping[old_name]
167
214
215 # rename checkpoints to follow file
216 self.log.debug("%s", old_checkpoints)
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 224 return notebook_id
169 225
170 226 def delete_notebook(self, notebook_id):
171 227 """Delete notebook by notebook_id."""
172 path = self.find_path(notebook_id)
228 path = self.get_path(notebook_id)
173 229 if not os.path.isfile(path):
174 230 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
231 self.log.debug("unlinking %s", path)
175 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 240 self.delete_notebook_id(notebook_id)
177 241
178 242 def increment_filename(self, basename):
@@ -191,6 +255,69 b' class FileNotebookManager(NotebookManager):'
191 255 else:
192 256 i = i+1
193 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 322 def info_string(self):
196 323 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -36,7 +36,7 b' class NotebookManager(LoggingConfigurable):'
36 36 # 1. Where the notebooks are stored if FileNotebookManager is used.
37 37 # 2. The cwd of the kernel for a project.
38 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 40 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
41 41 The directory to use for notebooks.
42 42 """)
@@ -205,7 +205,28 b' class NotebookManager(LoggingConfigurable):'
205 205 nb.metadata.name = name
206 206 notebook_id = self.write_notebook_object(nb)
207 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 230 def log_info(self):
210 231 self.log.info(self.info_string())
211 232
General Comments 0
You need to be logged in to leave comments. Login now