Show More
@@ -111,9 +111,20 b' class FileManagerMixin(object):' | |||||
111 | shutil.copyfile(src, dest) |
|
111 | shutil.copyfile(src, dest) | |
112 | try: |
|
112 | try: | |
113 | shutil.copystat(src, dest) |
|
113 | shutil.copystat(src, dest) | |
114 |
except OSError |
|
114 | except OSError: | |
115 | self.log.debug("copystat on %s failed", dest, exc_info=True) |
|
115 | self.log.debug("copystat on %s failed", dest, exc_info=True) | |
116 |
|
116 | |||
|
117 | def _read_notebook(self, os_path, as_version=4): | |||
|
118 | """Read a notebook from an os path.""" | |||
|
119 | with self.open(os_path, 'r', encoding='utf-8') as f: | |||
|
120 | try: | |||
|
121 | return nbformat.read(f, as_version=as_version) | |||
|
122 | except Exception as e: | |||
|
123 | raise web.HTTPError( | |||
|
124 | 400, | |||
|
125 | u"Unreadable Notebook: %s %r" % (os_path, e), | |||
|
126 | ) | |||
|
127 | ||||
117 | def _get_os_path(self, path): |
|
128 | def _get_os_path(self, path): | |
118 | """Given an API path, return its file system path. |
|
129 | """Given an API path, return its file system path. | |
119 |
|
130 | |||
@@ -129,82 +140,6 b' class FileManagerMixin(object):' | |||||
129 | """ |
|
140 | """ | |
130 | return to_os_path(path, self.root_dir) |
|
141 | return to_os_path(path, self.root_dir) | |
131 |
|
142 | |||
132 | def is_hidden(self, path): |
|
|||
133 | """Does the API style path correspond to a hidden directory or file? |
|
|||
134 |
|
||||
135 | Parameters |
|
|||
136 | ---------- |
|
|||
137 | path : string |
|
|||
138 | The path to check. This is an API path (`/` separated, |
|
|||
139 | relative to root_dir). |
|
|||
140 |
|
||||
141 | Returns |
|
|||
142 | ------- |
|
|||
143 | hidden : bool |
|
|||
144 | Whether the path exists and is hidden. |
|
|||
145 | """ |
|
|||
146 | path = path.strip('/') |
|
|||
147 | os_path = self._get_os_path(path=path) |
|
|||
148 | return is_hidden(os_path, self.root_dir) |
|
|||
149 |
|
||||
150 | def file_exists(self, path): |
|
|||
151 | """Returns True if the file exists, else returns False. |
|
|||
152 |
|
||||
153 | API-style wrapper for os.path.isfile |
|
|||
154 |
|
||||
155 | Parameters |
|
|||
156 | ---------- |
|
|||
157 | path : string |
|
|||
158 | The relative path to the file (with '/' as separator) |
|
|||
159 |
|
||||
160 | Returns |
|
|||
161 | ------- |
|
|||
162 | exists : bool |
|
|||
163 | Whether the file exists. |
|
|||
164 | """ |
|
|||
165 | path = path.strip('/') |
|
|||
166 | os_path = self._get_os_path(path) |
|
|||
167 | return os.path.isfile(os_path) |
|
|||
168 |
|
||||
169 | def dir_exists(self, path): |
|
|||
170 | """Does the API-style path refer to an extant directory? |
|
|||
171 |
|
||||
172 | API-style wrapper for os.path.isdir |
|
|||
173 |
|
||||
174 | Parameters |
|
|||
175 | ---------- |
|
|||
176 | path : string |
|
|||
177 | The path to check. This is an API path (`/` separated, |
|
|||
178 | relative to root_dir). |
|
|||
179 |
|
||||
180 | Returns |
|
|||
181 | ------- |
|
|||
182 | exists : bool |
|
|||
183 | Whether the path is indeed a directory. |
|
|||
184 | """ |
|
|||
185 | path = path.strip('/') |
|
|||
186 | os_path = self._get_os_path(path=path) |
|
|||
187 | return os.path.isdir(os_path) |
|
|||
188 |
|
||||
189 | def exists(self, path): |
|
|||
190 | """Returns True if the path exists, else returns False. |
|
|||
191 |
|
||||
192 | API-style wrapper for os.path.exists |
|
|||
193 |
|
||||
194 | Parameters |
|
|||
195 | ---------- |
|
|||
196 | path : string |
|
|||
197 | The API path to the file (with '/' as separator) |
|
|||
198 |
|
||||
199 | Returns |
|
|||
200 | ------- |
|
|||
201 | exists : bool |
|
|||
202 | Whether the target exists. |
|
|||
203 | """ |
|
|||
204 | path = path.strip('/') |
|
|||
205 | os_path = self._get_os_path(path=path) |
|
|||
206 | return os.path.exists(os_path) |
|
|||
207 |
|
||||
208 |
|
143 | |||
209 | class FileCheckpointManager(FileManagerMixin, CheckpointManager): |
|
144 | class FileCheckpointManager(FileManagerMixin, CheckpointManager): | |
210 | """ |
|
145 | """ | |
@@ -223,30 +158,44 b' class FileCheckpointManager(FileManagerMixin, CheckpointManager):' | |||||
223 | """, |
|
158 | """, | |
224 | ) |
|
159 | ) | |
225 |
|
160 | |||
226 | @property |
|
161 | root_dir = Unicode(config=True) | |
227 | def root_dir(self): |
|
162 | ||
|
163 | def _root_dir_default(self): | |||
228 | try: |
|
164 | try: | |
229 | return self.parent.root_dir |
|
165 | return self.parent.root_dir | |
230 | except AttributeError: |
|
166 | except AttributeError: | |
231 | return getcwd() |
|
167 | return getcwd() | |
232 |
|
168 | |||
233 | # public checkpoint API |
|
169 | # public checkpoint API | |
234 | def create_checkpoint(self, path): |
|
170 | def create_checkpoint(self, nb, path): | |
235 |
"""Create a checkpoint from the current |
|
171 | """Create a checkpoint from the current content of a notebook.""" | |
236 | path = path.strip('/') |
|
172 | path = path.strip('/') | |
237 | if not self.file_exists(path): |
|
|||
238 | raise web.HTTPError(404) |
|
|||
239 | src_path = self._get_os_path(path) |
|
|||
240 | # only the one checkpoint ID: |
|
173 | # only the one checkpoint ID: | |
241 | checkpoint_id = u"checkpoint" |
|
174 | checkpoint_id = u"checkpoint" | |
242 | cp_path = self.get_checkpoint_path(checkpoint_id, path) |
|
175 | os_checkpoint_path = self.get_checkpoint_path(checkpoint_id, path) | |
243 | self.log.debug("creating checkpoint for %s", path) |
|
176 | self.log.debug("creating checkpoint for %s", path) | |
244 | with self.perm_to_403(): |
|
177 | with self.perm_to_403(): | |
245 | self._copy(src_path, cp_path) |
|
178 | self._save_notebook(os_checkpoint_path, nb) | |
246 |
|
179 | |||
247 | # return the checkpoint info |
|
180 | # return the checkpoint info | |
248 | return self.get_checkpoint_model(checkpoint_id, path) |
|
181 | return self.get_checkpoint_model(checkpoint_id, path) | |
249 |
|
182 | |||
|
183 | def get_checkpoint_content(self, checkpoint_id, path): | |||
|
184 | """Get the content of a checkpoint. | |||
|
185 | ||||
|
186 | Returns an unvalidated model with the same structure as | |||
|
187 | the return value of ContentsManager.get | |||
|
188 | """ | |||
|
189 | path = path.strip('/') | |||
|
190 | self.log.info("restoring %s from checkpoint %s", path, checkpoint_id) | |||
|
191 | os_checkpoint_path = self.get_checkpoint_path(checkpoint_id, path) | |||
|
192 | return self._read_notebook(os_checkpoint_path, as_version=4) | |||
|
193 | ||||
|
194 | def _save_notebook(self, os_path, nb): | |||
|
195 | """Save a notebook file.""" | |||
|
196 | with self.atomic_writing(os_path, encoding='utf-8') as f: | |||
|
197 | nbformat.write(nb, f, version=nbformat.NO_CONVERT) | |||
|
198 | ||||
250 | def rename_checkpoint(self, checkpoint_id, old_path, new_path): |
|
199 | def rename_checkpoint(self, checkpoint_id, old_path, new_path): | |
251 | """Rename a checkpoint from old_path to new_path.""" |
|
200 | """Rename a checkpoint from old_path to new_path.""" | |
252 | old_cp_path = self.get_checkpoint_path(checkpoint_id, old_path) |
|
201 | old_cp_path = self.get_checkpoint_path(checkpoint_id, old_path) | |
@@ -260,6 +209,17 b' class FileCheckpointManager(FileManagerMixin, CheckpointManager):' | |||||
260 | with self.perm_to_403(): |
|
209 | with self.perm_to_403(): | |
261 | shutil.move(old_cp_path, new_cp_path) |
|
210 | shutil.move(old_cp_path, new_cp_path) | |
262 |
|
211 | |||
|
212 | def delete_checkpoint(self, checkpoint_id, path): | |||
|
213 | """delete a file's checkpoint""" | |||
|
214 | path = path.strip('/') | |||
|
215 | cp_path = self.get_checkpoint_path(checkpoint_id, path) | |||
|
216 | if not os.path.isfile(cp_path): | |||
|
217 | self.no_such_checkpoint(path, checkpoint_id) | |||
|
218 | ||||
|
219 | self.log.debug("unlinking %s", cp_path) | |||
|
220 | with self.perm_to_403(): | |||
|
221 | os.unlink(cp_path) | |||
|
222 | ||||
263 | def list_checkpoints(self, path): |
|
223 | def list_checkpoints(self, path): | |
264 | """list the checkpoints for a given file |
|
224 | """list the checkpoints for a given file | |
265 |
|
225 | |||
@@ -273,35 +233,6 b' class FileCheckpointManager(FileManagerMixin, CheckpointManager):' | |||||
273 | else: |
|
233 | else: | |
274 | return [self.get_checkpoint_model(checkpoint_id, path)] |
|
234 | return [self.get_checkpoint_model(checkpoint_id, path)] | |
275 |
|
235 | |||
276 | def restore_checkpoint(self, checkpoint_id, path): |
|
|||
277 | """restore a file to a checkpointed state""" |
|
|||
278 | path = path.strip('/') |
|
|||
279 | self.log.info("restoring %s from checkpoint %s", path, checkpoint_id) |
|
|||
280 | nb_path = self._get_os_path(path) |
|
|||
281 | cp_path = self.get_checkpoint_path(checkpoint_id, path) |
|
|||
282 | if not os.path.isfile(cp_path): |
|
|||
283 | self.log.debug("checkpoint file does not exist: %s", cp_path) |
|
|||
284 | self.no_such_checkpoint(path, checkpoint_id) |
|
|||
285 |
|
||||
286 | # ensure notebook is readable (never restore from unreadable notebook) |
|
|||
287 | if cp_path.endswith('.ipynb'): |
|
|||
288 | with self.open(cp_path, 'r', encoding='utf-8') as f: |
|
|||
289 | nbformat.read(f, as_version=4) |
|
|||
290 | self.log.debug("copying %s -> %s", cp_path, nb_path) |
|
|||
291 | with self.perm_to_403(): |
|
|||
292 | self._copy(cp_path, nb_path) |
|
|||
293 |
|
||||
294 | def delete_checkpoint(self, checkpoint_id, path): |
|
|||
295 | """delete a file's checkpoint""" |
|
|||
296 | path = path.strip('/') |
|
|||
297 | cp_path = self.get_checkpoint_path(checkpoint_id, path) |
|
|||
298 | if not os.path.isfile(cp_path): |
|
|||
299 | self.no_such_checkpoint(path, checkpoint_id) |
|
|||
300 |
|
||||
301 | self.log.debug("unlinking %s", cp_path) |
|
|||
302 | with self.perm_to_403(): |
|
|||
303 | os.unlink(cp_path) |
|
|||
304 |
|
||||
305 | # Checkpoint-related utilities |
|
236 | # Checkpoint-related utilities | |
306 | def get_checkpoint_path(self, checkpoint_id, path): |
|
237 | def get_checkpoint_path(self, checkpoint_id, path): | |
307 | """find the path to a checkpoint""" |
|
238 | """find the path to a checkpoint""" | |
@@ -413,6 +344,82 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||||
413 | def _checkpoint_manager_class_default(self): |
|
344 | def _checkpoint_manager_class_default(self): | |
414 | return FileCheckpointManager |
|
345 | return FileCheckpointManager | |
415 |
|
346 | |||
|
347 | def is_hidden(self, path): | |||
|
348 | """Does the API style path correspond to a hidden directory or file? | |||
|
349 | ||||
|
350 | Parameters | |||
|
351 | ---------- | |||
|
352 | path : string | |||
|
353 | The path to check. This is an API path (`/` separated, | |||
|
354 | relative to root_dir). | |||
|
355 | ||||
|
356 | Returns | |||
|
357 | ------- | |||
|
358 | hidden : bool | |||
|
359 | Whether the path exists and is hidden. | |||
|
360 | """ | |||
|
361 | path = path.strip('/') | |||
|
362 | os_path = self._get_os_path(path=path) | |||
|
363 | return is_hidden(os_path, self.root_dir) | |||
|
364 | ||||
|
365 | def file_exists(self, path): | |||
|
366 | """Returns True if the file exists, else returns False. | |||
|
367 | ||||
|
368 | API-style wrapper for os.path.isfile | |||
|
369 | ||||
|
370 | Parameters | |||
|
371 | ---------- | |||
|
372 | path : string | |||
|
373 | The relative path to the file (with '/' as separator) | |||
|
374 | ||||
|
375 | Returns | |||
|
376 | ------- | |||
|
377 | exists : bool | |||
|
378 | Whether the file exists. | |||
|
379 | """ | |||
|
380 | path = path.strip('/') | |||
|
381 | os_path = self._get_os_path(path) | |||
|
382 | return os.path.isfile(os_path) | |||
|
383 | ||||
|
384 | def dir_exists(self, path): | |||
|
385 | """Does the API-style path refer to an extant directory? | |||
|
386 | ||||
|
387 | API-style wrapper for os.path.isdir | |||
|
388 | ||||
|
389 | Parameters | |||
|
390 | ---------- | |||
|
391 | path : string | |||
|
392 | The path to check. This is an API path (`/` separated, | |||
|
393 | relative to root_dir). | |||
|
394 | ||||
|
395 | Returns | |||
|
396 | ------- | |||
|
397 | exists : bool | |||
|
398 | Whether the path is indeed a directory. | |||
|
399 | """ | |||
|
400 | path = path.strip('/') | |||
|
401 | os_path = self._get_os_path(path=path) | |||
|
402 | return os.path.isdir(os_path) | |||
|
403 | ||||
|
404 | def exists(self, path): | |||
|
405 | """Returns True if the path exists, else returns False. | |||
|
406 | ||||
|
407 | API-style wrapper for os.path.exists | |||
|
408 | ||||
|
409 | Parameters | |||
|
410 | ---------- | |||
|
411 | path : string | |||
|
412 | The API path to the file (with '/' as separator) | |||
|
413 | ||||
|
414 | Returns | |||
|
415 | ------- | |||
|
416 | exists : bool | |||
|
417 | Whether the target exists. | |||
|
418 | """ | |||
|
419 | path = path.strip('/') | |||
|
420 | os_path = self._get_os_path(path=path) | |||
|
421 | return os.path.exists(os_path) | |||
|
422 | ||||
416 | def _base_model(self, path): |
|
423 | def _base_model(self, path): | |
417 | """Build the common base of a contents model""" |
|
424 | """Build the common base of a contents model""" | |
418 | os_path = self._get_os_path(path) |
|
425 | os_path = self._get_os_path(path) | |
@@ -518,7 +525,6 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||||
518 |
|
525 | |||
519 | return model |
|
526 | return model | |
520 |
|
527 | |||
521 |
|
||||
522 | def _notebook_model(self, path, content=True): |
|
528 | def _notebook_model(self, path, content=True): | |
523 | """Build a notebook model |
|
529 | """Build a notebook model | |
524 |
|
530 | |||
@@ -529,11 +535,7 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||||
529 | model['type'] = 'notebook' |
|
535 | model['type'] = 'notebook' | |
530 | if content: |
|
536 | if content: | |
531 | os_path = self._get_os_path(path) |
|
537 | os_path = self._get_os_path(path) | |
532 | with self.open(os_path, 'r', encoding='utf-8') as f: |
|
538 | nb = self._read_notebook(os_path, as_version=4) | |
533 | try: |
|
|||
534 | nb = nbformat.read(f, as_version=4) |
|
|||
535 | except Exception as e: |
|
|||
536 | raise web.HTTPError(400, u"Unreadable Notebook: %s %r" % (os_path, e)) |
|
|||
537 | self.mark_trusted_cells(nb, path) |
|
539 | self.mark_trusted_cells(nb, path) | |
538 | model['content'] = nb |
|
540 | model['content'] = nb | |
539 | model['format'] = 'json' |
|
541 | model['format'] = 'json' | |
@@ -582,13 +584,15 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||||
582 | model = self._file_model(path, content=content, format=format) |
|
584 | model = self._file_model(path, content=content, format=format) | |
583 | return model |
|
585 | return model | |
584 |
|
586 | |||
585 |
def _save_notebook(self, os_path, model, path |
|
587 | def _save_notebook(self, os_path, model, path): | |
586 | """save a notebook file""" |
|
588 | """save a notebook file""" | |
587 | # Save the notebook file |
|
|||
588 | nb = nbformat.from_dict(model['content']) |
|
589 | nb = nbformat.from_dict(model['content']) | |
589 |
|
||||
590 | self.check_and_sign(nb, path) |
|
590 | self.check_and_sign(nb, path) | |
591 |
|
591 | |||
|
592 | # One checkpoint should always exist for notebooks. | |||
|
593 | if not self.checkpoint_manager.list_checkpoints(path): | |||
|
594 | self.checkpoint_manager.create_checkpoint(nb, path) | |||
|
595 | ||||
592 | with self.atomic_writing(os_path, encoding='utf-8') as f: |
|
596 | with self.atomic_writing(os_path, encoding='utf-8') as f: | |
593 | nbformat.write(nb, f, version=nbformat.NO_CONVERT) |
|
597 | nbformat.write(nb, f, version=nbformat.NO_CONVERT) | |
594 |
|
598 | |||
@@ -632,11 +636,6 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||||
632 |
|
636 | |||
633 | self.run_pre_save_hook(model=model, path=path) |
|
637 | self.run_pre_save_hook(model=model, path=path) | |
634 |
|
638 | |||
635 | cp_mgr = self.checkpoint_manager |
|
|||
636 | # One checkpoint should always exist |
|
|||
637 | if self.file_exists(path) and not cp_mgr.list_checkpoints(path): |
|
|||
638 | cp_mgr.create_checkpoint(path) |
|
|||
639 |
|
||||
640 | os_path = self._get_os_path(path) |
|
639 | os_path = self._get_os_path(path) | |
641 | self.log.debug("Saving %s", os_path) |
|
640 | self.log.debug("Saving %s", os_path) | |
642 | try: |
|
641 | try: |
@@ -11,6 +11,7 b' import re' | |||||
11 |
|
11 | |||
12 | from tornado.web import HTTPError |
|
12 | from tornado.web import HTTPError | |
13 |
|
13 | |||
|
14 | from IPython import nbformat | |||
14 | from IPython.config.configurable import LoggingConfigurable |
|
15 | from IPython.config.configurable import LoggingConfigurable | |
15 | from IPython.nbformat import sign, validate, ValidationError |
|
16 | from IPython.nbformat import sign, validate, ValidationError | |
16 | from IPython.nbformat.v4 import new_notebook |
|
17 | from IPython.nbformat.v4 import new_notebook | |
@@ -34,33 +35,36 b' class CheckpointManager(LoggingConfigurable):' | |||||
34 | Base class for managing checkpoints for a ContentsManager. |
|
35 | Base class for managing checkpoints for a ContentsManager. | |
35 | """ |
|
36 | """ | |
36 |
|
37 | |||
37 | def create_checkpoint(self, path): |
|
38 | def create_checkpoint(self, nb, path): | |
38 | """Create a checkpoint of the current state of a file |
|
39 | """Create a checkpoint of the current state of a file | |
39 |
|
40 | |||
40 | Returns a checkpoint_id for the new checkpoint. |
|
41 | Returns a checkpoint_id for the new checkpoint. | |
41 | """ |
|
42 | """ | |
42 | raise NotImplementedError("must be implemented in a subclass") |
|
43 | raise NotImplementedError("must be implemented in a subclass") | |
43 |
|
44 | |||
|
45 | def get_checkpoint_content(self, checkpoint_id, path): | |||
|
46 | """Get the content of a checkpoint. | |||
|
47 | ||||
|
48 | Returns an unvalidated model with the same structure as | |||
|
49 | the return value of ContentsManager.get | |||
|
50 | """ | |||
|
51 | raise NotImplementedError("must be implemented in a subclass") | |||
|
52 | ||||
44 | def rename_checkpoint(self, checkpoint_id, old_path, new_path): |
|
53 | def rename_checkpoint(self, checkpoint_id, old_path, new_path): | |
45 | """Rename a checkpoint from old_path to new_path.""" |
|
54 | """Rename a single checkpoint from old_path to new_path.""" | |
46 | raise NotImplementedError("must be implemented in a subclass") |
|
55 | raise NotImplementedError("must be implemented in a subclass") | |
47 |
|
56 | |||
48 | def delete_checkpoint(self, checkpoint_id, path): |
|
57 | def delete_checkpoint(self, checkpoint_id, path): | |
49 | """delete a checkpoint for a file""" |
|
58 | """delete a checkpoint for a file""" | |
50 | raise NotImplementedError("must be implemented in a subclass") |
|
59 | raise NotImplementedError("must be implemented in a subclass") | |
51 |
|
60 | |||
52 | def restore_checkpoint(self, checkpoint_id, path): |
|
|||
53 | """Restore a file from one of its checkpoints""" |
|
|||
54 | raise NotImplementedError("must be implemented in a subclass") |
|
|||
55 |
|
||||
56 | def list_checkpoints(self, path): |
|
61 | def list_checkpoints(self, path): | |
57 | """Return a list of checkpoints for a given file""" |
|
62 | """Return a list of checkpoints for a given file""" | |
58 | raise NotImplementedError("must be implemented in a subclass") |
|
63 | raise NotImplementedError("must be implemented in a subclass") | |
59 |
|
64 | |||
60 | def rename_all_checkpoints(self, old_path, new_path): |
|
65 | def rename_all_checkpoints(self, old_path, new_path): | |
61 | """Rename all checkpoints for old_path to new_path.""" |
|
66 | """Rename all checkpoints for old_path to new_path.""" | |
62 |
|
|
67 | for cp in self.list_checkpoints(old_path): | |
63 | for cp in old_checkpoints: |
|
|||
64 | self.rename_checkpoint(cp['id'], old_path, new_path) |
|
68 | self.rename_checkpoint(cp['id'], old_path, new_path) | |
65 |
|
69 | |||
66 | def delete_all_checkpoints(self, path): |
|
70 | def delete_all_checkpoints(self, path): | |
@@ -490,22 +494,34 b' class ContentsManager(LoggingConfigurable):' | |||||
490 | return not any(fnmatch(name, glob) for glob in self.hide_globs) |
|
494 | return not any(fnmatch(name, glob) for glob in self.hide_globs) | |
491 |
|
495 | |||
492 | # Part 3: Checkpoints API |
|
496 | # Part 3: Checkpoints API | |
493 | # By default, all methods are forwarded to our CheckpointManager instance. |
|
|||
494 | def create_checkpoint(self, path): |
|
497 | def create_checkpoint(self, path): | |
495 | return self.checkpoint_manager.create_checkpoint(path) |
|
498 | """Create a checkpoint.""" | |
496 |
|
499 | |||
497 | def rename_checkpoint(self, checkpoint_id, old_path, new_path): |
|
500 | nb = nbformat.from_dict(self.get(path, content=True)['content']) | |
498 | return self.checkpoint_manager.rename_checkpoint( |
|
501 | self.check_and_sign(nb, path) | |
499 | checkpoint_id, |
|
502 | return self.checkpoint_manager.create_checkpoint(nb, path) | |
500 | old_path, |
|
|||
501 | new_path, |
|
|||
502 | ) |
|
|||
503 |
|
503 | |||
504 | def list_checkpoints(self, path): |
|
504 | def list_checkpoints(self, path): | |
505 | return self.checkpoint_manager.list_checkpoints(path) |
|
505 | return self.checkpoint_manager.list_checkpoints(path) | |
506 |
|
506 | |||
507 | def restore_checkpoint(self, checkpoint_id, path): |
|
507 | def restore_checkpoint(self, checkpoint_id, path): | |
508 | return self.checkpoint_manager.restore_checkpoint(checkpoint_id, path) |
|
508 | """ | |
|
509 | Restore a checkpoint. | |||
|
510 | """ | |||
|
511 | nb = self.checkpoint_manager.get_checkpoint_content( | |||
|
512 | checkpoint_id, | |||
|
513 | path, | |||
|
514 | ) | |||
|
515 | ||||
|
516 | self.mark_trusted_cells(nb, path) | |||
|
517 | ||||
|
518 | model = { | |||
|
519 | 'content': nb, | |||
|
520 | 'type': 'notebook', | |||
|
521 | } | |||
|
522 | ||||
|
523 | self.validate_notebook_model(model) | |||
|
524 | return self.save(model, path) | |||
509 |
|
525 | |||
510 | def delete_checkpoint(self, checkpoint_id, path): |
|
526 | def delete_checkpoint(self, checkpoint_id, path): | |
511 | return self.checkpoint_manager.delete_checkpoint(checkpoint_id, path) |
|
527 | return self.checkpoint_manager.delete_checkpoint(checkpoint_id, path) |
General Comments 0
You need to be logged in to leave comments.
Login now