Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ default: all ## Default target is all.
help: ## display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

format: ## format python
pip install black
python -m black .

all: clean dev ## Clean Install and Build

install:
Expand Down
44 changes: 31 additions & 13 deletions jupyter_kernel_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
from jupyter_kernel_client.utils import UTC


def output_hook(outputs: list[dict[str, t.Any]], message: dict[str, t.Any]) -> set[int]: # noqa: C901
def output_hook(
outputs: list[dict[str, t.Any]], message: dict[str, t.Any]
) -> set[int]: # noqa: C901
"""Callback on messages captured during a code snippet execution.

The return list of updated output will be empty if no output where changed.
Expand Down Expand Up @@ -163,7 +165,9 @@ def __init__(
self, kernel_id: str | None = None, log: logging.Logger | None = None, **kwargs
) -> None:
super().__init__(log=log or get_logger())
self._manager = self.kernel_manager_class(parent=self, kernel_id=kernel_id, **kwargs)
self._manager = self.kernel_manager_class(
parent=self, kernel_id=kernel_id, **kwargs
)
# Set it after the manager as if a kernel_id is provided,
# we will try to connect to it.
self._own_kernel = self._manager.kernel is None
Expand Down Expand Up @@ -456,7 +460,8 @@ def set_variable(self, name: str, value: t.Any) -> None:
"""
kernel_language = (self.kernel_info or {}).get("language_info", {}).get("name")
if kernel_language not in SNIPPETS_REGISTRY.available_languages:
raise ValueError(f"""Code snippet for language {kernel_language} are not available.
raise ValueError(
f"""Code snippet for language {kernel_language} are not available.
You can set them yourself using:

from jupyter_kernel_client import SNIPPETS_REGISTRY, LanguageSnippets
Expand All @@ -469,10 +474,13 @@ def set_variable(self, name: str, value: t.Any) -> None:
get_variable_mimetypes="",
)
)
""")
"""
)
snippet = SNIPPETS_REGISTRY.get_set_variable(kernel_language)
data, metadata = serialize_object(value)
results = self.execute(snippet.format(name=name, data=data, metadata=metadata), silent=True)
results = self.execute(
snippet.format(name=name, data=data, metadata=metadata), silent=True
)
self.log.debug("Set variables: %s", results)
if results["status"] == "ok":
pass
Expand All @@ -496,7 +504,8 @@ def get_variable(self, name: str) -> tuple[dict[str, t.Any], dict[str, t.Any]]:
"""
kernel_language = (self.kernel_info or {}).get("language_info", {}).get("name")
if kernel_language not in SNIPPETS_REGISTRY.available_languages:
raise ValueError(f"""Code snippet for language {kernel_language} are not available.
raise ValueError(
f"""Code snippet for language {kernel_language} are not available.
You can set them yourself using:

from jupyter_kernel_client import SNIPPETS_REGISTRY, LanguageSnippets
Expand All @@ -509,7 +518,8 @@ def get_variable(self, name: str) -> tuple[dict[str, t.Any], dict[str, t.Any]]:
get_variable_mimetypes="",
)
)
""")
"""
)

snippet = SNIPPETS_REGISTRY.get_get_variable(kernel_language)
results = self.execute(snippet.format(name=name), silent=True)
Expand All @@ -536,7 +546,8 @@ def list_variables(self) -> list[VariableDescription]:
"""
kernel_language = (self.kernel_info or {}).get("language_info", {}).get("name")
if kernel_language not in SNIPPETS_REGISTRY.available_languages:
raise ValueError(f"""Code snippet for language {kernel_language} are not available.
raise ValueError(
f"""Code snippet for language {kernel_language} are not available.
You can set them yourself using:

from jupyter_kernel_client import SNIPPETS_REGISTRY, LanguageSnippets
Expand All @@ -549,7 +560,8 @@ def list_variables(self) -> list[VariableDescription]:
get_variable_mimetypes="",
)
)
""")
"""
)

snippet = SNIPPETS_REGISTRY.get_list_variables(kernel_language)
results = self.execute(snippet, silent=True)
Expand Down Expand Up @@ -592,21 +604,27 @@ def get_variable_mimetypes(
"""
kernel_language = (self.kernel_info or {}).get("language_info", {}).get("name")
if kernel_language not in SNIPPETS_REGISTRY.available_languages:
raise ValueError(f"""Code snippet for language {kernel_language} are not available.
raise ValueError(
f"""Code snippet for language {kernel_language} are not available.
You can set them yourself using:

from jupyter_kernel_client import SNIPPETS_REGISTRY, LanguageSnippets
SNIPPETS_REGISTRY.register("my-language", LanguageSnippets(list_variables="", get_variable=""))
""")
"""
)

snippet = SNIPPETS_REGISTRY.get_get_variable_mimetypes(kernel_language)
results = self.execute(snippet.format(name=name, mimetype=mimetype), silent=True)
results = self.execute(
snippet.format(name=name, mimetype=mimetype), silent=True
)

self.log.debug("Kernel variables: %s", results)

if results["status"] == "ok" and results["outputs"]:
if mimetype is None:
return results["outputs"][0]["data"], results["outputs"][0].get("metadata", {})
return results["outputs"][0]["data"], results["outputs"][0].get(
"metadata", {}
)
else:

def filter_dict(d: dict, mimetype: str) -> dict:
Expand Down
16 changes: 12 additions & 4 deletions jupyter_kernel_client/konsoleapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ class KonsoleApp(JupyterApp):

subcommands = Dict()

server_url = Unicode("http://localhost:8888", config=True, help="URL to the Jupyter Server.")
server_url = Unicode(
"http://localhost:8888", config=True, help="URL to the Jupyter Server."
)

# FIXME it does not support password
token = Unicode("", config=True, help="Jupyter Server token.")
Expand All @@ -126,10 +128,14 @@ class KonsoleApp(JupyterApp):

existing = CUnicode("", config=True, help="""Existing kernel ID to connect to.""")

kernel_name = Unicode("python3", config=True, help="""The name of the kernel to connect to.""")
kernel_name = Unicode(
"python3", config=True, help="""The name of the kernel to connect to."""
)

kernel_path = Unicode(
"", config=True, help="API path from server root to the kernel working directory."
"",
config=True,
help="API path from server root to the kernel working directory.",
)

confirm_exit = CBool(
Expand Down Expand Up @@ -198,7 +204,9 @@ def init_kernel_manager(self) -> None:
)

if not self.existing:
self.kernel_client.start_kernel(name=self.kernel_name, path=self.kernel_path)
self.kernel_client.start_kernel(
name=self.kernel_name, path=self.kernel_path
)
elif self.kernel_client.kernel is None:
msg = f"Unable to connect to kernel with ID {self.existing}."
raise RuntimeError(msg)
Expand Down
77 changes: 58 additions & 19 deletions jupyter_kernel_client/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def fetch(
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "Jupyter Kernel Client"
"User-Agent": "Jupyter Kernel Client",
}
headers.update(kwargs.pop("headers", {}))
if token:
Expand Down Expand Up @@ -88,15 +88,17 @@ def __init__(
else:
self.__extra_headers = {}

if 'headers' not in self.__client_kwargs:
self.__client_kwargs['headers'] = {}
self.__client_kwargs['headers'].update(self.__extra_headers)
if "headers" not in self.__client_kwargs:
self.__client_kwargs["headers"] = {}
self.__client_kwargs["headers"].update(self.__extra_headers)

if kernel_id:
self.__kernel = {
"id": kernel_id,
"execution_state": "unknown",
"last_activity": datetime.datetime.strftime(utcnow(), "%Y-%m-%dT%H:%M:%S.%fZ"),
"last_activity": datetime.datetime.strftime(
utcnow(), "%Y-%m-%dT%H:%M:%S.%fZ"
),
}
self.refresh_model()

Expand All @@ -118,7 +120,9 @@ def kernel_url(self) -> str | None:
else:
return None

client_class = DottedObjectName("jupyter_kernel_client.wsclient.KernelWebSocketClient")
client_class = DottedObjectName(
"jupyter_kernel_client.wsclient.KernelWebSocketClient"
)
client_factory = Type(klass="jupyter_client.client.KernelClientABC")

@default("client_factory")
Expand All @@ -137,7 +141,9 @@ def _client_class_changed(self, change: dict[str, DottedObjectName]) -> None:
def client(self) -> t.Any:
"""Create a client configured to connect to our kernel."""
if not self.kernel_url:
raise RuntimeError("You must first start a kernel before requesting a client.")
raise RuntimeError(
"You must first start a kernel before requesting a client."
)

if not self.__client:
base_ws_url = HTTP_PROTOCOL_REGEXP.sub("ws", self.kernel_url, 1)
Expand All @@ -156,7 +162,9 @@ def client(self) -> t.Any:

return self.__client

def refresh_model(self, timeout: float = REQUEST_TIMEOUT) -> dict[str, t.Any] | None:
def refresh_model(
self, timeout: float = REQUEST_TIMEOUT
) -> dict[str, t.Any] | None:
"""Refresh the kernel model.

Returns
Expand All @@ -173,7 +181,13 @@ def refresh_model(self, timeout: float = REQUEST_TIMEOUT) -> dict[str, t.Any] |

self.log.debug("Request kernel at: %s", self.kernel_url)
try:
response = fetch(self.kernel_url, token=self.token, method="GET", timeout=timeout, headers=self.__extra_headers)
response = fetch(
self.kernel_url,
token=self.token,
method="GET",
timeout=timeout,
headers=self.__extra_headers,
)
except HTTPError as error:
if error.response.status_code == 404:
self.log.warning("Kernel not found at: %s", self.kernel_url)
Expand All @@ -200,7 +214,13 @@ def list_kernels(self, timeout: float = REQUEST_TIMEOUT) -> list[dict[str, t.Any
kernels_url = url_path_join(self.server_url, "api/kernels")
self.log.debug("Request kernels at: %s", kernels_url)
try:
response = fetch(kernels_url, token=self.token, method="GET", timeout=timeout, headers=self.__extra_headers)
response = fetch(
kernels_url,
token=self.token,
method="GET",
timeout=timeout,
headers=self.__extra_headers,
)
except HTTPError as error:
self.log.error("Error fetching kernels: %s", error)
return []
Expand All @@ -209,7 +229,6 @@ def list_kernels(self, timeout: float = REQUEST_TIMEOUT) -> list[dict[str, t.Any
self.log.debug("Kernels retrieved: %s", models)
return models


# --------------------------------------------------------------------------
# Kernel management
# --------------------------------------------------------------------------
Expand Down Expand Up @@ -241,7 +260,7 @@ def start_kernel(
method="POST",
json={"name": name, "path": path},
timeout=timeout,
headers=self.__extra_headers
headers=self.__extra_headers,
)

self.__kernel = response.json()
Expand All @@ -256,7 +275,9 @@ def shutdown_kernel(
):
"""Attempts to stop the kernel process cleanly via HTTP."""
if not self.kernel_url:
raise RuntimeError("You must first start a kernel before requesting a client.")
raise RuntimeError(
"You must first start a kernel before requesting a client."
)

self.log.debug("Request shutdown kernel at: %s", self.kernel_url)
if not now:
Expand All @@ -271,7 +292,13 @@ def shutdown_kernel(

# If not now and refreshing the model still returns it, try the http way
try:
response = fetch(self.kernel_url, token=self.token, method="DELETE", timeout=timeout, headers=self.__extra_headers)
response = fetch(
self.kernel_url,
token=self.token,
method="DELETE",
timeout=timeout,
headers=self.__extra_headers,
)
self.log.debug(
"Shutdown kernel response: %d %s",
response.status_code,
Expand All @@ -291,17 +318,29 @@ def shutdown_kernel(
def restart_kernel(self, timeout: float = REQUEST_TIMEOUT, **kw):
"""Restarts a kernel via HTTP request."""
if not self.kernel_url:
raise RuntimeError("You must first start a kernel before requesting a client.")
raise RuntimeError(
"You must first start a kernel before requesting a client."
)

kernel_url = self.kernel_url + "/restart"
self.log.debug("Request restart kernel at: %s", kernel_url)
response = fetch(kernel_url, token=self.token, method="POST", timeout=timeout, headers=self.__extra_headers)
self.log.debug("Restart kernel response: %d %s", response.status_code, response.reason)
response = fetch(
kernel_url,
token=self.token,
method="POST",
timeout=timeout,
headers=self.__extra_headers,
)
self.log.debug(
"Restart kernel response: %d %s", response.status_code, response.reason
)

def interrupt_kernel(self, timeout: float = REQUEST_TIMEOUT):
"""Interrupts the kernel via an HTTP request."""
if not self.kernel_url:
raise RuntimeError("You must first start a kernel before requesting a client.")
raise RuntimeError(
"You must first start a kernel before requesting a client."
)

kernel_url = self.kernel_url + "/interrupt"
self.log.debug("Request interrupt kernel at: %s", kernel_url)
Expand All @@ -310,7 +349,7 @@ def interrupt_kernel(self, timeout: float = REQUEST_TIMEOUT):
token=self.token,
method="POST",
timeout=timeout,
headers=self.__extra_headers
headers=self.__extra_headers,
)
self.log.debug(
"Interrupt kernel response: %d %s",
Expand Down
11 changes: 8 additions & 3 deletions jupyter_kernel_client/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
from jupyter_console.ptshell import ZMQTerminalInteractiveShell

class WSTerminalInteractiveShell(ZMQTerminalInteractiveShell):
manager = Instance("jupyter_kernel_client.manager.KernelHttpManager", allow_none=True)
client = Instance("jupyter_kernel_client.wsclient.KernelWebSocketClient", allow_none=True)
manager = Instance(
"jupyter_kernel_client.manager.KernelHttpManager", allow_none=True
)
client = Instance(
"jupyter_kernel_client.wsclient.KernelWebSocketClient", allow_none=True
)

@default("banner")
def _default_banner(self):
Expand All @@ -36,7 +40,8 @@ async def handle_external_iopub(self, loop=None):
def show_banner(self):
print( # noqa T201
self.banner.format(
version=__version__, kernel_banner=self.kernel_info.get("banner", "")
version=__version__,
kernel_banner=self.kernel_info.get("banner", ""),
),
end="",
flush=True,
Expand Down
4 changes: 3 additions & 1 deletion jupyter_kernel_client/snippets.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ def register(self, language: str, snippets: LanguageSnippets) -> None:
snippets: Language snippets
"""
if language in self._snippets:
warnings.warn(f"Snippets for language {language} will be overridden.", stacklevel=2)
warnings.warn(
f"Snippets for language {language} will be overridden.", stacklevel=2
)
self._snippets[language] = snippets

def get_list_variables(self, language: str) -> str:
Expand Down
Loading
Loading