Skip to content

Commit e70deb6

Browse files
add --nbmake-find-import-errors (#87)
1 parent b549ab3 commit e70deb6

File tree

6 files changed

+102
-1
lines changed

6 files changed

+102
-1
lines changed

src/nbmake/nb_run.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
NB_VERSION = 4
1515

1616

17+
class CellImportError(Exception):
18+
pass
19+
20+
1721
class NotebookRun:
1822
filename: Path
1923
verbose: bool
@@ -24,11 +28,13 @@ def __init__(
2428
default_timeout: int,
2529
verbose: bool = False,
2630
kernel: Optional[str] = None,
31+
find_import_errors: bool = False,
2732
) -> None:
2833
self.filename = filename
2934
self.verbose = verbose
3035
self.default_timeout = default_timeout
3136
self.kernel = kernel
37+
self.find_import_errors = find_import_errors
3238

3339
def execute(
3440
self,
@@ -56,7 +62,8 @@ def execute(
5662
c = NotebookClient(
5763
nb,
5864
timeout=timeout,
59-
allow_errors=allow_errors,
65+
allow_errors=allow_errors or self.find_import_errors,
66+
interrupt_on_timeout=self.find_import_errors,
6067
record_timing=True,
6168
**extra_kwargs,
6269
)
@@ -68,6 +75,11 @@ async def apply_mocks(
6875
if any(o["output_type"] == "error" for o in cell["outputs"]):
6976
execute_reply["content"]["status"] = "error"
7077

78+
if "ename" in execute_reply["content"]:
79+
if execute_reply["content"]["ename"] == "ModuleNotFoundError":
80+
if self.find_import_errors:
81+
raise CellImportError()
82+
7183
if c.kc is None:
7284
raise Exception("there is no kernelclient")
7385
mocks: Dict[str, Any] = (
@@ -85,6 +97,8 @@ async def apply_mocks(
8597
c.on_cell_executed = apply_mocks
8698

8799
c.execute(cwd=self.filename.parent)
100+
except CellImportError:
101+
error = self._get_error(nb)
88102
except CellExecutionError:
89103
error = self._get_error(nb)
90104
except CellTimeoutError as err:

src/nbmake/pytest_items.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def runtest(self):
4747
option.nbmake_timeout,
4848
verbose=bool(option.verbose),
4949
kernel=option.nbmake_kernel,
50+
find_import_errors=option.nbmake_find_import_errors,
5051
)
5152

5253
res: NotebookResult = run.execute()

src/nbmake/pytest_plugin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ def pytest_addoption(parser: Any):
4242
type=str,
4343
)
4444

45+
group.addoption(
46+
"--nbmake-find-import-errors",
47+
action="store_true",
48+
help="Runs all cells, only reports import errors",
49+
default=False,
50+
)
51+
4552

4653
def pytest_collect_file(path: str, parent: Any) -> Optional[Any]:
4754
opt = parent.config.option

tests/resources/import_errs.ipynb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": null,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"import itertools\n",
10+
"\n",
11+
"1/0"
12+
]
13+
},
14+
{
15+
"cell_type": "code",
16+
"execution_count": null,
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"import time\n",
21+
"time.sleep(600)"
22+
]
23+
},
24+
{
25+
"cell_type": "code",
26+
"execution_count": null,
27+
"metadata": {},
28+
"outputs": [],
29+
"source": [
30+
"import lkjlkj"
31+
]
32+
},
33+
{
34+
"cell_type": "code",
35+
"execution_count": null,
36+
"metadata": {},
37+
"outputs": [],
38+
"source": [
39+
"import pickle"
40+
]
41+
}
42+
],
43+
"metadata": {
44+
"kernelspec": {
45+
"display_name": "Python 3.10.8 64-bit",
46+
"language": "python",
47+
"name": "python3"
48+
},
49+
"language_info": {
50+
"name": "python",
51+
"version": "3.10.8"
52+
},
53+
"vscode": {
54+
"interpreter": {
55+
"hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1"
56+
}
57+
}
58+
},
59+
"nbformat": 4,
60+
"nbformat_minor": 2
61+
}

tests/test_nb_run.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,10 @@ def test_when_empty_then_succeeds(self, testdir2: Never):
133133
run = NotebookRun(nb, 300)
134134
res: NotebookResult = run.execute()
135135
assert res.error is None
136+
137+
def test_when_import_error_then_fails(self, testdir2: Never):
138+
nb = Path(__file__).parent / "resources" / "import_errs.ipynb"
139+
run = NotebookRun(nb, 1, find_import_errors=True)
140+
res: NotebookResult = run.execute()
141+
assert res.error is not None
142+
assert "ModuleNotFoundError" in res.error.summary

tests/test_pytest_plugin.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,14 @@ def test_when_kernel_passed_then_override(pytester: Pytester, testdir2: Never):
184184
hook_recorder = pytester.inline_run("--nbmake", "--nbmake-kernel=python3")
185185

186186
assert hook_recorder.ret == ExitCode.OK
187+
188+
189+
def test_when_no_import_errs_then_pass(pytester: Pytester, testdir2: Never):
190+
write_nb(
191+
["import itertools", "1/0", "import pickle"],
192+
Path(pytester.path) / "a.ipynb",
193+
)
194+
195+
hook_recorder = pytester.inline_run("--nbmake", "--nbmake-find-import-errors")
196+
197+
assert hook_recorder.ret == ExitCode.OK

0 commit comments

Comments
 (0)