|
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