Skip to content

Commit 060fac3

Browse files
authored
jupyter-run: avoid traceback for NoSuchKernel (#994)
* jupyter-run: avoid traceback for NoSuchKernel NoSuchKernel used to raise during KernelManager instantiation, but it is now delayed. Access kernel_spec to ensure it's raised where it will be caught. Also removes a redundant warning log immediately before raising, which prevents complete handling of NoSuchError and produces unavoidable duplicate logs. * call wait_for_ready() in runapp ensures kernel is ready before running avoids lost output if iopub isn't connected yet and shutdown kernel when finished, rather than relying on the kernel shutting itself down
1 parent 8b80757 commit 060fac3

File tree

4 files changed

+45
-24
lines changed

4 files changed

+45
-24
lines changed

jupyter_client/consoleapp.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,11 @@ def init_kernel_manager(self) -> None:
306306
parent=self,
307307
data_dir=self.data_dir,
308308
)
309+
# access kernel_spec to ensure the NoSuchKernel error is raised
310+
# if it's going to be
311+
kernel_spec = self.kernel_manager.kernel_spec # noqa: F841
309312
except NoSuchKernel:
310-
self.log.critical("Could not find kernel %s", self.kernel_name)
313+
self.log.critical("Could not find kernel %r", self.kernel_name)
311314
self.exit(1) # type:ignore[attr-defined]
312315

313316
self.kernel_manager = t.cast(KernelManager, self.kernel_manager)

jupyter_client/kernelspec.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,6 @@ def get_kernel_spec(self, kernel_name: str) -> KernelSpec:
281281

282282
resource_dir = self._find_spec_directory(kernel_name.lower())
283283
if resource_dir is None:
284-
self.log.warning("Kernelspec name %s cannot be found!", kernel_name)
285284
raise NoSuchKernel(kernel_name)
286285

287286
return self._get_kernel_spec_by_name(kernel_name, resource_dir)

jupyter_client/runapp.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
# Distributed under the terms of the Modified BSD License.
44
from __future__ import annotations
55

6-
import queue
6+
import atexit
77
import signal
88
import sys
9-
import time
109
import typing as t
1110

1211
from jupyter_core.application import JupyterApp, base_aliases, base_flags
@@ -73,7 +72,8 @@ def initialize(self, argv: list[str] | None = None) -> None: # type:ignore[over
7372
super().initialize(argv)
7473
JupyterConsoleApp.initialize(self)
7574
signal.signal(signal.SIGINT, self.handle_sigint)
76-
self.init_kernel_info()
75+
if self.kernel_manager:
76+
atexit.register(self.kernel_manager.shutdown_kernel)
7777

7878
def handle_sigint(self, *args: t.Any) -> None:
7979
"""Handle SIGINT."""
@@ -82,28 +82,11 @@ def handle_sigint(self, *args: t.Any) -> None:
8282
else:
8383
self.log.error("Cannot interrupt kernels we didn't start.\n")
8484

85-
def init_kernel_info(self) -> None:
86-
"""Wait for a kernel to be ready, and store kernel info"""
87-
timeout = self.kernel_timeout
88-
tic = time.time()
89-
self.kernel_client.hb_channel.unpause()
90-
msg_id = self.kernel_client.kernel_info()
91-
while True:
92-
try:
93-
reply = self.kernel_client.get_shell_msg(timeout=1)
94-
except queue.Empty as e:
95-
if (time.time() - tic) > timeout:
96-
msg = "Kernel didn't respond to kernel_info_request"
97-
raise RuntimeError(msg) from e
98-
else:
99-
if reply["parent_header"].get("msg_id") == msg_id:
100-
self.kernel_info = reply["content"]
101-
return
102-
10385
def start(self) -> None:
10486
"""Start the application."""
10587
self.log.debug("jupyter run: starting...")
10688
super().start()
89+
self.kernel_client.wait_for_ready(timeout=self.kernel_timeout)
10790
if self.filenames_to_run:
10891
for filename in self.filenames_to_run:
10992
self.log.debug("jupyter run: executing `%s`", filename)
@@ -112,8 +95,10 @@ def start(self) -> None:
11295
reply = self.kernel_client.execute_interactive(code, timeout=OUTPUT_TIMEOUT)
11396
return_code = 0 if reply["content"]["status"] == "ok" else 1
11497
if return_code:
115-
raise Exception("jupyter-run error running '%s'" % filename)
98+
msg = f"jupyter-run error running '{filename}'"
99+
raise Exception(msg)
116100
else:
101+
self.log.debug("jupyter run: executing from stdin")
117102
code = sys.stdin.read()
118103
reply = self.kernel_client.execute_interactive(code, timeout=OUTPUT_TIMEOUT)
119104
return_code = 0 if reply["content"]["status"] == "ok" else 1

tests/test_runapp.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import sys
2+
from subprocess import run
3+
4+
5+
def test_runapp(tmp_path):
6+
test_py = tmp_path / "test.py"
7+
with test_py.open("w") as f:
8+
f.write("print('hello')")
9+
10+
p = run(
11+
[sys.executable, "-m", "jupyter_client.runapp", str(test_py)],
12+
capture_output=True,
13+
text=True,
14+
check=True,
15+
)
16+
assert p.stdout.strip() == "hello"
17+
18+
19+
def test_no_such_kernel(tmp_path):
20+
test_py = tmp_path / "test.py"
21+
with test_py.open("w") as f:
22+
f.write("print('hello')")
23+
kernel_name = "nosuchkernel"
24+
p = run(
25+
[sys.executable, "-m", "jupyter_client.runapp", "--kernel", kernel_name, str(test_py)],
26+
capture_output=True,
27+
text=True,
28+
check=False,
29+
)
30+
assert p.returncode
31+
assert "Could not find kernel" in p.stderr
32+
assert kernel_name in p.stderr
33+
# shouldn't show a traceback
34+
assert "Traceback" not in p.stderr

0 commit comments

Comments
 (0)