@@ -19,24 +19,29 @@ def run_in_process(func):
1919 """ Decorator to run the `func` in a separated process for terminating all relevant processes properly. """
2020 @wraps (func )
2121 def _wrapper (* args , ** kwargs ):
22- # pylint: disable=bare-except
23- returned_value = Queue ()
24- kwargs = {** kwargs , 'returned_value ' : returned_value }
22+ # pylint: disable=broad-exception-caught, broad-exception-raised
23+ _output_queue = Queue ()
24+ kwargs = {** kwargs , '_output_queue ' : _output_queue }
2525
2626 json_path = None
2727
28+ output = {'failed' : True , 'traceback' : '' }
29+
2830 try :
2931 process = Process (target = func , args = args , kwargs = kwargs )
3032 process .start ()
3133
3234 path = args [0 ] if args else kwargs ['path' ]
3335 json_path = f'{ TMP_DIR } /{ process .pid } .json'
36+
3437 with open (json_path , 'w' , encoding = 'utf-8' ) as file :
3538 json .dump ({'path' : path }, file )
3639
37- output = returned_value .get ()
40+ output = _output_queue .get ()
3841 process .join ()
39- except :
42+ except Exception as e :
43+ output = {'failed' : True , 'traceback' : e }
44+
4045 # Terminate all relevant processes when something went wrong, e.g. Keyboard Interrupt
4146 for child in psutil .Process (process .pid ).children ():
4247 if psutil .pid_exists (child .pid ):
@@ -48,6 +53,9 @@ def _wrapper(*args, **kwargs):
4853 if json_path is not None and os .path .exists (json_path ):
4954 os .remove (json_path )
5055
56+ if kwargs .get ('raise_exception' , False ) and output ['failed' ]:
57+ raise Exception (output ['traceback' ])
58+
5159 return output
5260 return _wrapper
5361
@@ -130,7 +138,7 @@ def run_notebook(path, inputs=None, outputs=None, inputs_pos=1, replace_inputs_p
130138 working_dir = './' , execute_kwargs = None ,
131139 out_path_db = None , out_path_ipynb = None , out_path_html = None , remove_db = 'always' , add_timestamp = True ,
132140 hide_code_cells = False , mask_extra_code = False , display_links = True ,
133- raise_exception = False , return_notebook = False , returned_value = None ):
141+ raise_exception = False , return_notebook = False , _output_queue = None ):
134142 """ Execute a Jupyter Notebook programmatically.
135143 Heavily inspired by https://github.com/tritemio/nbrun.
136144
@@ -190,6 +198,9 @@ def run_notebook(path, inputs=None, outputs=None, inputs_pos=1, replace_inputs_p
190198 Note, that database exists only if inputs and/or outputs are provided.
191199 execute_kwargs : dict, optional
192200 Parameters of `:class:ExecutePreprocessor`.
201+ For example, you can provide timeout, kernel_name, resources (such as metadata)
202+ and other `nbclient.client.NotebookClient` arguments from :ref:`the NotebookClient doc page
203+ <https://nbclient.readthedocs.io/en/latest/reference/nbclient.html#nbclient.client.NotebookClient>`.
193204 add_timestamp : bool, optional
194205 Whether to add a cell with execution information at the beginning of the saved notebook.
195206 hide_code_cells : bool, optional
@@ -203,7 +214,7 @@ def run_notebook(path, inputs=None, outputs=None, inputs_pos=1, replace_inputs_p
203214 Whether to re-raise exceptions from the notebook.
204215 return_notebook : bool, optional
205216 Whether to return the notebook object from this function.
206- returned_value : None
217+ _output_queue : None
207218 Placeholder for the :func:`~.run_in_process` decorator to return this function result.
208219
209220 Returns
@@ -224,7 +235,7 @@ def run_notebook(path, inputs=None, outputs=None, inputs_pos=1, replace_inputs_p
224235 Executed notebook object.
225236 Note that this output is provided only if `return_notebook` is True.
226237 """
227- # pylint: disable=bare-except, lost-exception
238+ # pylint: disable=bare-except, lost-exception, return-in-finally
228239 import nbformat
229240 from jupyter_client .manager import KernelManager
230241 from nbconvert .preprocessors import ExecutePreprocessor
@@ -310,9 +321,6 @@ def run_notebook(path, inputs=None, outputs=None, inputs_pos=1, replace_inputs_p
310321 if outputs is not None :
311322 executor .kc = kernel_manager .client () # For compatibility with 5.x.x version of `nbconvert`
312323 executor .preprocess_cell (output_cell , {'metadata' : {'path' : working_dir }}, - 1 )
313-
314- if raise_exception :
315- raise
316324 finally :
317325 # Shutdown kernel
318326 kernel_manager .cleanup_resources ()
@@ -340,6 +348,11 @@ def run_notebook(path, inputs=None, outputs=None, inputs_pos=1, replace_inputs_p
340348 # Prepare execution results: execution state, notebook outputs and error info (if exists)
341349 if failed :
342350 exec_res = {'failed' : failed , 'failed cell number' : error_cell_num , 'traceback' : traceback_message }
351+
352+ # Re-raise exception if needed
353+ if raise_exception :
354+ _output_queue .put (exec_res )
355+ return None
343356 else :
344357 exec_res = {'failed' : failed , 'failed cell number' : None , 'traceback' : '' }
345358
@@ -372,8 +385,8 @@ def run_notebook(path, inputs=None, outputs=None, inputs_pos=1, replace_inputs_p
372385 if return_notebook :
373386 exec_res ['notebook' ] = notebook
374387
375- returned_value .put (exec_res ) # return for parent process
376- return
388+ _output_queue .put (exec_res ) # return for parent process
389+ return None
377390
378391# Mask functions for database operations cells
379392def mask_inputs_reading (notebook , pos ):
0 commit comments