Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jupyter/jupyter_client
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v8.3.0
Choose a base ref
...
head repository: jupyter/jupyter_client
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: b4f7d947fae55a4fe59a27df0830a9a78dcd4e12
Choose a head ref
  • 4 commits
  • 8 files changed
  • 5 contributors

Commits on Jul 7, 2023

  1. Make cache_ports configurable with default value of False. (#956)

    Co-authored-by: Your Name <you@example.com>
    Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
    3 people authored Jul 7, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6dda5ad View commit details
  2. [pre-commit.ci] pre-commit autoupdate (#954)

    Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
    Co-authored-by: Steven Silvester <steven.silvester@ieee.org>
    pre-commit-ci[bot] and blink1073 authored Jul 7, 2023
    Copy the full SHA
    508982f View commit details

Commits on Aug 1, 2023

  1. [pre-commit.ci] pre-commit autoupdate (#962)

    Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
    pre-commit-ci[bot] authored Aug 1, 2023
    Copy the full SHA
    c08b87c View commit details

Commits on Aug 29, 2023

  1. Support external kernels (#961)

    * Support external kernels
    
    * Ignore linter rule
    
    * Check if connection directory exists
    
    * Use load_connection_info()
    davidbrochart authored Aug 29, 2023
    Copy the full SHA
    b4f7d94 View commit details
Showing with 114 additions and 12 deletions.
  1. +4 −4 .pre-commit-config.yaml
  2. +1 −1 jupyter_client/connect.py
  3. +17 −3 jupyter_client/manager.py
  4. +54 −1 jupyter_client/multikernelmanager.py
  5. +33 −0 jupyter_client/utils.py
  6. +3 −1 pyproject.toml
  7. +1 −1 tests/test_adapter.py
  8. +1 −1 tests/test_jsonutil.py
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ repos:
- id: trailing-whitespace

- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.23.1
rev: 0.23.3
hooks:
- id: check-github-workflows

@@ -30,12 +30,12 @@ repos:
- id: mdformat

- repo: https://github.com/psf/black
rev: 23.3.0
rev: 23.7.0
hooks:
- id: black

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.270
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.281
hooks:
- id: ruff
args: ["--fix"]
2 changes: 1 addition & 1 deletion jupyter_client/connect.py
Original file line number Diff line number Diff line change
@@ -114,7 +114,7 @@ def write_connection_file(
else:
N = 1
for _ in range(ports_needed):
while os.path.exists(f"{ip}-{str(N)}"):
while os.path.exists(f"{ip}-{N!s}"):
N += 1
ports.append(N)
N += 1
20 changes: 17 additions & 3 deletions jupyter_client/manager.py
Original file line number Diff line number Diff line change
@@ -85,7 +85,8 @@ async def wrapper(self, *args, **kwargs):
out = await method(self, *args, **kwargs)
# Add a small sleep to ensure tests can capture the state before done
await asyncio.sleep(0.01)
self._ready.set_result(None)
if self.owns_kernel:
self._ready.set_result(None)
return out
except Exception as e:
self._ready.set_exception(e)
@@ -105,6 +106,7 @@ class KernelManager(ConnectionFileMixin):

def __init__(self, *args, **kwargs):
"""Initialize a kernel manager."""
self._owns_kernel = kwargs.pop("owns_kernel", True)
super().__init__(**kwargs)
self._shutdown_status = _ShutdownStatus.Unset
self._attempted_start = False
@@ -178,12 +180,14 @@ def _kernel_name_changed(self, change: t.Dict[str, str]) -> None:

@property
def kernel_spec(self) -> t.Optional[kernelspec.KernelSpec]:
if self._kernel_spec is None and self.kernel_name != "": # noqa
if self._kernel_spec is None and self.kernel_name != "":
self._kernel_spec = self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
return self._kernel_spec

cache_ports: Bool = Bool(
help="True if the MultiKernelManager should cache ports for this KernelManager instance"
False,
config=True,
help="True if the MultiKernelManager should cache ports for this KernelManager instance",
)

@default("cache_ports") # type:ignore[misc]
@@ -493,6 +497,9 @@ async def _async_shutdown_kernel(self, now: bool = False, restart: bool = False)
Will this kernel be restarted after it is shutdown. When this
is True, connection files will not be cleaned up.
"""
if not self.owns_kernel:
return

self.shutting_down = True # Used by restarter to prevent race condition
# Stop monitoring for restarting while we shutdown.
self.stop_restarter()
@@ -556,6 +563,10 @@ async def _async_restart_kernel(

restart_kernel = run_sync(_async_restart_kernel)

@property
def owns_kernel(self) -> bool:
return self._owns_kernel

@property
def has_kernel(self) -> bool:
"""Has a kernel process been started that we are actively managing."""
@@ -644,6 +655,9 @@ async def _async_signal_kernel(self, signum: int) -> None:

async def _async_is_alive(self) -> bool:
"""Is the kernel process still running?"""
if not self.owns_kernel:
return True

if self.has_kernel:
assert self.provisioner is not None
ret = await self.provisioner.poll()
55 changes: 54 additions & 1 deletion jupyter_client/multikernelmanager.py
Original file line number Diff line number Diff line change
@@ -2,20 +2,23 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import asyncio
import json
import os
import socket
import typing as t
import uuid
from functools import wraps
from pathlib import Path

import zmq
from traitlets import Any, Bool, Dict, DottedObjectName, Instance, Unicode, default, observe
from traitlets.config.configurable import LoggingConfigurable
from traitlets.utils.importstring import import_item

from .connect import KernelConnectionInfo
from .kernelspec import NATIVE_KERNEL_NAME, KernelSpecManager
from .manager import KernelManager
from .utils import ensure_async, run_sync
from .utils import ensure_async, run_sync, utcnow


class DuplicateKernelError(Exception):
@@ -105,9 +108,14 @@ def _context_default(self) -> zmq.Context:
return zmq.Context()

connection_dir = Unicode("")
external_connection_dir = Unicode(None, allow_none=True)

_kernels = Dict()

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.kernel_id_to_connection_file = {}

def __del__(self):
"""Handle garbage collection. Destroy context if applicable."""
if self._created_context and self.context and not self.context.closed:
@@ -123,6 +131,51 @@ def __del__(self):

def list_kernel_ids(self) -> t.List[str]:
"""Return a list of the kernel ids of the active kernels."""
if self.external_connection_dir is not None:
external_connection_dir = Path(self.external_connection_dir)
if external_connection_dir.is_dir():
connection_files = [p for p in external_connection_dir.iterdir() if p.is_file()]

# remove kernels (whose connection file has disappeared) from our list
k = list(self.kernel_id_to_connection_file.keys())
v = list(self.kernel_id_to_connection_file.values())
for connection_file in list(self.kernel_id_to_connection_file.values()):
if connection_file not in connection_files:
kernel_id = k[v.index(connection_file)]
del self.kernel_id_to_connection_file[kernel_id]
del self._kernels[kernel_id]

# add kernels (whose connection file appeared) to our list
for connection_file in connection_files:
if connection_file in self.kernel_id_to_connection_file.values():
continue
try:
connection_info: KernelConnectionInfo = json.loads(
connection_file.read_text()
)
except Exception: # noqa: S112
continue
self.log.debug("Loading connection file %s", connection_file)
if not ("kernel_name" in connection_info and "key" in connection_info):
continue
# it looks like a connection file
kernel_id = self.new_kernel_id()
self.kernel_id_to_connection_file[kernel_id] = connection_file
km = self.kernel_manager_factory(
parent=self,
log=self.log,
owns_kernel=False,
)
km.load_connection_info(connection_info)
km.last_activity = utcnow()
km.execution_state = "idle"
km.connections = 1
km.kernel_id = kernel_id
km.kernel_name = connection_info["kernel_name"]
km.ready.set_result(None)

self._kernels[kernel_id] = km

# Create a copy so we can iterate over kernels in operations
# that delete keys.
return list(self._kernels.keys())
33 changes: 33 additions & 0 deletions jupyter_client/utils.py
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
- vendor functions from ipython_genutils that should be retired at some point.
"""
import os
from datetime import datetime, timedelta, tzinfo

from jupyter_core.utils import ensure_async, run_sync # noqa: F401 # noqa: F401

@@ -83,3 +84,35 @@ def _expand_path(s):
if os.name == "nt":
s = s.replace("IPYTHON_TEMP", "$\\")
return s


# constant for zero offset
ZERO = timedelta(0)


class tzUTC(tzinfo): # noqa
"""tzinfo object for UTC (zero offset)"""

def utcoffset(self, d):
"""Compute utcoffset."""
return ZERO

def dst(self, d):
"""Compute dst."""
return ZERO


UTC = tzUTC() # type:ignore


def utc_aware(unaware):
"""decorator for adding UTC tzinfo to datetime's utcfoo methods"""

def utc_method(*args, **kwargs):
dt = unaware(*args, **kwargs)
return dt.replace(tzinfo=UTC)

return utc_method


utcnow = utc_aware(datetime.utcnow)
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -111,7 +111,7 @@ test = "mypy --install-types --non-interactive {args:.}"
dependencies = [
"black[jupyter]==23.3.0",
"mdformat>0.7",
"ruff==0.0.270",
"ruff==0.0.276",
]
[tool.hatch.envs.lint.scripts]
style = [
@@ -249,6 +249,8 @@ ignore = [
"S110",
# PLW0603 Using the global statement to update
"PLW0603",
# Mutable class attributes should be annotated with `typing.ClassVar`
"RUF012",
]
unfixable = [
# Don't touch print statements
2 changes: 1 addition & 1 deletion tests/test_adapter.py
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ def test_default_version():

def test_code_to_line_no_code():
line, pos = code_to_line("", 0)
assert line == "" # noqa
assert line == ""
assert pos == 0


2 changes: 1 addition & 1 deletion tests/test_jsonutil.py
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ def __float__(self):

def test_parse_date_invalid():
assert jsonutil.parse_date(None) is None
assert jsonutil.parse_date("") == "" # noqa
assert jsonutil.parse_date("") == ""
assert jsonutil.parse_date("invalid-date") == "invalid-date"