File size: 4,065 Bytes
d1ceb73 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
"""Exceptions for nbclient."""
from __future__ import annotations
from typing import Any
from nbformat import NotebookNode
class CellControlSignal(Exception): # noqa
"""
A custom exception used to indicate that the exception is used for cell
control actions (not the best model, but it's needed to cover existing
behavior without major refactors).
"""
pass
class CellTimeoutError(TimeoutError, CellControlSignal):
"""
A custom exception to capture when a cell has timed out during execution.
"""
@classmethod
def error_from_timeout_and_cell(
cls, msg: str, timeout: int, cell: NotebookNode
) -> CellTimeoutError:
"""Create an error from a timeout on a cell."""
if cell and cell.source:
src_by_lines = cell.source.strip().split("\n")
src = (
cell.source
if len(src_by_lines) < 11
else f"{src_by_lines[:5]}\n...\n{src_by_lines[-5:]}"
)
else:
src = "Cell contents not found."
return cls(timeout_err_msg.format(timeout=timeout, msg=msg, cell_contents=src))
class DeadKernelError(RuntimeError):
"""A dead kernel error."""
pass
class CellExecutionComplete(CellControlSignal):
"""
Used as a control signal for cell execution across execute_cell and
process_message function calls. Raised when all execution requests
are completed and no further messages are expected from the kernel
over zeromq channels.
"""
pass
class CellExecutionError(CellControlSignal):
"""
Custom exception to propagate exceptions that are raised during
notebook execution to the caller. This is mostly useful when
using nbconvert as a library, since it allows to deal with
failures gracefully.
"""
def __init__(self, traceback: str, ename: str, evalue: str) -> None:
"""Initialize the error."""
super().__init__(traceback)
self.traceback = traceback
self.ename = ename
self.evalue = evalue
def __reduce__(self) -> tuple[Any]:
"""Reduce implementation."""
return type(self), (self.traceback, self.ename, self.evalue) # type:ignore[return-value]
def __str__(self) -> str:
"""Str repr."""
if self.traceback:
return self.traceback
else:
return f"{self.ename}: {self.evalue}"
@classmethod
def from_cell_and_msg(cls, cell: NotebookNode, msg: dict[str, Any]) -> CellExecutionError:
"""Instantiate from a code cell object and a message contents
(message is either execute_reply or error)
"""
# collect stream outputs for our error message
stream_outputs: list[str] = []
for output in cell.outputs:
if output["output_type"] == "stream":
stream_outputs.append(
stream_output_msg.format(name=output["name"], text=output["text"].rstrip())
)
if stream_outputs:
# add blank line before, trailing separator
# if there is any stream output to display
stream_outputs.insert(0, "")
stream_outputs.append("------------------")
stream_output: str = "\n".join(stream_outputs)
tb = "\n".join(msg.get("traceback", []) or [])
return cls(
exec_err_msg.format(
cell=cell,
stream_output=stream_output,
traceback=tb,
),
ename=msg.get("ename", "<Error>"),
evalue=msg.get("evalue", ""),
)
stream_output_msg: str = """\
----- {name} -----
{text}"""
exec_err_msg: str = """\
An error occurred while executing the following cell:
------------------
{cell.source}
------------------
{stream_output}
{traceback}
"""
timeout_err_msg: str = """\
A cell timed out while it was being executed, after {timeout} seconds.
The message was: {msg}.
Here is a preview of the cell contents:
-------------------
{cell_contents}
-------------------
"""
|