Show More
@@ -222,59 +222,48 b' class FileCheckpointManager(FileManagerMixin, CheckpointManager):' | |||
|
222 | 222 | except AttributeError: |
|
223 | 223 | return getcwd() |
|
224 | 224 | |
|
225 |
# |
|
|
226 |
def create_ |
|
|
227 | """Create a checkpoint from the current content of a notebook.""" | |
|
228 | path = path.strip('/') | |
|
229 | # only the one checkpoint ID: | |
|
230 | checkpoint_id = u"checkpoint" | |
|
231 | os_checkpoint_path = self.checkpoint_path(checkpoint_id, path) | |
|
232 | self.log.debug("creating checkpoint for %s", path) | |
|
233 | with self.perm_to_403(): | |
|
234 | self._save_file(os_checkpoint_path, content, format=format) | |
|
235 | ||
|
236 | # return the checkpoint info | |
|
237 | return self.checkpoint_model(checkpoint_id, os_checkpoint_path) | |
|
238 | ||
|
239 | def create_notebook_checkpoint(self, nb, path): | |
|
240 | """Create a checkpoint from the current content of a notebook.""" | |
|
241 | path = path.strip('/') | |
|
242 | # only the one checkpoint ID: | |
|
243 | checkpoint_id = u"checkpoint" | |
|
244 | os_checkpoint_path = self.checkpoint_path(checkpoint_id, path) | |
|
245 | self.log.debug("creating checkpoint for %s", path) | |
|
246 | with self.perm_to_403(): | |
|
247 | self._save_notebook(os_checkpoint_path, nb) | |
|
225 | # ContentsManager-dependent checkpoint API | |
|
226 | def create_checkpoint(self, contents_mgr, path): | |
|
227 | """ | |
|
228 | Create a checkpoint. | |
|
248 | 229 |
|
|
249 | # return the checkpoint info | |
|
250 | return self.checkpoint_model(checkpoint_id, os_checkpoint_path) | |
|
230 | If contents_mgr is backed by the local filesystem, just copy the | |
|
231 | appropriate file to the checkpoint directory. Otherwise, ask the | |
|
232 | ContentsManager for a model and write it ourselves. | |
|
233 | """ | |
|
234 | if contents_mgr.backend == 'local_file': | |
|
235 | # We know that the file is in the local filesystem, so just copy | |
|
236 | # from the base location to our location. | |
|
237 | checkpoint_id = u'checkpoint' | |
|
238 | src_path = contents_mgr._get_os_path(path) | |
|
239 | dest_path = self.checkpoint_path(checkpoint_id, path) | |
|
240 | self._copy(src_path, dest_path) | |
|
241 | return self.checkpoint_model(checkpoint_id, dest_path) | |
|
242 | else: | |
|
243 | return super(FileCheckpointManager, self).create_checkpoint( | |
|
244 | contents_mgr, path, | |
|
245 | ) | |
|
251 | 246 | |
|
252 |
def |
|
|
253 | """Get the content of a checkpoint. | |
|
247 | def restore_checkpoint(self, contents_mgr, checkpoint_id, path): | |
|
248 | """ | |
|
249 | Restore a checkpoint. | |
|
254 | 250 | |
|
255 | Returns a model suitable for passing to ContentsManager.save. | |
|
251 | If contents_mgr is backed by the local filesystem, just copy the | |
|
252 | appropriate file from the checkpoint directory. Otherwise, load the | |
|
253 | model and pass it to ContentsManager.save. | |
|
256 | 254 | """ |
|
257 | path = path.strip('/') | |
|
258 | self.log.info("restoring %s from checkpoint %s", path, checkpoint_id) | |
|
259 | os_checkpoint_path = self.checkpoint_path(checkpoint_id, path) | |
|
260 | if not os.path.isfile(os_checkpoint_path): | |
|
261 | self.no_such_checkpoint(path, checkpoint_id) | |
|
262 | if type == 'notebook': | |
|
263 | return { | |
|
264 | 'type': type, | |
|
265 | 'content': self._read_notebook( | |
|
266 | os_checkpoint_path, | |
|
267 | as_version=4, | |
|
268 | ), | |
|
269 | } | |
|
255 | if contents_mgr.backend == 'local_file': | |
|
256 | # We know that the file is in the local filesystem, so just copy | |
|
257 | # from our base location to the location expected by content | |
|
258 | src_path = self.checkpoint_path(checkpoint_id, path) | |
|
259 | dest_path = contents_mgr._get_os_path(path) | |
|
260 | self._copy(src_path, dest_path) | |
|
270 | 261 | else: |
|
271 | content, format = self._read_file(os_checkpoint_path, format=None) | |
|
272 | return { | |
|
273 | 'type': type, | |
|
274 | 'content': content, | |
|
275 | 'format': format, | |
|
276 | } | |
|
262 | super(FileCheckpointManager, self).restore_checkpoint( | |
|
263 | contents_mgr, checkpoint_id, path | |
|
264 | ) | |
|
277 | 265 | |
|
266 | # ContentsManager-independent checkpoint API | |
|
278 | 267 | def rename_checkpoint(self, checkpoint_id, old_path, new_path): |
|
279 | 268 | """Rename a checkpoint from old_path to new_path.""" |
|
280 | 269 | old_cp_path = self.checkpoint_path(checkpoint_id, old_path) |
@@ -341,6 +330,64 b' class FileCheckpointManager(FileManagerMixin, CheckpointManager):' | |||
|
341 | 330 | ) |
|
342 | 331 | return info |
|
343 | 332 | |
|
333 | def create_file_checkpoint(self, content, format, path): | |
|
334 | """Create a checkpoint from the current content of a notebook.""" | |
|
335 | path = path.strip('/') | |
|
336 | # only the one checkpoint ID: | |
|
337 | checkpoint_id = u"checkpoint" | |
|
338 | os_checkpoint_path = self.checkpoint_path(checkpoint_id, path) | |
|
339 | self.log.debug("creating checkpoint for %s", path) | |
|
340 | with self.perm_to_403(): | |
|
341 | self._save_file(os_checkpoint_path, content, format=format) | |
|
342 | ||
|
343 | # return the checkpoint info | |
|
344 | return self.checkpoint_model(checkpoint_id, os_checkpoint_path) | |
|
345 | ||
|
346 | def create_notebook_checkpoint(self, nb, path): | |
|
347 | """Create a checkpoint from the current content of a notebook.""" | |
|
348 | path = path.strip('/') | |
|
349 | # only the one checkpoint ID: | |
|
350 | checkpoint_id = u"checkpoint" | |
|
351 | os_checkpoint_path = self.checkpoint_path(checkpoint_id, path) | |
|
352 | self.log.debug("creating checkpoint for %s", path) | |
|
353 | with self.perm_to_403(): | |
|
354 | self._save_notebook(os_checkpoint_path, nb) | |
|
355 | ||
|
356 | # return the checkpoint info | |
|
357 | return self.checkpoint_model(checkpoint_id, os_checkpoint_path) | |
|
358 | ||
|
359 | def get_checkpoint(self, checkpoint_id, path, type): | |
|
360 | """Get the content of a checkpoint. | |
|
361 | ||
|
362 | Returns a model suitable for passing to ContentsManager.save. | |
|
363 | """ | |
|
364 | path = path.strip('/') | |
|
365 | self.log.info("restoring %s from checkpoint %s", path, checkpoint_id) | |
|
366 | os_checkpoint_path = self.checkpoint_path(checkpoint_id, path) | |
|
367 | if not os.path.isfile(os_checkpoint_path): | |
|
368 | self.no_such_checkpoint(path, checkpoint_id) | |
|
369 | ||
|
370 | if type == 'notebook': | |
|
371 | return { | |
|
372 | 'type': type, | |
|
373 | 'content': self._read_notebook( | |
|
374 | os_checkpoint_path, | |
|
375 | as_version=4, | |
|
376 | ), | |
|
377 | } | |
|
378 | elif type == 'file': | |
|
379 | content, format = self._read_file(os_checkpoint_path, format=None) | |
|
380 | return { | |
|
381 | 'type': type, | |
|
382 | 'content': content, | |
|
383 | 'format': format, | |
|
384 | } | |
|
385 | else: | |
|
386 | raise web.HTTPError( | |
|
387 | 500, | |
|
388 | u'Unexpected type %s' % type | |
|
389 | ) | |
|
390 | ||
|
344 | 391 | # Error Handling |
|
345 | 392 | def no_such_checkpoint(self, path, checkpoint_id): |
|
346 | 393 | raise web.HTTPError( |
@@ -421,6 +468,9 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||
|
421 | 468 | def _checkpoint_manager_class_default(self): |
|
422 | 469 | return FileCheckpointManager |
|
423 | 470 | |
|
471 | def _backend_default(self): | |
|
472 | return 'local_file' | |
|
473 | ||
|
424 | 474 | def is_hidden(self, path): |
|
425 | 475 | """Does the API style path correspond to a hidden directory or file? |
|
426 | 476 | |
@@ -681,10 +731,7 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||
|
681 | 731 | self._save_notebook(os_path, nb) |
|
682 | 732 | # One checkpoint should always exist for notebooks. |
|
683 | 733 | if not self.checkpoint_manager.list_checkpoints(path): |
|
684 |
self. |
|
|
685 | nb, | |
|
686 | path, | |
|
687 | ) | |
|
734 | self.create_checkpoint(path) | |
|
688 | 735 | elif model['type'] == 'file': |
|
689 | 736 | # Missing format will be handled internally by _save_file. |
|
690 | 737 | self._save_file(os_path, model['content'], model.get('format')) |
@@ -11,7 +11,6 b' import re' | |||
|
11 | 11 | |
|
12 | 12 | from tornado.web import HTTPError |
|
13 | 13 | |
|
14 | from IPython import nbformat | |
|
15 | 14 | from IPython.config.configurable import LoggingConfigurable |
|
16 | 15 | from IPython.nbformat import sign, validate, ValidationError |
|
17 | 16 | from IPython.nbformat.v4 import new_notebook |
@@ -34,6 +33,28 b' class CheckpointManager(LoggingConfigurable):' | |||
|
34 | 33 | """ |
|
35 | 34 | Base class for managing checkpoints for a ContentsManager. |
|
36 | 35 | """ |
|
36 | ||
|
37 | def create_checkpoint(self, contents_mgr, path): | |
|
38 | model = contents_mgr.get(path, content=True) | |
|
39 | type = model['type'] | |
|
40 | if type == 'notebook': | |
|
41 | return self.create_notebook_checkpoint( | |
|
42 | model['content'], | |
|
43 | path, | |
|
44 | ) | |
|
45 | elif type == 'file': | |
|
46 | return self.create_file_checkpoint( | |
|
47 | model['content'], | |
|
48 | model['format'], | |
|
49 | path, | |
|
50 | ) | |
|
51 | ||
|
52 | def restore_checkpoint(self, contents_mgr, checkpoint_id, path): | |
|
53 | """Restore a checkpoint.""" | |
|
54 | type = contents_mgr.get(path, content=False)['type'] | |
|
55 | model = self.get_checkpoint(checkpoint_id, path, type) | |
|
56 | contents_mgr.save(model, path) | |
|
57 | ||
|
37 | 58 | def create_file_checkpoint(self, content, format, path): |
|
38 | 59 | """Create a checkpoint of the current state of a file |
|
39 | 60 | |
@@ -159,6 +180,7 b' class ContentsManager(LoggingConfigurable):' | |||
|
159 | 180 | checkpoint_manager_class = Type(CheckpointManager, config=True) |
|
160 | 181 | checkpoint_manager = Instance(CheckpointManager, config=True) |
|
161 | 182 | checkpoint_manager_kwargs = Dict(allow_none=False, config=True) |
|
183 | backend = Unicode(default_value="") | |
|
162 | 184 | |
|
163 | 185 | def _checkpoint_manager_default(self): |
|
164 | 186 | return self.checkpoint_manager_class(**self.checkpoint_manager_kwargs) |
@@ -502,35 +524,16 b' class ContentsManager(LoggingConfigurable):' | |||
|
502 | 524 | # Part 3: Checkpoints API |
|
503 | 525 | def create_checkpoint(self, path): |
|
504 | 526 | """Create a checkpoint.""" |
|
505 | model = self.get(path, content=True) | |
|
506 | type = model['type'] | |
|
507 | if type == 'notebook': | |
|
508 | return self.checkpoint_manager.create_notebook_checkpoint( | |
|
509 | model['content'], | |
|
510 | path, | |
|
511 | ) | |
|
512 | elif type == 'file': | |
|
513 | return self.checkpoint_manager.create_file_checkpoint( | |
|
514 | model['content'], | |
|
515 | model['format'], | |
|
516 | path, | |
|
517 | ) | |
|
518 | ||
|
519 | def list_checkpoints(self, path): | |
|
520 | return self.checkpoint_manager.list_checkpoints(path) | |
|
527 | return self.checkpoint_manager.create_checkpoint(self, path) | |
|
521 | 528 | |
|
522 | 529 | def restore_checkpoint(self, checkpoint_id, path): |
|
523 | 530 | """ |
|
524 | 531 | Restore a checkpoint. |
|
525 | 532 | """ |
|
526 | return self.save( | |
|
527 | model=self.checkpoint_manager.get_checkpoint( | |
|
528 | checkpoint_id, | |
|
529 | path, | |
|
530 | self.get(path, content=False)['type'] | |
|
531 | ), | |
|
532 | path=path, | |
|
533 | ) | |
|
533 | self.checkpoint_manager.restore_checkpoint(self, checkpoint_id, path) | |
|
534 | ||
|
535 | def list_checkpoints(self, path): | |
|
536 | return self.checkpoint_manager.list_checkpoints(path) | |
|
534 | 537 | |
|
535 | 538 | def delete_checkpoint(self, checkpoint_id, path): |
|
536 | 539 | return self.checkpoint_manager.delete_checkpoint(checkpoint_id, path) |
@@ -614,3 +614,25 b' class APITest(NotebookTestBase):' | |||
|
614 | 614 | with TemporaryDirectory() as td: |
|
615 | 615 | with self.patch_cp_root(td): |
|
616 | 616 | self.test_file_checkpoints() |
|
617 | ||
|
618 | @contextmanager | |
|
619 | def patch_cm_backend(self): | |
|
620 | """ | |
|
621 | Temporarily patch our ContentsManager to present a different backend. | |
|
622 | """ | |
|
623 | mgr = self.notebook.contents_manager | |
|
624 | old_backend = mgr.backend | |
|
625 | mgr.backend = "" | |
|
626 | try: | |
|
627 | yield | |
|
628 | finally: | |
|
629 | mgr.backend = old_backend | |
|
630 | ||
|
631 | def test_checkpoints_empty_backend(self): | |
|
632 | with self.patch_cm_backend(): | |
|
633 | self.test_checkpoints() | |
|
634 | ||
|
635 | with self.patch_cm_backend(): | |
|
636 | self.test_file_checkpoints() | |
|
637 | ||
|
638 |
General Comments 0
You need to be logged in to leave comments.
Login now