Show More
@@ -16,8 +16,9 b' from tornado import web' | |||||
16 | from .manager import ContentsManager |
|
16 | from .manager import ContentsManager | |
17 | from IPython import nbformat |
|
17 | from IPython import nbformat | |
18 | from IPython.utils.io import atomic_writing |
|
18 | from IPython.utils.io import atomic_writing | |
|
19 | from IPython.utils.importstring import import_item | |||
19 | from IPython.utils.path import ensure_dir_exists |
|
20 | from IPython.utils.path import ensure_dir_exists | |
20 | from IPython.utils.traitlets import Unicode, Bool, TraitError |
|
21 | from IPython.utils.traitlets import Any, Unicode, Bool, TraitError | |
21 | from IPython.utils.py3compat import getcwd, str_to_unicode |
|
22 | from IPython.utils.py3compat import getcwd, str_to_unicode | |
22 | from IPython.utils import tz |
|
23 | from IPython.utils import tz | |
23 | from IPython.html.utils import is_hidden, to_os_path, to_api_path |
|
24 | from IPython.html.utils import is_hidden, to_os_path, to_api_path | |
@@ -71,6 +72,40 b' class FileContentsManager(ContentsManager):' | |||||
71 | Use `ipython nbconvert --to python [notebook]` instead. |
|
72 | Use `ipython nbconvert --to python [notebook]` instead. | |
72 | """) |
|
73 | """) | |
73 |
|
74 | |||
|
75 | post_save_hook = Any(None, config=True, | |||
|
76 | help="""Python callable or importstring thereof | |||
|
77 | ||||
|
78 | to be called on the path of a file just saved. | |||
|
79 | ||||
|
80 | This can be used to | |||
|
81 | This can be used to process the file on disk, | |||
|
82 | such as converting the notebook to other formats, such as Python or HTML via nbconvert | |||
|
83 | ||||
|
84 | It will be called as (all arguments passed by keyword): | |||
|
85 | ||||
|
86 | hook(os_path=os_path, model=model, contents_manager=instance) | |||
|
87 | ||||
|
88 | path: the filesystem path to the file just written | |||
|
89 | model: the model representing the file | |||
|
90 | contents_manager: this ContentsManager instance | |||
|
91 | """ | |||
|
92 | ) | |||
|
93 | def _post_save_hook_changed(self, name, old, new): | |||
|
94 | if new and isinstance(new, string_types): | |||
|
95 | self.post_save_hook = import_item(self.post_save_hook) | |||
|
96 | elif new: | |||
|
97 | if not callable(new): | |||
|
98 | raise TraitError("post_save_hook must be callable") | |||
|
99 | ||||
|
100 | def run_post_save_hook(self, model, os_path): | |||
|
101 | """Run the post-save hook if defined, and log errors""" | |||
|
102 | if self.post_save_hook: | |||
|
103 | try: | |||
|
104 | self.log.debug("Running post-save hook on %s", os_path) | |||
|
105 | self.post_save_hook(os_path=os_path, model=model, contents_manager=self) | |||
|
106 | except Exception: | |||
|
107 | self.log.error("Post-save hook failed on %s", os_path, exc_info=True) | |||
|
108 | ||||
74 | def _root_dir_changed(self, name, old, new): |
|
109 | def _root_dir_changed(self, name, old, new): | |
75 | """Do a bit of validation of the root_dir.""" |
|
110 | """Do a bit of validation of the root_dir.""" | |
76 | if not os.path.isabs(new): |
|
111 | if not os.path.isabs(new): | |
@@ -404,6 +439,8 b' class FileContentsManager(ContentsManager):' | |||||
404 | if 'content' not in model and model['type'] != 'directory': |
|
439 | if 'content' not in model and model['type'] != 'directory': | |
405 | raise web.HTTPError(400, u'No file content provided') |
|
440 | raise web.HTTPError(400, u'No file content provided') | |
406 |
|
441 | |||
|
442 | self.run_pre_save_hook(model=model, path=path) | |||
|
443 | ||||
407 | # One checkpoint should always exist |
|
444 | # One checkpoint should always exist | |
408 | if self.file_exists(path) and not self.list_checkpoints(path): |
|
445 | if self.file_exists(path) and not self.list_checkpoints(path): | |
409 | self.create_checkpoint(path) |
|
446 | self.create_checkpoint(path) | |
@@ -433,6 +470,9 b' class FileContentsManager(ContentsManager):' | |||||
433 | model = self.get(path, content=False) |
|
470 | model = self.get(path, content=False) | |
434 | if validation_message: |
|
471 | if validation_message: | |
435 | model['message'] = validation_message |
|
472 | model['message'] = validation_message | |
|
473 | ||||
|
474 | self.run_post_save_hook(model=model, os_path=os_path) | |||
|
475 | ||||
436 | return model |
|
476 | return model | |
437 |
|
477 | |||
438 | def update(self, model, path): |
|
478 | def update(self, model, path): |
@@ -14,7 +14,9 b' from tornado.web import HTTPError' | |||||
14 | from IPython.config.configurable import LoggingConfigurable |
|
14 | from IPython.config.configurable import LoggingConfigurable | |
15 | from IPython.nbformat import sign, validate, ValidationError |
|
15 | from IPython.nbformat import sign, validate, ValidationError | |
16 | from IPython.nbformat.v4 import new_notebook |
|
16 | from IPython.nbformat.v4 import new_notebook | |
17 | from IPython.utils.traitlets import Instance, Unicode, List |
|
17 | from IPython.utils.importstring import import_item | |
|
18 | from IPython.utils.traitlets import Instance, Unicode, List, Any, TraitError | |||
|
19 | from IPython.utils.py3compat import string_types | |||
18 |
|
20 | |||
19 | copy_pat = re.compile(r'\-Copy\d*\.') |
|
21 | copy_pat = re.compile(r'\-Copy\d*\.') | |
20 |
|
22 | |||
@@ -60,6 +62,41 b' class ContentsManager(LoggingConfigurable):' | |||||
60 | help="The base name used when creating untitled directories." |
|
62 | help="The base name used when creating untitled directories." | |
61 | ) |
|
63 | ) | |
62 |
|
64 | |||
|
65 | pre_save_hook = Any(None, config=True, | |||
|
66 | help="""Python callable or importstring thereof | |||
|
67 | ||||
|
68 | To be called on a contents model prior to save. | |||
|
69 | ||||
|
70 | This can be used to process the structure, | |||
|
71 | such as removing notebook outputs or other side effects that | |||
|
72 | should not be saved. | |||
|
73 | ||||
|
74 | It will be called as (all arguments passed by keyword): | |||
|
75 | ||||
|
76 | hook(path=path, model=model, contents_manager=self) | |||
|
77 | ||||
|
78 | model: the model to be saved. Includes file contents. | |||
|
79 | modifying this dict will affect the file that is stored. | |||
|
80 | path: the API path of the save destination | |||
|
81 | contents_manager: this ContentsManager instance | |||
|
82 | """ | |||
|
83 | ) | |||
|
84 | def _pre_save_hook_changed(self, name, old, new): | |||
|
85 | if new and isinstance(new, string_types): | |||
|
86 | self.pre_save_hook = import_item(self.pre_save_hook) | |||
|
87 | elif new: | |||
|
88 | if not callable(new): | |||
|
89 | raise TraitError("pre_save_hook must be callable") | |||
|
90 | ||||
|
91 | def run_pre_save_hook(self, model, path, **kwargs): | |||
|
92 | """Run the pre-save hook if defined, and log errors""" | |||
|
93 | if self.pre_save_hook: | |||
|
94 | try: | |||
|
95 | self.log.debug("Running pre-save hook on %s", path) | |||
|
96 | self.pre_save_hook(model=model, path=path, contents_manager=self, **kwargs) | |||
|
97 | except Exception: | |||
|
98 | self.log.error("Pre-save hook failed on %s", path, exc_info=True) | |||
|
99 | ||||
63 | # ContentsManager API part 1: methods that must be |
|
100 | # ContentsManager API part 1: methods that must be | |
64 | # implemented in subclasses. |
|
101 | # implemented in subclasses. | |
65 |
|
102 | |||
@@ -142,7 +179,11 b' class ContentsManager(LoggingConfigurable):' | |||||
142 | raise NotImplementedError('must be implemented in a subclass') |
|
179 | raise NotImplementedError('must be implemented in a subclass') | |
143 |
|
180 | |||
144 | def save(self, model, path): |
|
181 | def save(self, model, path): | |
145 |
"""Save the file or directory and return the model with no content. |
|
182 | """Save the file or directory and return the model with no content. | |
|
183 | ||||
|
184 | Save implementations should call self.run_pre_save_hook(model=model, path=path) | |||
|
185 | prior to writing any data. | |||
|
186 | """ | |||
146 | raise NotImplementedError('must be implemented in a subclass') |
|
187 | raise NotImplementedError('must be implemented in a subclass') | |
147 |
|
188 | |||
148 | def update(self, model, path): |
|
189 | def update(self, model, path): |
General Comments 0
You need to be logged in to leave comments.
Login now