Skip to content

Commit

Permalink
jupyter-run: avoid traceback for NoSuchKernel (#994)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
minrk authored Sep 29, 2024
1 parent 8b80757 commit 060fac3
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 24 deletions.
5 changes: 4 additions & 1 deletion jupyter_client/consoleapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,11 @@ def init_kernel_manager(self) -> None:
parent=self,
data_dir=self.data_dir,
)
# access kernel_spec to ensure the NoSuchKernel error is raised
# if it's going to be
kernel_spec = self.kernel_manager.kernel_spec # noqa: F841
except NoSuchKernel:
self.log.critical("Could not find kernel %s", self.kernel_name)
self.log.critical("Could not find kernel %r", self.kernel_name)
self.exit(1) # type:ignore[attr-defined]

self.kernel_manager = t.cast(KernelManager, self.kernel_manager)
Expand Down
1 change: 0 additions & 1 deletion jupyter_client/kernelspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ def get_kernel_spec(self, kernel_name: str) -> KernelSpec:

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

return self._get_kernel_spec_by_name(kernel_name, resource_dir)
Expand Down
29 changes: 7 additions & 22 deletions jupyter_client/runapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import queue
import atexit
import signal
import sys
import time
import typing as t

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

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

def init_kernel_info(self) -> None:
"""Wait for a kernel to be ready, and store kernel info"""
timeout = self.kernel_timeout
tic = time.time()
self.kernel_client.hb_channel.unpause()
msg_id = self.kernel_client.kernel_info()
while True:
try:
reply = self.kernel_client.get_shell_msg(timeout=1)
except queue.Empty as e:
if (time.time() - tic) > timeout:
msg = "Kernel didn't respond to kernel_info_request"
raise RuntimeError(msg) from e
else:
if reply["parent_header"].get("msg_id") == msg_id:
self.kernel_info = reply["content"]
return

def start(self) -> None:
"""Start the application."""
self.log.debug("jupyter run: starting...")
super().start()
self.kernel_client.wait_for_ready(timeout=self.kernel_timeout)
if self.filenames_to_run:
for filename in self.filenames_to_run:
self.log.debug("jupyter run: executing `%s`", filename)
Expand All @@ -112,8 +95,10 @@ def start(self) -> None:
reply = self.kernel_client.execute_interactive(code, timeout=OUTPUT_TIMEOUT)
return_code = 0 if reply["content"]["status"] == "ok" else 1
if return_code:
raise Exception("jupyter-run error running '%s'" % filename)
msg = f"jupyter-run error running '{filename}'"
raise Exception(msg)
else:
self.log.debug("jupyter run: executing from stdin")
code = sys.stdin.read()
reply = self.kernel_client.execute_interactive(code, timeout=OUTPUT_TIMEOUT)
return_code = 0 if reply["content"]["status"] == "ok" else 1
Expand Down
34 changes: 34 additions & 0 deletions tests/test_runapp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import sys
from subprocess import run


def test_runapp(tmp_path):
test_py = tmp_path / "test.py"
with test_py.open("w") as f:
f.write("print('hello')")

p = run(
[sys.executable, "-m", "jupyter_client.runapp", str(test_py)],
capture_output=True,
text=True,
check=True,
)
assert p.stdout.strip() == "hello"


def test_no_such_kernel(tmp_path):
test_py = tmp_path / "test.py"
with test_py.open("w") as f:
f.write("print('hello')")
kernel_name = "nosuchkernel"
p = run(
[sys.executable, "-m", "jupyter_client.runapp", "--kernel", kernel_name, str(test_py)],
capture_output=True,
text=True,
check=False,
)
assert p.returncode
assert "Could not find kernel" in p.stderr
assert kernel_name in p.stderr
# shouldn't show a traceback
assert "Traceback" not in p.stderr

0 comments on commit 060fac3

Please sign in to comment.