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

Stop documentation build raising Exception if code cells raise Exceptions #393

Closed
Closed
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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ exclude: >

repos:

- repo: git://github.com/pre-commit/pre-commit-hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: check-json
Expand Down
32 changes: 26 additions & 6 deletions myst_nb/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,25 @@
LOGGER = logging.getLogger(__name__)


class NotebookExecutionError(RuntimeError):
pass


def execution_error(message: str, allow_errors: bool):
"""Raise an exception or just log based on `allow_errors` value"""

if allow_errors:
LOGGER.error(message)
else:
raise NotebookExecutionError(message)


def update_execution_cache(
app: Sphinx, builder: Builder, added: Set[str], changed: Set[str], removed: Set[str]
app: Sphinx,
builder: Builder,
added: Set[str],
changed: Set[str],
removed: Set[str],
):
"""If caching is required, stage and execute the added or modified notebooks,
and cache them for later retrieval.
Expand Down Expand Up @@ -150,7 +167,7 @@ def generate_notebook_outputs(
show_traceback,
"Execution Failed with traceback saved in {}",
)
LOGGER.error(message)
execution_error(message, env.config["execution_allow_errors"])

ntbk = result.nb

Expand Down Expand Up @@ -196,7 +213,7 @@ def generate_notebook_outputs(
)
message += suffix

LOGGER.error(message)
execution_error(message, env.config["execution_allow_errors"])

else:
LOGGER.verbose("Merged cached outputs into %s", str(r_file_path))
Expand Down Expand Up @@ -291,12 +308,15 @@ def _stage_and_execute(
# Normally we want to keep the stage records available, so that we can retrieve
# execution tracebacks at the `generate_notebook_outputs` stage,
# but we need to flush if it becomes 'corrupted'
LOGGER.error(
"Execution failed in an unexpected way, clearing staged notebooks: %s", err
)
for record in cache_base.list_staged_records():
cache_base.discard_staged_notebook(record.pk)

execution_error(
"Execution failed in an unexpected way, clearing staged notebooks: %s",
err,
env.config["execution_allow_errors"],
)


def execute_staged_nb(
cache_base,
Expand Down
53 changes: 22 additions & 31 deletions tests/test_execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import pytest

from myst_nb.execution import NotebookExecutionError


def regress_nb_doc(file_regression, sphinx_run, check_nbs):
file_regression.check(
Expand Down Expand Up @@ -94,37 +96,27 @@ def test_exclude_path(sphinx_run, file_regression):
"basic_failing.ipynb", conf={"jupyter_execute_notebooks": "cache"}
)
def test_basic_failing_cache(sphinx_run, file_regression, check_nbs):
sphinx_run.build()
assert "Execution Failed" in sphinx_run.warnings()
with pytest.raises(NotebookExecutionError) as e:
sphinx_run.build()
exception = e.value
expected_path = "" if os.name == "nt" else "source/basic_failing.ipynb"
assert (
f"Couldn't find cache key for notebook file {expected_path}"
in sphinx_run.warnings()
assert f"Couldn't find cache key for notebook file {expected_path}" in str(
exception
)
regress_nb_doc(file_regression, sphinx_run, check_nbs)
sphinx_run.get_report_file()

assert "basic_failing" in sphinx_run.env.nb_execution_data
assert sphinx_run.env.nb_execution_data["basic_failing"]["method"] == "cache"
assert sphinx_run.env.nb_execution_data["basic_failing"]["succeeded"] is False
assert "error_log" in sphinx_run.env.nb_execution_data["basic_failing"]


@pytest.mark.sphinx_params(
"basic_failing.ipynb", conf={"jupyter_execute_notebooks": "auto"}
"basic_failing.ipynb",
conf={"jupyter_execute_notebooks": "auto"},
)
def test_basic_failing_auto(sphinx_run, file_regression, check_nbs):
sphinx_run.build()
# print(sphinx_run.status())
assert "Execution Failed" in sphinx_run.warnings()
assert "Execution Failed with traceback saved in" in sphinx_run.warnings()
regress_nb_doc(file_regression, sphinx_run, check_nbs)
sphinx_run.get_report_file()

assert "basic_failing" in sphinx_run.env.nb_execution_data
assert sphinx_run.env.nb_execution_data["basic_failing"]["method"] == "auto"
assert sphinx_run.env.nb_execution_data["basic_failing"]["succeeded"] is False
assert "error_log" in sphinx_run.env.nb_execution_data["basic_failing"]
with pytest.raises(NotebookExecutionError) as e:
sphinx_run.build()
# print(sphinx_run.status())
exception = e.value
assert "Execution Failed with traceback saved in" in str(exception)


@pytest.mark.sphinx_params(
Expand Down Expand Up @@ -215,16 +207,16 @@ def test_jupyter_cache_path(sphinx_run, file_regression, check_nbs):
"basic_relative.ipynb", conf={"jupyter_execute_notebooks": "cache"}
)
def test_relative_path_cache(sphinx_run, file_regression, check_nbs):
sphinx_run.build()
assert "Execution Failed" not in sphinx_run.status(), sphinx_run.status()
with pytest.raises(NotebookExecutionError):
sphinx_run.build()


@pytest.mark.sphinx_params(
"basic_relative.ipynb", conf={"jupyter_execute_notebooks": "force"}
)
def test_relative_path_force(sphinx_run, file_regression, check_nbs):
sphinx_run.build()
assert "Execution Failed" not in sphinx_run.status(), sphinx_run.status()
with pytest.raises(NotebookExecutionError):
sphinx_run.build()


# Execution timeout configuration
Expand All @@ -234,9 +226,8 @@ def test_relative_path_force(sphinx_run, file_regression, check_nbs):
)
def test_execution_timeout(sphinx_run, file_regression, check_nbs):
"""execution should fail given the low timeout value"""
sphinx_run.build()
# print(sphinx_run.status())
assert "execution failed" in sphinx_run.warnings()
with pytest.raises(NotebookExecutionError):
sphinx_run.build()


@pytest.mark.sphinx_params(
Expand All @@ -245,8 +236,8 @@ def test_execution_timeout(sphinx_run, file_regression, check_nbs):
)
def test_execution_metadata_timeout(sphinx_run, file_regression, check_nbs):
"""notebook timeout metadata has higher preference then execution_timeout config"""
sphinx_run.build()
assert "execution failed" in sphinx_run.warnings()
with pytest.raises(NotebookExecutionError):
sphinx_run.build()


@pytest.mark.sphinx_params(
Expand Down