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

Add lazy execution mode #652

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
9 changes: 8 additions & 1 deletion docs/computation/execute.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ To trigger the execution of notebook pages, use the global `nb_execution_mode` c
| -------- | -------------------------------------------------------------------- |
| `off` | Do not execute the notebook |
| `force` | Always execute the notebook (before parsing) |
| `auto` | Execute notebooks with missing outputs (before parsing) |
| `auto` | Execute notebooks with any missing outputs (before parsing) |
| `lazy` | Execute notebooks without any existing outputs (before parsing) |
| `cache` | Execute notebook and store/retrieve outputs from a cache |
| `inline` | Execute the notebook during parsing (allows for variable evaluation) |

Expand All @@ -36,6 +37,12 @@ nb_execution_mode = "auto"
This will only execute notebooks that are missing at least one output.
If a notebook has *all* of its outputs populated, then it will not be executed.

To only execute notebooks that do not have *any* of its outputs populated, change the above configuration value to:

```python
nb_execution_mode = "lazy"
```

To force the execution of all notebooks, regardless of their outputs, change the above configuration value to:

```python
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"off",
"force",
"auto",
"lazy",
"cache",
"commonmark",
"gfm",
Expand Down
5 changes: 4 additions & 1 deletion myst_nb/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,16 @@ def __post_init__(self):
"sections": (Section.global_lvl, Section.file_lvl, Section.execute),
},
)
execution_mode: Literal["off", "force", "auto", "cache", "inline"] = dc.field(
execution_mode: Literal[
"off", "force", "auto", "lazy", "cache", "inline"
] = dc.field(
default="auto",
metadata={
"validator": in_(
[
"off",
"auto",
"lazy",
"force",
"cache",
"inline",
Expand Down
9 changes: 7 additions & 2 deletions myst_nb/core/execute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,20 @@ def create_client(
logger.info(f"Excluded from execution by pattern: {pattern!r}")
return NotebookClientBase(notebook, path, nb_config, logger)

# 'auto' mode only executes the notebook if it is missing at least one output
# 'auto' mode only executes the notebook if it is missing at least one output,
# 'lazy' mode only executes if all outputs are missing.
missing_outputs = (
len(cell.outputs) == 0 for cell in notebook.cells if cell["cell_type"] == "code"
)
if nb_config.execution_mode == "auto" and not any(missing_outputs):
logger.info("Skipped execution in 'auto' mode (all outputs present)")
return NotebookClientBase(notebook, path, nb_config, logger)

if nb_config.execution_mode in ("auto", "force"):
if nb_config.execution_mode == "lazy" and not all(missing_outputs):
logger.info("Skipped execution in 'lazy' mode (at least one output present)")
return NotebookClientBase(notebook, path, nb_config, logger)

if nb_config.execution_mode in ("auto", "lazy", "force"):
return NotebookClientDirect(notebook, path, nb_config, logger)

if nb_config.execution_mode == "cache":
Expand Down
30 changes: 30 additions & 0 deletions tests/test_execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ def test_basic_unrun_auto(sphinx_run, file_regression, check_nbs):
assert data["succeeded"] is True


@pytest.mark.sphinx_params("basic_unrun.ipynb", conf={"nb_execution_mode": "lazy"})
def test_basic_unrun_lazy(sphinx_run, file_regression, check_nbs):
sphinx_run.build()
# print(sphinx_run.status())
assert sphinx_run.warnings() == ""
assert "test_name" in sphinx_run.app.env.metadata["basic_unrun"]
regress_nb_doc(file_regression, sphinx_run, check_nbs)

assert NbMetadataCollector.new_exec_data(sphinx_run.env)
data = NbMetadataCollector.get_exec_data(sphinx_run.env, "basic_unrun")
assert data
assert data["method"] == "lazy"
assert data["succeeded"] is True


@pytest.mark.sphinx_params("basic_unrun.ipynb", conf={"nb_execution_mode": "cache"})
def test_basic_unrun_cache(sphinx_run, file_regression, check_nbs):
"""The outputs should be populated."""
Expand Down Expand Up @@ -149,6 +164,21 @@ def test_basic_failing_auto(sphinx_run, file_regression, check_nbs):
sphinx_run.get_report_file()


@pytest.mark.skipif(ipy_version[0] < 8, reason="Error message changes for ipython v8")
@pytest.mark.sphinx_params("basic_failing.ipynb", conf={"nb_execution_mode": "lazy"})
def test_basic_failing_lazy(sphinx_run, file_regression, check_nbs):
sphinx_run.build()
assert "Executing notebook failed" in sphinx_run.warnings()
regress_nb_doc(file_regression, sphinx_run, check_nbs)

data = NbMetadataCollector.get_exec_data(sphinx_run.env, "basic_failing")
assert data
assert data["method"] == "lazy"
assert data["succeeded"] is False
assert data["traceback"]
sphinx_run.get_report_file()


@pytest.mark.skipif(ipy_version[0] < 8, reason="Error message changes for ipython v8")
@pytest.mark.sphinx_params("basic_failing.ipynb", conf={"nb_execution_mode": "inline"})
def test_basic_failing_inline(sphinx_run, file_regression, check_nbs):
Expand Down
Loading