Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion papermill/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ class PapermillMissingParameterException(PapermillException):
class PapermillExecutionError(PapermillException):
"""Raised when an exception is encountered in a notebook."""

def __init__(self, cell_index, exec_count, source, ename, evalue, traceback):
def __init__(self, cell_index, exec_count, source, ename, evalue, traceback, output_notebook=None):
args = cell_index, exec_count, source, ename, evalue, traceback
self.cell_index = cell_index
self.exec_count = exec_count
self.source = source
self.ename = ename
self.evalue = evalue
self.traceback = traceback
self.output_notebook = output_notebook

super().__init__(*args)
Comment on lines +23 to 33
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PapermillExecutionError is explicitly tested as pickle/unpickleable (see papermill/tests/test_exceptions.py). Because output_notebook isn’t included in args (and there’s no custom __reduce__), pickling an error that has output_notebook set will silently drop the notebook on unpickle. Either include output_notebook in the pickled state (e.g., via args or __reduce__) or document/ensure it’s intentionally not preserved.

Copilot uses AI. Check for mistakes.

Expand Down
1 change: 1 addition & 0 deletions papermill/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ def raise_for_execution_errors(nb, output_path):
ename=output.ename,
evalue=output.evalue,
traceback=output.traceback,
output_notebook=nb,
)
Comment on lines 211 to 215
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the error-output branch passes output_notebook=nb. The fallback branch for CellExecutionError (the papermill.exception metadata path) still constructs PapermillExecutionError without output_notebook, so callers will sometimes see None for the new attribute. Pass the current notebook there as well to make the new API consistent.

Copilot uses AI. Check for mistakes.
Comment on lines 211 to 215
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

output_notebook is being set to nb before raise_for_execution_errors upgrades/inserts the error marker cells. Since nbformat.v4.upgrade(nb) can return a new notebook object, err.output_notebook may not reflect the final notebook that gets written. Consider assigning error.output_notebook (or constructing the exception) after the upgrade/marker insert so it matches the on-disk output notebook state.

Copilot uses AI. Check for mistakes.
break

Expand Down
7 changes: 7 additions & 0 deletions papermill/tests/test_execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ def setUp(self):
def tearDown(self):
shutil.rmtree(self.test_dir)

def test_execution_error_includes_output_notebook(self):
notebook_name = 'broken.ipynb'
nb_test_executed_fname = os.path.join(self.test_dir, f'output_{notebook_name}')
with self.assertRaises(PapermillExecutionError) as err:
execute_notebook(get_notebook_path(notebook_name), nb_test_executed_fname)
self.assertIsInstance(err.exception.output_notebook, nbformat.NotebookNode)

@patch(f"{engines.__name__}.PapermillNotebookClient")
def test_start_timeout(self, preproc_mock):
execute_notebook(self.notebook_path, self.nb_test_executed_fname, start_timeout=123)
Expand Down
Loading