-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optionally write out executed notebook in jupyter-execute (#307)
* fix: Actually write out executed notebook in jupyter-execute Fixes #306. Now actually write out the executed notebook to the same path as the input. * feat: Added cli options to save executed notebook This commit adds two options to optionally save the executed notebook. * `--inplace`: Save the executed notebook to the input notebook path * `--output`: Save the executed notebook to `output`. This option can take a pattern like `{notebook_name}-new`, where `notebook_name` is the name of the input notebook without extension `.ipynb`. Also, the output location is always relative to the input notebook location. * chore: update mocker for opening files to remove error --------- Co-authored-by: wpk <[email protected]>
- Loading branch information
1 parent
c788781
commit 3286ae0
Showing
2 changed files
with
241 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
from pathlib import Path | ||
from subprocess import CalledProcessError, check_output | ||
from unittest.mock import call, mock_open, patch | ||
|
||
import pytest | ||
|
||
from nbclient.cli import NbClientApp | ||
|
||
current_dir = Path(__file__).parent.absolute() | ||
|
||
|
||
@pytest.fixture() | ||
def jupyterapp(): | ||
with patch("nbclient.cli.JupyterApp.initialize") as mocked: | ||
yield mocked | ||
|
||
|
||
@pytest.fixture() | ||
def client(): | ||
with patch("nbclient.cli.NotebookClient", autospec=True) as mocked: | ||
yield mocked | ||
|
||
|
||
@pytest.fixture() | ||
def writer(): | ||
with patch("nbformat.write", autospec=True) as mocked: | ||
yield mocked | ||
|
||
|
||
@pytest.fixture() | ||
def reader(): | ||
with patch("nbformat.read", autospec=True, return_value="nb") as mocked: | ||
yield mocked | ||
|
||
|
||
@pytest.fixture() | ||
def path_open(): | ||
opener = mock_open() | ||
|
||
def mocked_open(self, *args, **kwargs): | ||
return opener(self, *args, **kwargs) | ||
|
||
with patch("nbclient.cli.Path.open", mocked_open): | ||
yield opener | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"input_names", [("Other Comms",), ("Other Comms.ipynb",), ("Other Comms", "HelloWorld.ipynb")] | ||
) | ||
@pytest.mark.parametrize("relative", [False, True]) | ||
@pytest.mark.parametrize("inplace", [False, True]) | ||
def test_mult(input_names, relative, inplace, jupyterapp, client, reader, writer, path_open): | ||
paths = [current_dir / "files" / name for name in input_names] | ||
if relative: | ||
paths = [p.relative_to(Path.cwd()) for p in paths] | ||
|
||
c = NbClientApp(notebooks=[str(p) for p in paths], kernel_name="python3", inplace=inplace) | ||
c.initialize() | ||
|
||
# add suffix if needed | ||
paths = [p.with_suffix(".ipynb") for p in paths] | ||
|
||
assert path_open.mock_calls[::3] == [call(p) for p in paths] | ||
assert reader.call_count == len(paths) | ||
# assert reader.mock_calls == [call(p, as_version=4) for p in paths] | ||
|
||
expected = [] | ||
for p in paths: | ||
expected.extend( | ||
[ | ||
call( | ||
"nb", | ||
timeout=c.timeout, | ||
startup_timeout=c.startup_timeout, | ||
skip_cells_with_tag=c.skip_cells_with_tag, | ||
allow_errors=c.allow_errors, | ||
kernel_name=c.kernel_name, | ||
resources={"metadata": {"path": p.parent.absolute()}}, | ||
), | ||
call().execute(), | ||
] | ||
) | ||
|
||
assert client.mock_calls == expected | ||
|
||
if inplace: | ||
assert writer.mock_calls == [call("nb", p) for p in paths] | ||
else: | ||
writer.assert_not_called() | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"input_names", [("Other Comms",), ("Other Comms.ipynb",), ("Other Comms", "HelloWorld.ipynb")] | ||
) | ||
@pytest.mark.parametrize("relative", [False, True]) | ||
@pytest.mark.parametrize("output_base", ["thing", "thing.ipynb", "{notebook_name}-new.ipynb"]) | ||
def test_output(input_names, relative, output_base, jupyterapp, client, reader, writer, path_open): | ||
paths = [current_dir / "files" / name for name in input_names] | ||
if relative: | ||
paths = [p.relative_to(Path.cwd()) for p in paths] | ||
|
||
c = NbClientApp( | ||
notebooks=[str(p) for p in paths], kernel_name="python3", output_base=output_base | ||
) | ||
|
||
if len(paths) != 1 and "{notebook_name}" not in output_base: | ||
with pytest.raises(ValueError) as e: | ||
c.initialize() | ||
assert "If passing multiple" in str(e.value) | ||
return | ||
|
||
c.initialize() | ||
|
||
# add suffix if needed | ||
paths = [p.with_suffix(".ipynb") for p in paths] | ||
|
||
assert path_open.mock_calls[::3] == [call(p) for p in paths] | ||
assert reader.call_count == len(paths) | ||
|
||
expected = [] | ||
for p in paths: | ||
expected.extend( | ||
[ | ||
call( | ||
"nb", | ||
timeout=c.timeout, | ||
startup_timeout=c.startup_timeout, | ||
skip_cells_with_tag=c.skip_cells_with_tag, | ||
allow_errors=c.allow_errors, | ||
kernel_name=c.kernel_name, | ||
resources={"metadata": {"path": p.parent.absolute()}}, | ||
), | ||
call().execute(), | ||
] | ||
) | ||
|
||
assert client.mock_calls == expected | ||
|
||
assert writer.mock_calls == [ | ||
call( | ||
"nb", | ||
(p.parent / output_base.format(notebook_name=p.with_suffix("").name)).with_suffix( | ||
".ipynb" | ||
), | ||
) | ||
for p in paths | ||
] | ||
|
||
|
||
def test_bad_output_dir(jupyterapp, client, reader, writer, path_open): | ||
input_names = ["Other Comms"] | ||
output_base = "thing/thing" | ||
|
||
paths = [current_dir / "files" / name for name in input_names] | ||
|
||
c = NbClientApp( | ||
notebooks=[str(p) for p in paths], kernel_name="python3", output_base=output_base | ||
) | ||
|
||
with pytest.raises(ValueError) as e: | ||
c.initialize() | ||
|
||
assert "Cannot write to directory" in str(e.value) | ||
|
||
|
||
# simple runner from command line | ||
def test_cli_simple(): | ||
path = current_dir / "files" / "Other Comms" | ||
|
||
with pytest.raises(CalledProcessError): | ||
check_output(["jupyter-execute", "--output", "thing/thing", str(path)]) # noqa: S603, S607 | ||
|
||
|
||
def test_no_notebooks(jupyterapp): | ||
c = NbClientApp(notebooks=[], kernel_name="python3") | ||
|
||
with pytest.raises(SystemExit): | ||
c.initialize() |