Show More
@@ -222,59 +222,48 b' class FileCheckpointManager(FileManagerMixin, CheckpointManager):' | |||||
222 | except AttributeError: |
|
222 | except AttributeError: | |
223 | return getcwd() |
|
223 | return getcwd() | |
224 |
|
224 | |||
225 |
# |
|
225 | # ContentsManager-dependent checkpoint API | |
226 |
def create_ |
|
226 | def create_checkpoint(self, contents_mgr, path): | |
227 | """Create a checkpoint from the current content of a notebook.""" |
|
227 | """ | |
228 | path = path.strip('/') |
|
228 | Create a checkpoint. | |
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) |
|
|||
248 |
|
|
229 | ||
249 | # return the checkpoint info |
|
230 | If contents_mgr is backed by the local filesystem, just copy the | |
250 | return self.checkpoint_model(checkpoint_id, os_checkpoint_path) |
|
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 |
|
247 | def restore_checkpoint(self, contents_mgr, checkpoint_id, path): | |
253 | """Get the content of a checkpoint. |
|
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('/') |
|
255 | if contents_mgr.backend == 'local_file': | |
258 | self.log.info("restoring %s from checkpoint %s", path, checkpoint_id) |
|
256 | # We know that the file is in the local filesystem, so just copy | |
259 | os_checkpoint_path = self.checkpoint_path(checkpoint_id, path) |
|
257 | # from our base location to the location expected by content | |
260 | if not os.path.isfile(os_checkpoint_path): |
|
258 | src_path = self.checkpoint_path(checkpoint_id, path) | |
261 | self.no_such_checkpoint(path, checkpoint_id) |
|
259 | dest_path = contents_mgr._get_os_path(path) | |
262 | if type == 'notebook': |
|
260 | self._copy(src_path, dest_path) | |
263 | return { |
|
|||
264 | 'type': type, |
|
|||
265 | 'content': self._read_notebook( |
|
|||
266 | os_checkpoint_path, |
|
|||
267 | as_version=4, |
|
|||
268 | ), |
|
|||
269 | } |
|
|||
270 | else: |
|
261 | else: | |
271 | content, format = self._read_file(os_checkpoint_path, format=None) |
|
262 | super(FileCheckpointManager, self).restore_checkpoint( | |
272 | return { |
|
263 | contents_mgr, checkpoint_id, path | |
273 | 'type': type, |
|
264 | ) | |
274 | 'content': content, |
|
|||
275 | 'format': format, |
|
|||
276 | } |
|
|||
277 |
|
265 | |||
|
266 | # ContentsManager-independent checkpoint API | |||
278 | def rename_checkpoint(self, checkpoint_id, old_path, new_path): |
|
267 | def rename_checkpoint(self, checkpoint_id, old_path, new_path): | |
279 | """Rename a checkpoint from old_path to new_path.""" |
|
268 | """Rename a checkpoint from old_path to new_path.""" | |
280 | old_cp_path = self.checkpoint_path(checkpoint_id, old_path) |
|
269 | old_cp_path = self.checkpoint_path(checkpoint_id, old_path) | |
@@ -341,6 +330,64 b' class FileCheckpointManager(FileManagerMixin, CheckpointManager):' | |||||
341 | ) |
|
330 | ) | |
342 | return info |
|
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 | # Error Handling |
|
391 | # Error Handling | |
345 | def no_such_checkpoint(self, path, checkpoint_id): |
|
392 | def no_such_checkpoint(self, path, checkpoint_id): | |
346 | raise web.HTTPError( |
|
393 | raise web.HTTPError( | |
@@ -421,6 +468,9 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||||
421 | def _checkpoint_manager_class_default(self): |
|
468 | def _checkpoint_manager_class_default(self): | |
422 | return FileCheckpointManager |
|
469 | return FileCheckpointManager | |
423 |
|
470 | |||
|
471 | def _backend_default(self): | |||
|
472 | return 'local_file' | |||
|
473 | ||||
424 | def is_hidden(self, path): |
|
474 | def is_hidden(self, path): | |
425 | """Does the API style path correspond to a hidden directory or file? |
|
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 | self._save_notebook(os_path, nb) |
|
731 | self._save_notebook(os_path, nb) | |
682 | # One checkpoint should always exist for notebooks. |
|
732 | # One checkpoint should always exist for notebooks. | |
683 | if not self.checkpoint_manager.list_checkpoints(path): |
|
733 | if not self.checkpoint_manager.list_checkpoints(path): | |
684 |
self. |
|
734 | self.create_checkpoint(path) | |
685 | nb, |
|
|||
686 | path, |
|
|||
687 | ) |
|
|||
688 | elif model['type'] == 'file': |
|
735 | elif model['type'] == 'file': | |
689 | # Missing format will be handled internally by _save_file. |
|
736 | # Missing format will be handled internally by _save_file. | |
690 | self._save_file(os_path, model['content'], model.get('format')) |
|
737 | self._save_file(os_path, model['content'], model.get('format')) |
@@ -11,7 +11,6 b' import re' | |||||
11 |
|
11 | |||
12 | from tornado.web import HTTPError |
|
12 | from tornado.web import HTTPError | |
13 |
|
13 | |||
14 | from IPython import nbformat |
|
|||
15 | from IPython.config.configurable import LoggingConfigurable |
|
14 | from IPython.config.configurable import LoggingConfigurable | |
16 | from IPython.nbformat import sign, validate, ValidationError |
|
15 | from IPython.nbformat import sign, validate, ValidationError | |
17 | from IPython.nbformat.v4 import new_notebook |
|
16 | from IPython.nbformat.v4 import new_notebook | |
@@ -34,6 +33,28 b' class CheckpointManager(LoggingConfigurable):' | |||||
34 | """ |
|
33 | """ | |
35 | Base class for managing checkpoints for a ContentsManager. |
|
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 | def create_file_checkpoint(self, content, format, path): |
|
58 | def create_file_checkpoint(self, content, format, path): | |
38 | """Create a checkpoint of the current state of a file |
|
59 | """Create a checkpoint of the current state of a file | |
39 |
|
60 | |||
@@ -159,6 +180,7 b' class ContentsManager(LoggingConfigurable):' | |||||
159 | checkpoint_manager_class = Type(CheckpointManager, config=True) |
|
180 | checkpoint_manager_class = Type(CheckpointManager, config=True) | |
160 | checkpoint_manager = Instance(CheckpointManager, config=True) |
|
181 | checkpoint_manager = Instance(CheckpointManager, config=True) | |
161 | checkpoint_manager_kwargs = Dict(allow_none=False, config=True) |
|
182 | checkpoint_manager_kwargs = Dict(allow_none=False, config=True) | |
|
183 | backend = Unicode(default_value="") | |||
162 |
|
184 | |||
163 | def _checkpoint_manager_default(self): |
|
185 | def _checkpoint_manager_default(self): | |
164 | return self.checkpoint_manager_class(**self.checkpoint_manager_kwargs) |
|
186 | return self.checkpoint_manager_class(**self.checkpoint_manager_kwargs) | |
@@ -502,35 +524,16 b' class ContentsManager(LoggingConfigurable):' | |||||
502 | # Part 3: Checkpoints API |
|
524 | # Part 3: Checkpoints API | |
503 | def create_checkpoint(self, path): |
|
525 | def create_checkpoint(self, path): | |
504 | """Create a checkpoint.""" |
|
526 | """Create a checkpoint.""" | |
505 | model = self.get(path, content=True) |
|
527 | return self.checkpoint_manager.create_checkpoint(self, path) | |
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) |
|
|||
521 |
|
528 | |||
522 | def restore_checkpoint(self, checkpoint_id, path): |
|
529 | def restore_checkpoint(self, checkpoint_id, path): | |
523 | """ |
|
530 | """ | |
524 | Restore a checkpoint. |
|
531 | Restore a checkpoint. | |
525 | """ |
|
532 | """ | |
526 | return self.save( |
|
533 | self.checkpoint_manager.restore_checkpoint(self, checkpoint_id, path) | |
527 | model=self.checkpoint_manager.get_checkpoint( |
|
534 | ||
528 | checkpoint_id, |
|
535 | def list_checkpoints(self, path): | |
529 | path, |
|
536 | return self.checkpoint_manager.list_checkpoints(path) | |
530 | self.get(path, content=False)['type'] |
|
|||
531 | ), |
|
|||
532 | path=path, |
|
|||
533 | ) |
|
|||
534 |
|
537 | |||
535 | def delete_checkpoint(self, checkpoint_id, path): |
|
538 | def delete_checkpoint(self, checkpoint_id, path): | |
536 | return self.checkpoint_manager.delete_checkpoint(checkpoint_id, path) |
|
539 | return self.checkpoint_manager.delete_checkpoint(checkpoint_id, path) |
@@ -614,3 +614,25 b' class APITest(NotebookTestBase):' | |||||
614 | with TemporaryDirectory() as td: |
|
614 | with TemporaryDirectory() as td: | |
615 | with self.patch_cp_root(td): |
|
615 | with self.patch_cp_root(td): | |
616 | self.test_file_checkpoints() |
|
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