1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
import sys
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
if sys.version_info >= (3, 13):
PATH_OPEN_CALL_STEP = 4
else:
PATH_OPEN_CALL_STEP = 3
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[::PATH_OPEN_CALL_STEP] == [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[::PATH_OPEN_CALL_STEP] == [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()
|