Show More
@@ -16,8 +16,9 b' from tornado import web' | |||
|
16 | 16 | from .manager import ContentsManager |
|
17 | 17 | from IPython import nbformat |
|
18 | 18 | from IPython.utils.io import atomic_writing |
|
19 | from IPython.utils.importstring import import_item | |
|
19 | 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 | 22 | from IPython.utils.py3compat import getcwd, str_to_unicode |
|
22 | 23 | from IPython.utils import tz |
|
23 | 24 | from IPython.html.utils import is_hidden, to_os_path, to_api_path |
@@ -71,6 +72,40 b' class FileContentsManager(ContentsManager):' | |||
|
71 | 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 | 109 | def _root_dir_changed(self, name, old, new): |
|
75 | 110 | """Do a bit of validation of the root_dir.""" |
|
76 | 111 | if not os.path.isabs(new): |
@@ -404,6 +439,8 b' class FileContentsManager(ContentsManager):' | |||
|
404 | 439 | if 'content' not in model and model['type'] != 'directory': |
|
405 | 440 | raise web.HTTPError(400, u'No file content provided') |
|
406 | 441 | |
|
442 | self.run_pre_save_hook(model=model, path=path) | |
|
443 | ||
|
407 | 444 | # One checkpoint should always exist |
|
408 | 445 | if self.file_exists(path) and not self.list_checkpoints(path): |
|
409 | 446 | self.create_checkpoint(path) |
@@ -433,6 +470,9 b' class FileContentsManager(ContentsManager):' | |||
|
433 | 470 | model = self.get(path, content=False) |
|
434 | 471 | if validation_message: |
|
435 | 472 | model['message'] = validation_message |
|
473 | ||
|
474 | self.run_post_save_hook(model=model, os_path=os_path) | |
|
475 | ||
|
436 | 476 | return model |
|
437 | 477 | |
|
438 | 478 | def update(self, model, path): |
@@ -14,7 +14,9 b' from tornado.web import HTTPError' | |||
|
14 | 14 | from IPython.config.configurable import LoggingConfigurable |
|
15 | 15 | from IPython.nbformat import sign, validate, ValidationError |
|
16 | 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 | 21 | copy_pat = re.compile(r'\-Copy\d*\.') |
|
20 | 22 | |
@@ -60,6 +62,41 b' class ContentsManager(LoggingConfigurable):' | |||
|
60 | 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 | 100 | # ContentsManager API part 1: methods that must be |
|
64 | 101 | # implemented in subclasses. |
|
65 | 102 | |
@@ -142,7 +179,11 b' class ContentsManager(LoggingConfigurable):' | |||
|
142 | 179 | raise NotImplementedError('must be implemented in a subclass') |
|
143 | 180 | |
|
144 | 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 | 187 | raise NotImplementedError('must be implemented in a subclass') |
|
147 | 188 | |
|
148 | 189 | def update(self, model, path): |
General Comments 0
You need to be logged in to leave comments.
Login now