|
24 | 24 | CellExecutionComplete,
|
25 | 25 | CellExecutionError
|
26 | 26 | )
|
27 |
| -from .util import run_sync, ensure_async |
| 27 | +from .util import run_sync, ensure_async, run_hook |
28 | 28 | from .output_widget import OutputWidget
|
29 | 29 |
|
30 | 30 |
|
@@ -223,6 +223,35 @@ class NotebookClient(LoggingConfigurable):
|
223 | 223 |
|
224 | 224 | kernel_manager_class = Type(config=True, help='The kernel manager class to use.')
|
225 | 225 |
|
| 226 | + on_kernel_create = Any( |
| 227 | + default_value=None, |
| 228 | + allow_none=True, |
| 229 | + help="""A callable which executes when the kernel is created.""", |
| 230 | + ).tag(config=True) |
| 231 | + |
| 232 | + on_cell_start = Any( |
| 233 | + default_value=None, |
| 234 | + allow_none=True, |
| 235 | + help="""A callable which executes before a cell is executed.""", |
| 236 | + ).tag(config=True) |
| 237 | + |
| 238 | + on_cell_complete = Any( |
| 239 | + default_value=None, |
| 240 | + allow_none=True, |
| 241 | + help=dedent( |
| 242 | + """ |
| 243 | + A callable which executes after a cell execution is complete. It is |
| 244 | + called even when a cell results in a failure. |
| 245 | + """ |
| 246 | + ), |
| 247 | + ).tag(config=True) |
| 248 | + |
| 249 | + on_cell_error = Any( |
| 250 | + default_value=None, |
| 251 | + allow_none=True, |
| 252 | + help="""A callable which executes when a cell execution results in an error.""", |
| 253 | + ).tag(config=True) |
| 254 | + |
226 | 255 | @default('kernel_manager_class')
|
227 | 256 | def _kernel_manager_class_default(self):
|
228 | 257 | """Use a dynamic default to avoid importing jupyter_client at startup"""
|
@@ -378,6 +407,7 @@ async def async_start_new_kernel_client(self, **kwargs):
|
378 | 407 |
|
379 | 408 | kernel_id = await ensure_async(self.km.start_kernel(extra_arguments=self.extra_arguments,
|
380 | 409 | **kwargs))
|
| 410 | + run_hook(self.on_kernel_create, kernel_id) |
381 | 411 |
|
382 | 412 | # if self.km is not a KernelManager, it's probably a MultiKernelManager
|
383 | 413 | try:
|
@@ -661,14 +691,15 @@ def _passed_deadline(self, deadline):
|
661 | 691 | return True
|
662 | 692 | return False
|
663 | 693 |
|
664 |
| - def _check_raise_for_error(self, cell, exec_reply): |
| 694 | + def _check_raise_for_error(self, cell, cell_index, exec_reply): |
665 | 695 | cell_allows_errors = self.allow_errors or "raises-exception" in cell.metadata.get(
|
666 | 696 | "tags", []
|
667 | 697 | )
|
668 | 698 |
|
669 |
| - if self.force_raise_errors or not cell_allows_errors: |
670 |
| - if (exec_reply is not None) and exec_reply['content']['status'] == 'error': |
671 |
| - raise CellExecutionError.from_cell_and_msg(cell, exec_reply['content']) |
| 699 | + if (exec_reply is not None) and exec_reply['content']['status'] == 'error': |
| 700 | + run_hook(self.on_cell_error, cell, cell_index) |
| 701 | + if self.force_raise_errors or not cell_allows_errors: |
| 702 | + raise CellExecutionError.from_cell_and_msg(cell, exec_reply['content']) |
672 | 703 |
|
673 | 704 | async def async_execute_cell(self, cell, cell_index, execution_count=None, store_history=True):
|
674 | 705 | """
|
@@ -712,6 +743,7 @@ async def async_execute_cell(self, cell, cell_index, execution_count=None, store
|
712 | 743 | cell['metadata']['execution'] = {}
|
713 | 744 |
|
714 | 745 | self.log.debug("Executing cell:\n%s", cell.source)
|
| 746 | + run_hook(self.on_cell_start, cell, cell_index) |
715 | 747 | parent_msg_id = await ensure_async(
|
716 | 748 | self.kc.execute(
|
717 | 749 | cell.source,
|
@@ -744,7 +776,7 @@ async def async_execute_cell(self, cell, cell_index, execution_count=None, store
|
744 | 776 |
|
745 | 777 | if execution_count:
|
746 | 778 | cell['execution_count'] = execution_count
|
747 |
| - self._check_raise_for_error(cell, exec_reply) |
| 779 | + self._check_raise_for_error(cell, cell_index, exec_reply) |
748 | 780 | self.nb['cells'][cell_index] = cell
|
749 | 781 | return cell
|
750 | 782 |
|
|
0 commit comments