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