Skip to content

Commit ba22f95

Browse files
authored
✨ NEW: Add nb_execution_raise_on_error config (#404)
1 parent edb0daf commit ba22f95

14 files changed

Lines changed: 62 additions & 19 deletions

docs/use/execute.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ tags: [raises-exception]
161161
print(thisvariabledoesntexist)
162162
```
163163
164+
(execute/raise_on_error)=
165+
## Error Reporting: Warning vs. Failure
166+
167+
When an error occurs in a context where `nb_execution_allow_errors=False`,
168+
the default behaviour is for this to be reported as a warning.
169+
This warning will simply be logged and not cause the build to fail unless `sphinx-build` is run with the [`-W` option](https://www.sphinx-doc.org/en/master/man/sphinx-build.html#cmdoption-sphinx-build-W).
170+
If you would like unexpected execution errors to cause a build failure rather than a warning regardless of the `-W` option, you can achieve this by setting `nb_execution_raise_on_error=True` in your `conf.py`.
171+
164172
(execute/statistics)=
165173
## Execution statistics
166174

myst_nb/core/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,14 @@ def __post_init__(self):
202202
"legacy_name": "execution_allow_errors",
203203
},
204204
)
205+
execution_raise_on_error: bool = dc.field(
206+
default=False,
207+
metadata={
208+
"validator": instance_of(bool),
209+
"help": "Raise an exception on failed execution, "
210+
"rather than emitting a warning",
211+
},
212+
)
205213
execution_show_tb: bool = dc.field( # TODO implement
206214
default=False,
207215
metadata={

myst_nb/core/execute.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ class ExecutionResult(TypedDict):
3535
"""traceback if the notebook failed"""
3636

3737

38+
class ExecutionError(Exception):
39+
"""An exception for failed execution and `execution_raise_on_error` is true."""
40+
41+
3842
def execute_notebook(
3943
notebook: NotebookNode,
4044
source: str,
@@ -111,6 +115,8 @@ def execute_notebook(
111115
)
112116

113117
if result.err is not None:
118+
if nb_config.execution_raise_on_error:
119+
raise ExecutionError(str(source)) from result.err
114120
msg = f"Executing notebook failed: {result.err.__class__.__name__}"
115121
if nb_config.execution_show_tb:
116122
msg += f"\n{result.exc_string}"
@@ -187,6 +193,8 @@ def execute_notebook(
187193
# handle success / failure cases
188194
# TODO do in try/except to be careful (in case of database write errors?
189195
if result.err is not None:
196+
if nb_config.execution_raise_on_error:
197+
raise ExecutionError(str(source)) from result.err
190198
msg = f"Executing notebook failed: {result.err.__class__.__name__}"
191199
if nb_config.execution_show_tb:
192200
msg += f"\n{result.exc_string}"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ testing = [
8787
"coverage<5.0",
8888
"beautifulsoup4",
8989
"ipykernel~=5.5",
90-
"ipython<8.1.0", # see https://github.com/ipython/ipython/issues/13554
90+
"ipython!=8.1.0", # see https://github.com/ipython/ipython/issues/13554
9191
"ipywidgets",
9292
"jupytext~=1.11.2",
9393
# TODO: 3.4.0 has some warnings that need to be fixed in the tests.

tests/test_execute.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from IPython import version_info as ipy_version
66
import pytest
77

8+
from myst_nb.core.execute import ExecutionError
89
from myst_nb.sphinx_ import NbMetadataCollector
910

1011

@@ -153,6 +154,24 @@ def test_allow_errors_auto(sphinx_run, file_regression, check_nbs):
153154
regress_nb_doc(file_regression, sphinx_run, check_nbs)
154155

155156

157+
@pytest.mark.sphinx_params(
158+
"basic_failing.ipynb",
159+
conf={"nb_execution_raise_on_error": True, "nb_execution_mode": "force"},
160+
)
161+
def test_raise_on_error_force(sphinx_run):
162+
with pytest.raises(ExecutionError, match="basic_failing.ipynb"):
163+
sphinx_run.build()
164+
165+
166+
@pytest.mark.sphinx_params(
167+
"basic_failing.ipynb",
168+
conf={"nb_execution_raise_on_error": True, "nb_execution_mode": "cache"},
169+
)
170+
def test_raise_on_error_cache(sphinx_run):
171+
with pytest.raises(ExecutionError, match="basic_failing.ipynb"):
172+
sphinx_run.build()
173+
174+
156175
@pytest.mark.sphinx_params("basic_unrun.ipynb", conf={"nb_execution_mode": "force"})
157176
def test_outputs_present(sphinx_run, file_regression, check_nbs):
158177
sphinx_run.build()
@@ -222,13 +241,13 @@ def test_jupyter_cache_path(sphinx_run, file_regression, check_nbs):
222241

223242
# Testing relative paths within the notebook
224243
@pytest.mark.sphinx_params("basic_relative.ipynb", conf={"nb_execution_mode": "cache"})
225-
def test_relative_path_cache(sphinx_run, file_regression, check_nbs):
244+
def test_relative_path_cache(sphinx_run):
226245
sphinx_run.build()
227246
assert "Execution Failed" not in sphinx_run.status(), sphinx_run.status()
228247

229248

230249
@pytest.mark.sphinx_params("basic_relative.ipynb", conf={"nb_execution_mode": "force"})
231-
def test_relative_path_force(sphinx_run, file_regression, check_nbs):
250+
def test_relative_path_force(sphinx_run):
232251
sphinx_run.build()
233252
assert "Execution Failed" not in sphinx_run.status(), sphinx_run.status()
234253

@@ -238,7 +257,7 @@ def test_relative_path_force(sphinx_run, file_regression, check_nbs):
238257
"sleep_10.ipynb",
239258
conf={"nb_execution_mode": "cache", "nb_execution_timeout": 1},
240259
)
241-
def test_execution_timeout(sphinx_run, file_regression, check_nbs):
260+
def test_execution_timeout(sphinx_run):
242261
"""execution should fail given the low timeout value"""
243262
sphinx_run.build()
244263
# print(sphinx_run.warnings())
@@ -249,7 +268,7 @@ def test_execution_timeout(sphinx_run, file_regression, check_nbs):
249268
"sleep_10_metadata_timeout.ipynb",
250269
conf={"nb_execution_mode": "cache", "nb_execution_timeout": 60},
251270
)
252-
def test_execution_metadata_timeout(sphinx_run, file_regression, check_nbs):
271+
def test_execution_metadata_timeout(sphinx_run):
253272
"""notebook timeout metadata has higher preference then execution_timeout config"""
254273
sphinx_run.build()
255274
# print(sphinx_run.warnings())
@@ -260,7 +279,7 @@ def test_execution_metadata_timeout(sphinx_run, file_regression, check_nbs):
260279
"nb_exec_table.md",
261280
conf={"nb_execution_mode": "auto"},
262281
)
263-
def test_nb_exec_table(sphinx_run, file_regression, check_nbs):
282+
def test_nb_exec_table(sphinx_run, file_regression):
264283
"""Test that the table gets output into the HTML,
265284
including a row for the executed notebook.
266285
"""

tests/test_execute/test_allow_errors_auto.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"traceback": [
2222
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
2323
"\u001b[0;31mException\u001b[0m Traceback (most recent call last)",
24-
"Input \u001b[0;32mIn [1]\u001b[0m, in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\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;124moopsie!\u001b[39m\u001b[38;5;124m'\u001b[39m)\n",
24+
"Input \u001b[0;32mIn [1]\u001b[0m, in \u001b[0;36m<cell line: 1>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\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;124moopsie!\u001b[39m\u001b[38;5;124m'\u001b[39m)\n",
2525
"\u001b[0;31mException\u001b[0m: oopsie!"
2626
]
2727
}
@@ -47,7 +47,7 @@
4747
"name": "python",
4848
"nbconvert_exporter": "python",
4949
"pygments_lexer": "ipython3",
50-
"version": "3.8.12"
50+
"version": "3.8.13"
5151
},
5252
"test_name": "notebook1"
5353
},

tests/test_execute/test_allow_errors_auto.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<literal_block classes="output traceback" language="ipythontb" xml:space="preserve">
1313
---------------------------------------------------------------------------
1414
Exception Traceback (most recent call last)
15-
Input In [1], in <module>
15+
Input In [1], in <cell line: 1>()
1616
----> 1 raise Exception('oopsie!')
1717

1818
Exception: oopsie!

tests/test_execute/test_allow_errors_cache.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"traceback": [
2222
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
2323
"\u001b[0;31mException\u001b[0m Traceback (most recent call last)",
24-
"Input \u001b[0;32mIn [1]\u001b[0m, in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\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;124moopsie!\u001b[39m\u001b[38;5;124m'\u001b[39m)\n",
24+
"Input \u001b[0;32mIn [1]\u001b[0m, in \u001b[0;36m<cell line: 1>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\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;124moopsie!\u001b[39m\u001b[38;5;124m'\u001b[39m)\n",
2525
"\u001b[0;31mException\u001b[0m: oopsie!"
2626
]
2727
}
@@ -47,7 +47,7 @@
4747
"name": "python",
4848
"nbconvert_exporter": "python",
4949
"pygments_lexer": "ipython3",
50-
"version": "3.8.12"
50+
"version": "3.8.13"
5151
},
5252
"test_name": "notebook1"
5353
},

tests/test_execute/test_allow_errors_cache.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<literal_block classes="output traceback" language="ipythontb" xml:space="preserve">
1313
---------------------------------------------------------------------------
1414
Exception Traceback (most recent call last)
15-
Input In [1], in <module>
15+
Input In [1], in <cell line: 1>()
1616
----> 1 raise Exception('oopsie!')
1717

1818
Exception: oopsie!

tests/test_execute/test_basic_failing_auto.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"traceback": [
2222
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
2323
"\u001b[0;31mException\u001b[0m Traceback (most recent call last)",
24-
"Input \u001b[0;32mIn [1]\u001b[0m, in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\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;124moopsie!\u001b[39m\u001b[38;5;124m'\u001b[39m)\n",
24+
"Input \u001b[0;32mIn [1]\u001b[0m, in \u001b[0;36m<cell line: 1>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\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;124moopsie!\u001b[39m\u001b[38;5;124m'\u001b[39m)\n",
2525
"\u001b[0;31mException\u001b[0m: oopsie!"
2626
]
2727
}
@@ -47,7 +47,7 @@
4747
"name": "python",
4848
"nbconvert_exporter": "python",
4949
"pygments_lexer": "ipython3",
50-
"version": "3.8.12"
50+
"version": "3.8.13"
5151
},
5252
"test_name": "notebook1"
5353
},

0 commit comments

Comments
 (0)