##// END OF EJS Templates
add pre/post-save hooks...
Min RK -
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