Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

include stream output in CellExecutionError #282

Merged
merged 4 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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 nbclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,6 @@ async def async_execute_cell(
await run_hook(
self.on_cell_executed, cell=cell, cell_index=cell_index, execute_reply=exec_reply
)
await self._check_raise_for_error(cell, cell_index, exec_reply)

if self.coalesce_streams and cell.outputs:
new_outputs = []
Expand Down Expand Up @@ -1056,6 +1055,8 @@ async def async_execute_cell(

cell.outputs = new_outputs

await self._check_raise_for_error(cell, cell_index, exec_reply)

self.nb['cells'][cell_index] = cell
return cell

Expand Down
23 changes: 22 additions & 1 deletion nbclient/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Exceptions for nbclient."""
from typing import Dict
from typing import Dict, List

from nbformat import NotebookNode

Expand Down Expand Up @@ -84,22 +84,43 @@ def from_cell_and_msg(cls, cell: NotebookNode, msg: Dict) -> "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}
"""
Expand Down
48 changes: 45 additions & 3 deletions nbclient/tests/files/Skip Exceptions with Cell Tags.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,36 @@
]
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"hello\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"errorred\n"
]
},
{
"ename": "Exception",
"evalue": "message",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mException\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-1-644b5753a261>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# üñîçø∂é\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"message\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"Cell \u001b[0;32mIn[1], line 5\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124merrorred\u001b[39m\u001b[38;5;124m\"\u001b[39m, file\u001b[38;5;241m=\u001b[39msys\u001b[38;5;241m.\u001b[39mstderr)\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m# üñîçø∂é\u001b[39;00m\n\u001b[0;32m----> 5\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mmessage\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
"\u001b[0;31mException\u001b[0m: message"
]
}
],
"source": [
"import sys\n",
"print(\"hello\")\n",
"print(\"errorred\", file=sys.stderr)\n",
"# üñîçø∂é\n",
"raise Exception(\"message\")"
]
Expand All @@ -44,7 +61,32 @@
]
}
],
"metadata": {},
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.9"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"state": {},
"version_major": 2,
"version_minor": 0
}
}
},
"nbformat": 4,
"nbformat_minor": 1
"nbformat_minor": 4
}
25 changes: 22 additions & 3 deletions nbclient/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import functools
import os
import re
import sys
import threading
import warnings
from base64 import b64decode, b64encode
Expand Down Expand Up @@ -708,7 +709,10 @@ def test_allow_errors(self):
res['metadata']['path'] = os.path.dirname(filename)
with pytest.raises(CellExecutionError) as exc:
run_notebook(filename, {"allow_errors": False}, res)
self.assertIsInstance(str(exc.value), str)
assert isinstance(str(exc.value), str)
# FIXME: we seem to have an encoding problem on Windows
# same check in force_raise_errors
if not sys.platform.startswith("win"):
assert "# üñîçø∂é" in str(exc.value)

def test_force_raise_errors(self):
Expand All @@ -721,8 +725,23 @@ def test_force_raise_errors(self):
res['metadata']['path'] = os.path.dirname(filename)
with pytest.raises(CellExecutionError) as exc:
run_notebook(filename, {"force_raise_errors": True}, res)
self.assertIsInstance(str(exc.value), str)
assert "# üñîçø∂é" in str(exc.value)

# verify CellExecutionError contents
exc_str = str(exc.value)
# print for better debugging with captured output
# print(exc_str)
assert "Exception: message" in exc_str
# FIXME: unicode handling seems to have a problem on Windows
# same check in allow_errors
if not sys.platform.startswith("win"):
assert "# üñîçø∂é" in exc_str
assert "stderr" in exc_str
assert "stdout" in exc_str
assert "hello\n" in exc_str
assert "errorred\n" in exc_str
# stricter check for stream output format
assert "\n".join(["", "----- stdout -----", "hello", "---"]) in exc_str
assert "\n".join(["", "----- stderr -----", "errorred", "---"]) in exc_str

def test_reset_kernel_client(self):
filename = os.path.join(current_dir, 'files', 'HelloWorld.ipynb')
Expand Down