1515from jupyter_client .client import KernelClient
1616from nbformat import NotebookNode
1717from nbformat .v4 import output_from_msg
18- from traitlets import Any , Bool , Dict , Enum , Integer , List , Type , Unicode , default
18+ from traitlets import (
19+ Any ,
20+ Bool ,
21+ Callable ,
22+ Dict ,
23+ Enum ,
24+ Integer ,
25+ List ,
26+ Type ,
27+ Unicode ,
28+ default ,
29+ )
1930from traitlets .config .configurable import LoggingConfigurable
2031
2132from .exceptions import (
2637 DeadKernelError ,
2738)
2839from .output_widget import OutputWidget
29- from .util import ensure_async , run_sync , run_hook
40+ from .util import ensure_async , run_hook , run_sync
3041
3142
3243def timestamp (msg : Optional [Dict ] = None ) -> str :
@@ -261,43 +272,85 @@ class NotebookClient(LoggingConfigurable):
261272
262273 kernel_manager_class : KernelManager = Type (config = True , help = 'The kernel manager class to use.' )
263274
264- on_execution_start : t .Optional [t .Callable ] = Any (
275+ on_notebook_start : t .Optional [t .Callable ] = Callable (
265276 default_value = None ,
266277 allow_none = True ,
267- help = dedent ("""
268- Called after the kernel manager and kernel client are setup, and cells
269- are about to execute.
270- Called with kwargs `kernel_id`.
271- """ ),
278+ help = dedent (
279+ """
280+ A callable which executes after the kernel manager and kernel client are setup, and
281+ cells are about to execute.
282+ Called with kwargs `notebook`.
283+ """
284+ ),
272285 ).tag (config = True )
273286
274- on_cell_start : t .Optional [t .Callable ] = Any (
287+ on_notebook_complete : t .Optional [t .Callable ] = Callable (
275288 default_value = None ,
276289 allow_none = True ,
277- help = dedent ("""
278- A callable which executes before a cell is executed.
279- Called with kwargs `cell`, and `cell_index`.
280- """ ),
290+ help = dedent (
291+ """
292+ A callable which executes after the kernel is cleaned up.
293+ Called with kwargs `notebook`.
294+ """
295+ ),
281296 ).tag (config = True )
282297
283- on_cell_complete : t .Optional [t .Callable ] = Any (
298+ on_notebook_error : t .Optional [t .Callable ] = Callable (
284299 default_value = None ,
285300 allow_none = True ,
286- help = dedent ("""
287- A callable which executes after a cell execution is complete. It is
288- called even when a cell results in a failure.
289- Called with kwargs `cell`, and `cell_index`.
290- """ ),
301+ help = dedent (
302+ """
303+ A callable which executes when the notebook encounters an error.
304+ Called with kwargs `notebook`.
305+ """
306+ ),
291307 ).tag (config = True )
292308
293- on_cell_error : t .Optional [t .Callable ] = Any (
309+ on_cell_start : t .Optional [t .Callable ] = Callable (
294310 default_value = None ,
295311 allow_none = True ,
296- help = dedent ("""
297- A callable which executes when a cell execution results in an error.
298- This is executed even if errors are suppressed with `cell_allows_errors`.
299- Called with kwargs `cell`, and `cell_index`.
300- """ ),
312+ help = dedent (
313+ """
314+ A callable which executes before a cell is executed and before non-executing cells
315+ are skipped.
316+ Called with kwargs `cell` and `cell_index`.
317+ """
318+ ),
319+ ).tag (config = True )
320+
321+ on_cell_execute : t .Optional [t .Callable ] = Callable (
322+ default_value = None ,
323+ allow_none = True ,
324+ help = dedent (
325+ """
326+ A callable which executes just before a code cell is executed.
327+ Called with kwargs `cell` and `cell_index`.
328+ """
329+ ),
330+ ).tag (config = True )
331+
332+ on_cell_complete : t .Optional [t .Callable ] = Callable (
333+ default_value = None ,
334+ allow_none = True ,
335+ help = dedent (
336+ """
337+ A callable which executes after a cell execution is complete. It is
338+ called even when a cell results in a failure.
339+ Called with kwargs `cell` and `cell_index`.
340+ """
341+ ),
342+ ).tag (config = True )
343+
344+ on_cell_error : t .Optional [t .Callable ] = Callable (
345+ default_value = None ,
346+ allow_none = True ,
347+ help = dedent (
348+ """
349+ A callable which executes when a cell execution results in an error.
350+ This is executed even if errors are suppressed with `cell_allows_errors`.
351+ Called with kwargs `cell` and `cell_index`.
352+ """
353+ ),
301354 ).tag (config = True )
302355
303356 @default ('kernel_manager_class' )
@@ -481,7 +534,7 @@ async def async_start_new_kernel_client(self) -> KernelClient:
481534 await self ._async_cleanup_kernel ()
482535 raise
483536 self .kc .allow_stdin = False
484- run_hook (sself . on_execution_start )
537+ await run_hook (self . on_notebook_start , notebook = self . nb )
485538 return self .kc
486539
487540 start_new_kernel_client = run_sync (async_start_new_kernel_client )
@@ -553,10 +606,13 @@ def on_signal():
553606 await self .async_start_new_kernel_client ()
554607 try :
555608 yield
609+ except RuntimeError as e :
610+ await run_hook (self .on_notebook_error , notebook = self .nb )
611+ raise e
556612 finally :
557613 if cleanup_kc :
558614 await self ._async_cleanup_kernel ()
559-
615+ await run_hook ( self . on_notebook_complete , notebook = self . nb )
560616 atexit .unregister (self ._cleanup_kernel )
561617 try :
562618 loop .remove_signal_handler (signal .SIGINT )
@@ -785,11 +841,9 @@ def _passed_deadline(self, deadline: int) -> bool:
785841 return True
786842 return False
787843
788- def _check_raise_for_error (
789- self ,
790- cell : NotebookNode ,
791- cell_index : int ,
792- exec_reply : t .Optional [t .Dict ]) -> None :
844+ async def _check_raise_for_error (
845+ self , cell : NotebookNode , cell_index : int , exec_reply : t .Optional [t .Dict ]
846+ ) -> None :
793847
794848 if exec_reply is None :
795849 return None
@@ -803,11 +857,9 @@ def _check_raise_for_error(
803857 or exec_reply_content .get ('ename' ) in self .allow_error_names
804858 or "raises-exception" in cell .metadata .get ("tags" , [])
805859 )
806-
807- if (exec_reply is not None ) and exec_reply ['content' ]['status' ] == 'error' :
808- run_hook (self .on_cell_error , cell = cell , cell_index = cell_index )
809- if self .force_raise_errors or not cell_allows_errors :
810- raise CellExecutionError .from_cell_and_msg (cell , exec_reply ['content' ])
860+ await run_hook (self .on_cell_error , cell = cell , cell_index = cell_index )
861+ if not cell_allows_errors :
862+ raise CellExecutionError .from_cell_and_msg (cell , exec_reply_content )
811863
812864 async def async_execute_cell (
813865 self ,
@@ -850,6 +902,9 @@ async def async_execute_cell(
850902 The cell which was just processed.
851903 """
852904 assert self .kc is not None
905+
906+ await run_hook (self .on_cell_start , cell = cell , cell_index = cell_index )
907+
853908 if cell .cell_type != 'code' or not cell .source .strip ():
854909 self .log .debug ("Skipping non-executing cell %s" , cell_index )
855910 return cell
@@ -867,13 +922,13 @@ async def async_execute_cell(
867922 self .allow_errors or "raises-exception" in cell .metadata .get ("tags" , [])
868923 )
869924
870- run_hook (self .on_cell_start , cell = cell , cell_index = cell_index )
925+ await run_hook (self .on_cell_execute , cell = cell , cell_index = cell_index )
871926 parent_msg_id = await ensure_async (
872927 self .kc .execute (
873928 cell .source , store_history = store_history , stop_on_error = not cell_allows_errors
874929 )
875930 )
876- run_hook (self .on_cell_complete , cell = cell , cell_index = cell_index )
931+ await run_hook (self .on_cell_complete , cell = cell , cell_index = cell_index )
877932 # We launched a code cell to execute
878933 self .code_cells_executed += 1
879934 exec_timeout = self ._get_timeout (cell )
@@ -907,7 +962,7 @@ async def async_execute_cell(
907962
908963 if execution_count :
909964 cell ['execution_count' ] = execution_count
910- self ._check_raise_for_error (cell , cell_index , exec_reply )
965+ await self ._check_raise_for_error (cell , cell_index , exec_reply )
911966 self .nb ['cells' ][cell_index ] = cell
912967 return cell
913968
0 commit comments