Show More
@@ -8,7 +8,7 b' Classes for managing Checkpoints.' | |||||
8 | from IPython.config.configurable import LoggingConfigurable |
|
8 | from IPython.config.configurable import LoggingConfigurable | |
9 |
|
9 | |||
10 |
|
10 | |||
11 |
class Checkpoint |
|
11 | class Checkpoints(LoggingConfigurable): | |
12 | """ |
|
12 | """ | |
13 | Base class for managing checkpoints for a ContentsManager. |
|
13 | Base class for managing checkpoints for a ContentsManager. | |
14 |
|
14 | |||
@@ -51,20 +51,25 b' class CheckpointManager(LoggingConfigurable):' | |||||
51 | self.delete_checkpoint(checkpoint['id'], path) |
|
51 | self.delete_checkpoint(checkpoint['id'], path) | |
52 |
|
52 | |||
53 |
|
53 | |||
54 | class GenericCheckpointMixin(object): |
|
54 | class GenericCheckpointsMixin(object): | |
55 | """ |
|
55 | """ | |
56 |
Helper for creating Checkpoint |
|
56 | Helper for creating Checkpoints subclasses that can be used with any | |
57 | ContentsManager. |
|
57 | ContentsManager. | |
58 |
|
58 | |||
59 |
Provides an implementation of `create_checkpoint` |
|
59 | Provides a ContentsManager-agnostic implementation of `create_checkpoint` | |
60 | in terms of the following operations: |
|
60 | and `restore_checkpoint` in terms of the following operations: | |
61 |
|
61 | |||
62 | create_file_checkpoint(self, content, format, path) |
|
62 | create_file_checkpoint(self, content, format, path) | |
63 | create_notebook_checkpoint(self, nb, path) |
|
63 | create_notebook_checkpoint(self, nb, path) | |
64 | get_checkpoint(self, checkpoint_id, path, type) |
|
64 | get_checkpoint(self, checkpoint_id, path, type) | |
65 |
|
65 | |||
66 | **Any** valid CheckpointManager implementation should also be valid when |
|
66 | To create a generic CheckpointManager, add this mixin to a class that | |
67 | this mixin is applied. |
|
67 | implement the above three methods plus the remaining Checkpoints API | |
|
68 | methods: | |||
|
69 | ||||
|
70 | delete_checkpoint(self, checkpoint_id, path) | |||
|
71 | list_checkpoints(self, path) | |||
|
72 | rename_checkpoint(self, checkpoint_id, old_path, new_path) | |||
68 | """ |
|
73 | """ | |
69 |
|
74 | |||
70 | def create_checkpoint(self, contents_mgr, path): |
|
75 | def create_checkpoint(self, contents_mgr, path): |
@@ -1,5 +1,5 b'' | |||||
1 | """ |
|
1 | """ | |
2 |
File-based Checkpoint |
|
2 | File-based Checkpoints implementations. | |
3 | """ |
|
3 | """ | |
4 | import os |
|
4 | import os | |
5 | import shutil |
|
5 | import shutil | |
@@ -7,8 +7,8 b' import shutil' | |||||
7 | from tornado.web import HTTPError |
|
7 | from tornado.web import HTTPError | |
8 |
|
8 | |||
9 | from .checkpoints import ( |
|
9 | from .checkpoints import ( | |
10 |
Checkpoint |
|
10 | Checkpoints, | |
11 | GenericCheckpointMixin, |
|
11 | GenericCheckpointsMixin, | |
12 | ) |
|
12 | ) | |
13 | from .fileio import FileManagerMixin |
|
13 | from .fileio import FileManagerMixin | |
14 |
|
14 | |||
@@ -18,12 +18,12 b' from IPython.utils.py3compat import getcwd' | |||||
18 | from IPython.utils.traitlets import Unicode |
|
18 | from IPython.utils.traitlets import Unicode | |
19 |
|
19 | |||
20 |
|
20 | |||
21 |
class FileCheckpoint |
|
21 | class FileCheckpoints(FileManagerMixin, Checkpoints): | |
22 | """ |
|
22 | """ | |
23 |
A Checkpoint |
|
23 | A Checkpoints that caches checkpoints for files in adjacent | |
24 | directories. |
|
24 | directories. | |
25 |
|
25 | |||
26 |
Only works with FileContentsManager. Use GenericFileCheckpoint |
|
26 | Only works with FileContentsManager. Use GenericFileCheckpoints if | |
27 | you want file-based checkpoints with another ContentsManager. |
|
27 | you want file-based checkpoints with another ContentsManager. | |
28 | """ |
|
28 | """ | |
29 |
|
29 | |||
@@ -136,10 +136,9 b' class FileCheckpointManager(FileManagerMixin, CheckpointManager):' | |||||
136 | ) |
|
136 | ) | |
137 |
|
137 | |||
138 |
|
138 | |||
139 |
class GenericFileCheckpoint |
|
139 | class GenericFileCheckpoints(GenericCheckpointsMixin, FileCheckpoints): | |
140 | FileCheckpointManager): |
|
|||
141 | """ |
|
140 | """ | |
142 |
Local filesystem Checkpoint |
|
141 | Local filesystem Checkpoints that works with any conforming | |
143 | ContentsManager. |
|
142 | ContentsManager. | |
144 | """ |
|
143 | """ | |
145 | def create_file_checkpoint(self, content, format, path): |
|
144 | def create_file_checkpoint(self, content, format, path): |
@@ -30,7 +30,7 b' class FileManagerMixin(object):' | |||||
30 | Provides facilities for reading, writing, and copying both notebooks and |
|
30 | Provides facilities for reading, writing, and copying both notebooks and | |
31 | generic files. |
|
31 | generic files. | |
32 |
|
32 | |||
33 |
Shared by FileContentsManager and FileCheckpoint |
|
33 | Shared by FileContentsManager and FileCheckpoints. | |
34 |
|
34 | |||
35 | Note |
|
35 | Note | |
36 | ---- |
|
36 | ---- |
@@ -11,7 +11,7 b' import mimetypes' | |||||
11 |
|
11 | |||
12 | from tornado import web |
|
12 | from tornado import web | |
13 |
|
13 | |||
14 |
from .filecheckpoints import FileCheckpoint |
|
14 | from .filecheckpoints import FileCheckpoints | |
15 | from .fileio import FileManagerMixin |
|
15 | from .fileio import FileManagerMixin | |
16 | from .manager import ContentsManager |
|
16 | from .manager import ContentsManager | |
17 |
|
17 | |||
@@ -121,8 +121,8 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||||
121 | if not os.path.isdir(new): |
|
121 | if not os.path.isdir(new): | |
122 | raise TraitError("%r is not a directory" % new) |
|
122 | raise TraitError("%r is not a directory" % new) | |
123 |
|
123 | |||
124 |
def _checkpoint |
|
124 | def _checkpoints_class_default(self): | |
125 |
return FileCheckpoint |
|
125 | return FileCheckpoints | |
126 |
|
126 | |||
127 | def is_hidden(self, path): |
|
127 | def is_hidden(self, path): | |
128 | """Does the API style path correspond to a hidden directory or file? |
|
128 | """Does the API style path correspond to a hidden directory or file? | |
@@ -383,7 +383,7 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||||
383 | self.check_and_sign(nb, path) |
|
383 | self.check_and_sign(nb, path) | |
384 | self._save_notebook(os_path, nb) |
|
384 | self._save_notebook(os_path, nb) | |
385 | # One checkpoint should always exist for notebooks. |
|
385 | # One checkpoint should always exist for notebooks. | |
386 |
if not self.checkpoint |
|
386 | if not self.checkpoints.list_checkpoints(path): | |
387 | self.create_checkpoint(path) |
|
387 | self.create_checkpoint(path) | |
388 | elif model['type'] == 'file': |
|
388 | elif model['type'] == 'file': | |
389 | # Missing format will be handled internally by _save_file. |
|
389 | # Missing format will be handled internally by _save_file. | |
@@ -421,7 +421,7 b' class FileContentsManager(FileManagerMixin, ContentsManager):' | |||||
421 | # Don't delete non-empty directories. |
|
421 | # Don't delete non-empty directories. | |
422 | # A directory containing only leftover checkpoints is |
|
422 | # A directory containing only leftover checkpoints is | |
423 | # considered empty. |
|
423 | # considered empty. | |
424 |
cp_dir = getattr(self.checkpoint |
|
424 | cp_dir = getattr(self.checkpoints, 'checkpoint_dir', None) | |
425 | for entry in listing: |
|
425 | for entry in listing: | |
426 | if entry != cp_dir: |
|
426 | if entry != cp_dir: | |
427 | raise web.HTTPError(400, u'Directory %s not empty' % os_path) |
|
427 | raise web.HTTPError(400, u'Directory %s not empty' % os_path) |
@@ -11,7 +11,7 b' import re' | |||||
11 |
|
11 | |||
12 | from tornado.web import HTTPError |
|
12 | from tornado.web import HTTPError | |
13 |
|
13 | |||
14 |
from .checkpoints import Checkpoint |
|
14 | from .checkpoints import Checkpoints | |
15 | from IPython.config.configurable import LoggingConfigurable |
|
15 | from IPython.config.configurable import LoggingConfigurable | |
16 | from IPython.nbformat import sign, validate, ValidationError |
|
16 | from IPython.nbformat import sign, validate, ValidationError | |
17 | from IPython.nbformat.v4 import new_notebook |
|
17 | from IPython.nbformat.v4 import new_notebook | |
@@ -107,14 +107,14 b' class ContentsManager(LoggingConfigurable):' | |||||
107 | except Exception: |
|
107 | except Exception: | |
108 | self.log.error("Pre-save hook failed on %s", path, exc_info=True) |
|
108 | self.log.error("Pre-save hook failed on %s", path, exc_info=True) | |
109 |
|
109 | |||
110 |
checkpoint |
|
110 | checkpoints_class = Type(Checkpoints, config=True) | |
111 |
checkpoint |
|
111 | checkpoints = Instance(Checkpoints, config=True) | |
112 |
checkpoint |
|
112 | checkpoints_kwargs = Dict(allow_none=False, config=True) | |
113 |
|
113 | |||
114 |
def _checkpoint |
|
114 | def _checkpoints_default(self): | |
115 |
return self.checkpoint |
|
115 | return self.checkpoints_class(**self.checkpoints_kwargs) | |
116 |
|
116 | |||
117 |
def _checkpoint |
|
117 | def _checkpoints_kwargs_default(self): | |
118 | return dict( |
|
118 | return dict( | |
119 | parent=self, |
|
119 | parent=self, | |
120 | log=self.log, |
|
120 | log=self.log, | |
@@ -223,12 +223,12 b' class ContentsManager(LoggingConfigurable):' | |||||
223 | def delete(self, path): |
|
223 | def delete(self, path): | |
224 | """Delete a file/directory and any associated checkpoints.""" |
|
224 | """Delete a file/directory and any associated checkpoints.""" | |
225 | self.delete_file(path) |
|
225 | self.delete_file(path) | |
226 |
self.checkpoint |
|
226 | self.checkpoints.delete_all_checkpoints(path) | |
227 |
|
227 | |||
228 | def rename(self, old_path, new_path): |
|
228 | def rename(self, old_path, new_path): | |
229 | """Rename a file and any checkpoints associated with that file.""" |
|
229 | """Rename a file and any checkpoints associated with that file.""" | |
230 | self.rename_file(old_path, new_path) |
|
230 | self.rename_file(old_path, new_path) | |
231 |
self.checkpoint |
|
231 | self.checkpoints.rename_all_checkpoints(old_path, new_path) | |
232 |
|
232 | |||
233 | def update(self, model, path): |
|
233 | def update(self, model, path): | |
234 | """Update the file's path |
|
234 | """Update the file's path | |
@@ -453,16 +453,16 b' class ContentsManager(LoggingConfigurable):' | |||||
453 | # Part 3: Checkpoints API |
|
453 | # Part 3: Checkpoints API | |
454 | def create_checkpoint(self, path): |
|
454 | def create_checkpoint(self, path): | |
455 | """Create a checkpoint.""" |
|
455 | """Create a checkpoint.""" | |
456 |
return self.checkpoint |
|
456 | return self.checkpoints.create_checkpoint(self, path) | |
457 |
|
457 | |||
458 | def restore_checkpoint(self, checkpoint_id, path): |
|
458 | def restore_checkpoint(self, checkpoint_id, path): | |
459 | """ |
|
459 | """ | |
460 | Restore a checkpoint. |
|
460 | Restore a checkpoint. | |
461 | """ |
|
461 | """ | |
462 |
self.checkpoint |
|
462 | self.checkpoints.restore_checkpoint(self, checkpoint_id, path) | |
463 |
|
463 | |||
464 | def list_checkpoints(self, path): |
|
464 | def list_checkpoints(self, path): | |
465 |
return self.checkpoint |
|
465 | return self.checkpoints.list_checkpoints(path) | |
466 |
|
466 | |||
467 | def delete_checkpoint(self, checkpoint_id, path): |
|
467 | def delete_checkpoint(self, checkpoint_id, path): | |
468 |
return self.checkpoint |
|
468 | return self.checkpoints.delete_checkpoint(checkpoint_id, path) |
@@ -13,7 +13,7 b' pjoin = os.path.join' | |||||
13 |
|
13 | |||
14 | import requests |
|
14 | import requests | |
15 |
|
15 | |||
16 |
from ..filecheckpoints import GenericFileCheckpoint |
|
16 | from ..filecheckpoints import GenericFileCheckpoints | |
17 |
|
17 | |||
18 | from IPython.config import Config |
|
18 | from IPython.config import Config | |
19 | from IPython.html.utils import url_path_join, url_escape, to_os_path |
|
19 | from IPython.html.utils import url_path_join, url_escape, to_os_path | |
@@ -593,7 +593,7 b' class APITest(NotebookTestBase):' | |||||
593 | """ |
|
593 | """ | |
594 | Temporarily patch the root dir of our checkpoint manager. |
|
594 | Temporarily patch the root dir of our checkpoint manager. | |
595 | """ |
|
595 | """ | |
596 |
cpm = self.notebook.contents_manager.checkpoint |
|
596 | cpm = self.notebook.contents_manager.checkpoints | |
597 | old_dirname = cpm.root_dir |
|
597 | old_dirname = cpm.root_dir | |
598 | cpm.root_dir = dirname |
|
598 | cpm.root_dir = dirname | |
599 | try: |
|
599 | try: | |
@@ -603,7 +603,7 b' class APITest(NotebookTestBase):' | |||||
603 |
|
603 | |||
604 | def test_checkpoints_separate_root(self): |
|
604 | def test_checkpoints_separate_root(self): | |
605 | """ |
|
605 | """ | |
606 |
Test that FileCheckpoint |
|
606 | Test that FileCheckpoints functions correctly even when it's | |
607 | using a different root dir from FileContentsManager. This also keeps |
|
607 | using a different root dir from FileContentsManager. This also keeps | |
608 | the implementation honest for use with ContentsManagers that don't map |
|
608 | the implementation honest for use with ContentsManagers that don't map | |
609 | models to the filesystem |
|
609 | models to the filesystem | |
@@ -621,9 +621,16 b' class APITest(NotebookTestBase):' | |||||
621 |
|
621 | |||
622 | class GenericFileCheckpointsAPITest(APITest): |
|
622 | class GenericFileCheckpointsAPITest(APITest): | |
623 | """ |
|
623 | """ | |
624 |
Run the tests from APITest with GenericFileCheckpoint |
|
624 | Run the tests from APITest with GenericFileCheckpoints. | |
625 | """ |
|
625 | """ | |
626 |
|
||||
627 | config = Config() |
|
626 | config = Config() | |
628 |
config.FileContentsManager.checkpoint |
|
627 | config.FileContentsManager.checkpoints_class = GenericFileCheckpoints | |
629 | GenericFileCheckpointManager |
|
628 | ||
|
629 | def test_config_did_something(self): | |||
|
630 | ||||
|
631 | self.assertIsInstance( | |||
|
632 | self.notebook.contents_manager.checkpoints, | |||
|
633 | GenericFileCheckpoints, | |||
|
634 | ) | |||
|
635 | ||||
|
636 |
@@ -84,7 +84,7 b' class TestFileContentsManager(TestCase):' | |||||
84 | root = td |
|
84 | root = td | |
85 | os.mkdir(os.path.join(td, subd)) |
|
85 | os.mkdir(os.path.join(td, subd)) | |
86 | fm = FileContentsManager(root_dir=root) |
|
86 | fm = FileContentsManager(root_dir=root) | |
87 |
cpm = fm.checkpoint |
|
87 | cpm = fm.checkpoints | |
88 | cp_dir = cpm.checkpoint_path( |
|
88 | cp_dir = cpm.checkpoint_path( | |
89 | 'cp', 'test.ipynb' |
|
89 | 'cp', 'test.ipynb' | |
90 | ) |
|
90 | ) |
General Comments 0
You need to be logged in to leave comments.
Login now