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

BUG: Get eval results from metadata #597

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
13 changes: 9 additions & 4 deletions myst_nb/core/execute/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
from pathlib import Path, PurePosixPath
from typing import TYPE_CHECKING

from .base import ExecutionError, ExecutionResult, NotebookClientBase # noqa: F401
from .base import (
ExecutionError, # noqa: F401
ExecutionResult, # noqa: F401
NotebookClientBase,
NotebookClientReadOnly,
)
from .cache import NotebookClientCache
from .direct import NotebookClientDirect
from .inline import NotebookClientInline
Expand Down Expand Up @@ -48,15 +53,15 @@ def create_client(
for pattern in nb_config.execution_excludepatterns:
if posix_path.match(pattern):
logger.info(f"Excluded from execution by pattern: {pattern!r}")
return NotebookClientBase(notebook, path, nb_config, logger)
return NotebookClientReadOnly(notebook, path, nb_config, logger)

# 'auto' mode only executes the notebook if it is missing at least one output
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)
return NotebookClientReadOnly(notebook, path, nb_config, logger)

if nb_config.execution_mode in ("auto", "force"):
return NotebookClientDirect(notebook, path, nb_config, logger)
Expand All @@ -67,4 +72,4 @@ def create_client(
if nb_config.execution_mode == "inline":
return NotebookClientInline(notebook, path, nb_config, logger)

return NotebookClientBase(notebook, path, nb_config, logger)
return NotebookClientReadOnly(notebook, path, nb_config, logger)
27 changes: 25 additions & 2 deletions myst_nb/core/execute/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class EvalNameError(Exception):


class NotebookClientBase:
"""A base client for interacting with Jupyter notebooks.
"""A client base class for interacting with Jupyter notebooks.

This class is intended to be used as a context manager,
and should only be entered once.
Expand Down Expand Up @@ -181,7 +181,30 @@ def eval_variable(
:raises NotImplementedError: if the execution mode does not support this feature
:raises EvalNameError: if the variable name is invalid
"""
raise NotImplementedError


class NotebookClientReadOnly(NotebookClientBase):
"""A notebook client that does not execute the notebook."""

def eval_variable(
self, name: str, current_cell: SyntaxTreeNode
) -> list[NotebookNode]:
"""Retrieve the value of a variable from the current cell metadata if possible.

:param name: the name of the variable
:returns: code cell outputs
:raises RetrievalError: if the cell metadata does not contain the eval result
:raises EvalNameError: if the variable name is invalid
"""
for ux in current_cell.meta.get("metadata", {}).get("user_expressions", []):
if ux["expression"] == name:
return from_dict([ux["result"]])
raise NotImplementedError

from myst_nb.core.variables import RetrievalError

msg = (
f"Cell {current_cell.meta.get('index', '')} does "
f"not contain execution result for variable {name!r}"
)
raise RetrievalError(msg)
2 changes: 2 additions & 0 deletions myst_nb/ext/eval/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ def retrieve_eval_data(document: nodes.document, key: str) -> list[VariableOutpu
outputs = element.renderer.nb_client.eval_variable(
key, element.renderer.current_cell
)
except RetrievalError:
raise
except NotImplementedError:
raise RetrievalError("This document does not have a running kernel")
except EvalNameError:
Expand Down
2 changes: 0 additions & 2 deletions tests/notebooks/with_eval.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
file_format: mystnb
kernelspec:
name: python3
mystnb:
execution_mode: 'inline'
Comment on lines -5 to -6
Copy link
Contributor Author

@flying-sheep flying-sheep Dec 10, 2024

Choose a reason for hiding this comment

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

This is specified also in the tests. If also specified here, the version here overrides the one specified in the tests. In order to add another test, I removed it here. All tests pass.

---

# Inline evaluation
Expand Down
14 changes: 14 additions & 0 deletions tests/test_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,17 @@ def test_no_build(sphinx_run, clean_doctree, file_regression):
assert sphinx_run.warnings() == ""
doctree = clean_doctree(sphinx_run.get_resolved_doctree("with_eval_executed"))
file_regression.check(doctree.pformat(), encoding="utf-8")


@pytest.mark.sphinx_params("with_eval.md", conf={"nb_execution_mode": "off"})
def test_no_build_error(sphinx_run):
"""Test that lack of execution results errors."""
sphinx_run.build()
assert (
"Cell 2 does not contain execution result for variable 'a'"
in sphinx_run.warnings()
)
assert (
"Cell 4 does not contain execution result for variable 'img'"
in sphinx_run.warnings()
)
Loading