4
4
import itertools
5
5
import json
6
6
import re
7
+ import warnings
7
8
from fnmatch import fnmatch
8
9
9
10
from ipython_genutils .importstring import import_item
@@ -126,10 +127,55 @@ def _validate_pre_save_hook(self, proposal):
126
127
value = import_item (self .pre_save_hook )
127
128
if not callable (value ):
128
129
raise TraitError ("pre_save_hook must be callable" )
130
+ if self .pre_save_hook is not None :
131
+ warnings .warn (
132
+ f"Overriding existing pre_save_hook ({ self .pre_save_hook .__name__ } ) with a new one ({ value .__name__ } )." ,
133
+ stacklevel = 2 ,
134
+ )
135
+ return value
136
+
137
+ post_save_hook = Any (
138
+ None ,
139
+ config = True ,
140
+ allow_none = True ,
141
+ help = """Python callable or importstring thereof
142
+
143
+ to be called on the path of a file just saved.
144
+
145
+ This can be used to process the file on disk,
146
+ such as converting the notebook to a script or HTML via nbconvert.
147
+
148
+ It will be called as (all arguments passed by keyword)::
149
+
150
+ hook(os_path=os_path, model=model, contents_manager=instance)
151
+
152
+ - path: the filesystem path to the file just written
153
+ - model: the model representing the file
154
+ - contents_manager: this ContentsManager instance
155
+ """ ,
156
+ )
157
+
158
+ @validate ("post_save_hook" )
159
+ def _validate_post_save_hook (self , proposal ):
160
+ value = proposal ["value" ]
161
+ if isinstance (value , str ):
162
+ value = import_item (value )
163
+ if not callable (value ):
164
+ raise TraitError ("post_save_hook must be callable" )
165
+ if self .post_save_hook is not None :
166
+ warnings .warn (
167
+ f"Overriding existing post_save_hook ({ self .post_save_hook .__name__ } ) with a new one ({ value .__name__ } )." ,
168
+ stacklevel = 2 ,
169
+ )
129
170
return value
130
171
131
172
def run_pre_save_hook (self , model , path , ** kwargs ):
132
173
"""Run the pre-save hook if defined, and log errors"""
174
+ warnings .warn (
175
+ "run_pre_save_hook is deprecated, use run_pre_save_hooks instead." ,
176
+ DeprecationWarning ,
177
+ stacklevel = 2 ,
178
+ )
133
179
if self .pre_save_hook :
134
180
try :
135
181
self .log .debug ("Running pre-save hook on %s" , path )
@@ -143,6 +189,77 @@ def run_pre_save_hook(self, model, path, **kwargs):
143
189
# which could cause frustrating data loss
144
190
self .log .error ("Pre-save hook failed on %s" , path , exc_info = True )
145
191
192
+ def run_post_save_hook (self , model , os_path ):
193
+ """Run the post-save hook if defined, and log errors"""
194
+ warnings .warn (
195
+ "run_post_save_hook is deprecated, use run_post_save_hooks instead." ,
196
+ DeprecationWarning ,
197
+ stacklevel = 2 ,
198
+ )
199
+ if self .post_save_hook :
200
+ try :
201
+ self .log .debug ("Running post-save hook on %s" , os_path )
202
+ self .post_save_hook (os_path = os_path , model = model , contents_manager = self )
203
+ except Exception as e :
204
+ self .log .error ("Post-save hook failed o-n %s" , os_path , exc_info = True )
205
+ raise HTTPError (500 , "Unexpected error while running post hook save: %s" % e ) from e
206
+
207
+ _pre_save_hooks = List ()
208
+ _post_save_hooks = List ()
209
+
210
+ def register_pre_save_hook (self , hook ):
211
+ if isinstance (hook , str ):
212
+ hook = import_item (hook )
213
+ if not callable (hook ):
214
+ raise RuntimeError ("hook must be callable" )
215
+ self ._pre_save_hooks .append (hook )
216
+
217
+ def register_post_save_hook (self , hook ):
218
+ if isinstance (hook , str ):
219
+ hook = import_item (hook )
220
+ if not callable (hook ):
221
+ raise RuntimeError ("hook must be callable" )
222
+ self ._post_save_hooks .append (hook )
223
+
224
+ def run_pre_save_hooks (self , model , path , ** kwargs ):
225
+ """Run the pre-save hooks if any, and log errors"""
226
+ pre_save_hooks = [self .pre_save_hook ] if self .pre_save_hook is not None else []
227
+ pre_save_hooks += self ._pre_save_hooks
228
+ for pre_save_hook in pre_save_hooks :
229
+ try :
230
+ self .log .debug ("Running pre-save hook on %s" , path )
231
+ pre_save_hook (model = model , path = path , contents_manager = self , ** kwargs )
232
+ except HTTPError :
233
+ # allow custom HTTPErrors to raise,
234
+ # rejecting the save with a message.
235
+ raise
236
+ except Exception :
237
+ # unhandled errors don't prevent saving,
238
+ # which could cause frustrating data loss
239
+ self .log .error (
240
+ "Pre-save hook %s failed on %s" ,
241
+ pre_save_hook .__name__ ,
242
+ path ,
243
+ exc_info = True ,
244
+ )
245
+
246
+ def run_post_save_hooks (self , model , os_path ):
247
+ """Run the post-save hooks if any, and log errors"""
248
+ post_save_hooks = [self .post_save_hook ] if self .post_save_hook is not None else []
249
+ post_save_hooks += self ._post_save_hooks
250
+ for post_save_hook in post_save_hooks :
251
+ try :
252
+ self .log .debug ("Running post-save hook on %s" , os_path )
253
+ post_save_hook (os_path = os_path , model = model , contents_manager = self )
254
+ except Exception as e :
255
+ self .log .error (
256
+ "Post-save %s hook failed on %s" ,
257
+ post_save_hook .__name__ ,
258
+ os_path ,
259
+ exc_info = True ,
260
+ )
261
+ raise HTTPError (500 , "Unexpected error while running post hook save: %s" % e ) from e
262
+
146
263
checkpoints_class = Type (Checkpoints , config = True )
147
264
checkpoints = Instance (Checkpoints , config = True )
148
265
checkpoints_kwargs = Dict (config = True )
0 commit comments