first comit

This commit is contained in:
2024-02-23 10:30:02 +00:00
commit ddeb07d0ba
12482 changed files with 1857507 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
from .blocking import BlockingInProcessKernelClient
from .channels import InProcessChannel, InProcessHBChannel
from .client import InProcessKernelClient
from .manager import InProcessKernelManager

View File

@@ -0,0 +1,108 @@
""" Implements a fully blocking kernel client.
Useful for test suites and blocking terminal interfaces.
"""
import sys
# -----------------------------------------------------------------------------
# Copyright (C) 2012 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------
from queue import Empty, Queue
# IPython imports
from traitlets import Type
# Local imports
from .channels import InProcessChannel
from .client import InProcessKernelClient
class BlockingInProcessChannel(InProcessChannel):
"""A blocking in-process channel."""
def __init__(self, *args, **kwds):
"""Initialize the channel."""
super().__init__(*args, **kwds)
self._in_queue: Queue[object] = Queue()
def call_handlers(self, msg):
"""Call the handlers for a message."""
self._in_queue.put(msg)
def get_msg(self, block=True, timeout=None):
"""Gets a message if there is one that is ready."""
if timeout is None:
# Queue.get(timeout=None) has stupid uninteruptible
# behavior, so wait for a week instead
timeout = 604800
return self._in_queue.get(block, timeout)
def get_msgs(self):
"""Get all messages that are currently ready."""
msgs = []
while True:
try:
msgs.append(self.get_msg(block=False))
except Empty:
break
return msgs
def msg_ready(self):
"""Is there a message that has been received?"""
return not self._in_queue.empty()
class BlockingInProcessStdInChannel(BlockingInProcessChannel):
"""A blocking in-process stdin channel."""
def call_handlers(self, msg):
"""Overridden for the in-process channel.
This methods simply calls raw_input directly.
"""
msg_type = msg["header"]["msg_type"]
if msg_type == "input_request":
_raw_input = self.client.kernel._sys_raw_input
prompt = msg["content"]["prompt"]
print(prompt, end="", file=sys.__stdout__)
sys.__stdout__.flush()
self.client.input(_raw_input())
class BlockingInProcessKernelClient(InProcessKernelClient):
"""A blocking in-process kernel client."""
# The classes to use for the various channels.
shell_channel_class = Type(BlockingInProcessChannel) # type:ignore[arg-type]
iopub_channel_class = Type(BlockingInProcessChannel) # type:ignore[arg-type]
stdin_channel_class = Type(BlockingInProcessStdInChannel) # type:ignore[arg-type]
def wait_for_ready(self):
"""Wait for kernel info reply on shell channel."""
while True:
self.kernel_info()
try:
msg = self.shell_channel.get_msg(block=True, timeout=1)
except Empty:
pass
else:
if msg["msg_type"] == "kernel_info_reply":
# Checking that IOPub is connected. If it is not connected, start over.
try:
self.iopub_channel.get_msg(block=True, timeout=0.2)
except Empty:
pass
else:
self._handle_kernel_info_reply(msg)
break
# Flush IOPub channel
while True:
try:
msg = self.iopub_channel.get_msg(block=True, timeout=0.2)
print(msg["msg_type"])
except Empty:
break

View File

@@ -0,0 +1,109 @@
"""A kernel client for in-process kernels."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from typing import List
from jupyter_client.channelsabc import HBChannelABC
# -----------------------------------------------------------------------------
# Channel classes
# -----------------------------------------------------------------------------
class InProcessChannel:
"""Base class for in-process channels."""
proxy_methods: List[object] = []
def __init__(self, client=None):
"""Initialize the channel."""
super().__init__()
self.client = client
self._is_alive = False
def is_alive(self):
"""Test if the channel is alive."""
return self._is_alive
def start(self):
"""Start the channel."""
self._is_alive = True
def stop(self):
"""Stop the channel."""
self._is_alive = False
def call_handlers(self, msg):
"""This method is called in the main thread when a message arrives.
Subclasses should override this method to handle incoming messages.
"""
msg = "call_handlers must be defined in a subclass."
raise NotImplementedError(msg)
def flush(self, timeout=1.0):
"""Flush the channel."""
def call_handlers_later(self, *args, **kwds):
"""Call the message handlers later.
The default implementation just calls the handlers immediately, but this
method exists so that GUI toolkits can defer calling the handlers until
after the event loop has run, as expected by GUI frontends.
"""
self.call_handlers(*args, **kwds)
def process_events(self):
"""Process any pending GUI events.
This method will be never be called from a frontend without an event
loop (e.g., a terminal frontend).
"""
raise NotImplementedError
class InProcessHBChannel:
"""A dummy heartbeat channel interface for in-process kernels.
Normally we use the heartbeat to check that the kernel process is alive.
When the kernel is in-process, that doesn't make sense, but clients still
expect this interface.
"""
time_to_dead = 3.0
def __init__(self, client=None):
"""Initialize the channel."""
super().__init__()
self.client = client
self._is_alive = False
self._pause = True
def is_alive(self):
"""Test if the channel is alive."""
return self._is_alive
def start(self):
"""Start the channel."""
self._is_alive = True
def stop(self):
"""Stop the channel."""
self._is_alive = False
def pause(self):
"""Pause the channel."""
self._pause = True
def unpause(self):
"""Unpause the channel."""
self._pause = False
def is_beating(self):
"""Test if the channel is beating."""
return not self._pause
HBChannelABC.register(InProcessHBChannel)

View File

@@ -0,0 +1,223 @@
"""A client for in-process kernels."""
# -----------------------------------------------------------------------------
# Copyright (C) 2012 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file LICENSE, distributed as part of this software.
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Imports
# -----------------------------------------------------------------------------
import asyncio
from jupyter_client.client import KernelClient
from jupyter_client.clientabc import KernelClientABC
from jupyter_core.utils import run_sync
# IPython imports
from traitlets import Instance, Type, default
# Local imports
from .channels import InProcessChannel, InProcessHBChannel
# -----------------------------------------------------------------------------
# Main kernel Client class
# -----------------------------------------------------------------------------
class InProcessKernelClient(KernelClient):
"""A client for an in-process kernel.
This class implements the interface of
`jupyter_client.clientabc.KernelClientABC` and allows
(asynchronous) frontends to be used seamlessly with an in-process kernel.
See `jupyter_client.client.KernelClient` for docstrings.
"""
# The classes to use for the various channels.
shell_channel_class = Type(InProcessChannel) # type:ignore[arg-type]
iopub_channel_class = Type(InProcessChannel) # type:ignore[arg-type]
stdin_channel_class = Type(InProcessChannel) # type:ignore[arg-type]
control_channel_class = Type(InProcessChannel) # type:ignore[arg-type]
hb_channel_class = Type(InProcessHBChannel) # type:ignore[arg-type]
kernel = Instance("ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True)
# --------------------------------------------------------------------------
# Channel management methods
# --------------------------------------------------------------------------
@default("blocking_class")
def _default_blocking_class(self):
from .blocking import BlockingInProcessKernelClient
return BlockingInProcessKernelClient
def get_connection_info(self):
"""Get the connection info for the client."""
d = super().get_connection_info()
d["kernel"] = self.kernel # type:ignore[assignment]
return d
def start_channels(self, *args, **kwargs):
"""Start the channels on the client."""
super().start_channels()
if self.kernel:
self.kernel.frontends.append(self)
@property
def shell_channel(self):
if self._shell_channel is None:
self._shell_channel = self.shell_channel_class(self) # type:ignore[abstract,call-arg]
return self._shell_channel
@property
def iopub_channel(self):
if self._iopub_channel is None:
self._iopub_channel = self.iopub_channel_class(self) # type:ignore[abstract,call-arg]
return self._iopub_channel
@property
def stdin_channel(self):
if self._stdin_channel is None:
self._stdin_channel = self.stdin_channel_class(self) # type:ignore[abstract,call-arg]
return self._stdin_channel
@property
def control_channel(self):
if self._control_channel is None:
self._control_channel = self.control_channel_class(self) # type:ignore[abstract,call-arg]
return self._control_channel
@property
def hb_channel(self):
if self._hb_channel is None:
self._hb_channel = self.hb_channel_class(self) # type:ignore[abstract,call-arg]
return self._hb_channel
# Methods for sending specific messages
# -------------------------------------
def execute(
self, code, silent=False, store_history=True, user_expressions=None, allow_stdin=None
):
"""Execute code on the client."""
if allow_stdin is None:
allow_stdin = self.allow_stdin
content = dict(
code=code,
silent=silent,
store_history=store_history,
user_expressions=user_expressions or {},
allow_stdin=allow_stdin,
)
msg = self.session.msg("execute_request", content)
self._dispatch_to_kernel(msg)
return msg["header"]["msg_id"]
def complete(self, code, cursor_pos=None):
"""Get code completion."""
if cursor_pos is None:
cursor_pos = len(code)
content = dict(code=code, cursor_pos=cursor_pos)
msg = self.session.msg("complete_request", content)
self._dispatch_to_kernel(msg)
return msg["header"]["msg_id"]
def inspect(self, code, cursor_pos=None, detail_level=0):
"""Get code inspection."""
if cursor_pos is None:
cursor_pos = len(code)
content = dict(
code=code,
cursor_pos=cursor_pos,
detail_level=detail_level,
)
msg = self.session.msg("inspect_request", content)
self._dispatch_to_kernel(msg)
return msg["header"]["msg_id"]
def history(self, raw=True, output=False, hist_access_type="range", **kwds):
"""Get code history."""
content = dict(raw=raw, output=output, hist_access_type=hist_access_type, **kwds)
msg = self.session.msg("history_request", content)
self._dispatch_to_kernel(msg)
return msg["header"]["msg_id"]
def shutdown(self, restart=False):
"""Handle shutdown."""
# FIXME: What to do here?
msg = "Cannot shutdown in-process kernel"
raise NotImplementedError(msg)
def kernel_info(self):
"""Request kernel info."""
msg = self.session.msg("kernel_info_request")
self._dispatch_to_kernel(msg)
return msg["header"]["msg_id"]
def comm_info(self, target_name=None):
"""Request a dictionary of valid comms and their targets."""
content = {} if target_name is None else dict(target_name=target_name)
msg = self.session.msg("comm_info_request", content)
self._dispatch_to_kernel(msg)
return msg["header"]["msg_id"]
def input(self, string):
"""Handle kernel input."""
if self.kernel is None:
msg = "Cannot send input reply. No kernel exists."
raise RuntimeError(msg)
self.kernel.raw_input_str = string
def is_complete(self, code):
"""Handle an is_complete request."""
msg = self.session.msg("is_complete_request", {"code": code})
self._dispatch_to_kernel(msg)
return msg["header"]["msg_id"]
def _dispatch_to_kernel(self, msg):
"""Send a message to the kernel and handle a reply."""
kernel = self.kernel
if kernel is None:
msg = "Cannot send request. No kernel exists."
raise RuntimeError(msg)
stream = kernel.shell_stream
self.session.send(stream, msg)
msg_parts = stream.recv_multipart()
if run_sync is not None:
dispatch_shell = run_sync(kernel.dispatch_shell)
dispatch_shell(msg_parts)
else:
loop = asyncio.get_event_loop() # type:ignore[unreachable]
loop.run_until_complete(kernel.dispatch_shell(msg_parts))
idents, reply_msg = self.session.recv(stream, copy=False)
self.shell_channel.call_handlers_later(reply_msg)
def get_shell_msg(self, block=True, timeout=None):
"""Get a shell message."""
return self.shell_channel.get_msg(block, timeout)
def get_iopub_msg(self, block=True, timeout=None):
"""Get an iopub message."""
return self.iopub_channel.get_msg(block, timeout)
def get_stdin_msg(self, block=True, timeout=None):
"""Get a stdin message."""
return self.stdin_channel.get_msg(block, timeout)
def get_control_msg(self, block=True, timeout=None):
"""Get a control message."""
return self.control_channel.get_msg(block, timeout)
# -----------------------------------------------------------------------------
# ABC Registration
# -----------------------------------------------------------------------------
KernelClientABC.register(InProcessKernelClient)

View File

@@ -0,0 +1,8 @@
"""Shared constants.
"""
# Because inprocess communication is not networked, we can use a common Session
# key everywhere. This is not just the empty bytestring to avoid tripping
# certain security checks in the rest of Jupyter that assumes that empty keys
# are insecure.
INPROCESS_KEY = b"inprocess"

View File

@@ -0,0 +1,203 @@
"""An in-process kernel"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import logging
import sys
from contextlib import contextmanager
from IPython.core.interactiveshell import InteractiveShellABC
from traitlets import Any, Enum, Instance, List, Type, default
from ipykernel.ipkernel import IPythonKernel
from ipykernel.jsonutil import json_clean
from ipykernel.zmqshell import ZMQInteractiveShell
from ..iostream import BackgroundSocket, IOPubThread, OutStream
from .constants import INPROCESS_KEY
from .socket import DummySocket
# -----------------------------------------------------------------------------
# Main kernel class
# -----------------------------------------------------------------------------
class InProcessKernel(IPythonKernel):
"""An in-process kernel."""
# -------------------------------------------------------------------------
# InProcessKernel interface
# -------------------------------------------------------------------------
# The frontends connected to this kernel.
frontends = List(Instance("ipykernel.inprocess.client.InProcessKernelClient", allow_none=True))
# The GUI environment that the kernel is running under. This need not be
# specified for the normal operation for the kernel, but is required for
# IPython's GUI support (including pylab). The default is 'inline' because
# it is safe under all GUI toolkits.
gui = Enum(("tk", "gtk", "wx", "qt", "qt4", "inline"), default_value="inline")
raw_input_str = Any()
stdout = Any()
stderr = Any()
# -------------------------------------------------------------------------
# Kernel interface
# -------------------------------------------------------------------------
shell_class = Type(allow_none=True) # type:ignore[assignment]
_underlying_iopub_socket = Instance(DummySocket, ())
iopub_thread: IOPubThread = Instance(IOPubThread) # type:ignore[assignment]
shell_stream = Instance(DummySocket, ()) # type:ignore[arg-type]
@default("iopub_thread")
def _default_iopub_thread(self):
thread = IOPubThread(self._underlying_iopub_socket)
thread.start()
return thread
iopub_socket: BackgroundSocket = Instance(BackgroundSocket) # type:ignore[assignment]
@default("iopub_socket")
def _default_iopub_socket(self):
return self.iopub_thread.background_socket
stdin_socket = Instance(DummySocket, ()) # type:ignore[assignment]
def __init__(self, **traits):
"""Initialize the kernel."""
super().__init__(**traits)
self._underlying_iopub_socket.observe(self._io_dispatch, names=["message_sent"])
if self.shell:
self.shell.kernel = self
async def execute_request(self, stream, ident, parent):
"""Override for temporary IO redirection."""
with self._redirected_io():
await super().execute_request(stream, ident, parent)
def start(self):
"""Override registration of dispatchers for streams."""
if self.shell:
self.shell.exit_now = False
def _abort_queues(self):
"""The in-process kernel doesn't abort requests."""
async def _flush_control_queue(self):
"""No need to flush control queues for in-process"""
def _input_request(self, prompt, ident, parent, password=False):
# Flush output before making the request.
self.raw_input_str = None
sys.stderr.flush()
sys.stdout.flush()
# Send the input request.
content = json_clean(dict(prompt=prompt, password=password))
assert self.session is not None
msg = self.session.msg("input_request", content, parent)
for frontend in self.frontends:
assert frontend is not None
if frontend.session.session == parent["header"]["session"]:
frontend.stdin_channel.call_handlers(msg)
break
else:
logging.error("No frontend found for raw_input request")
return ""
# Await a response.
while self.raw_input_str is None:
frontend.stdin_channel.process_events()
return self.raw_input_str # type:ignore[unreachable]
# -------------------------------------------------------------------------
# Protected interface
# -------------------------------------------------------------------------
@contextmanager
def _redirected_io(self):
"""Temporarily redirect IO to the kernel."""
sys_stdout, sys_stderr = sys.stdout, sys.stderr
try:
sys.stdout, sys.stderr = self.stdout, self.stderr
yield
finally:
sys.stdout, sys.stderr = sys_stdout, sys_stderr
# ------ Trait change handlers --------------------------------------------
def _io_dispatch(self, change):
"""Called when a message is sent to the IO socket."""
assert self.iopub_socket.io_thread is not None
assert self.session is not None
ident, msg = self.session.recv(self.iopub_socket.io_thread.socket, copy=False)
for frontend in self.frontends:
assert frontend is not None
frontend.iopub_channel.call_handlers(msg)
# ------ Trait initializers -----------------------------------------------
@default("log")
def _default_log(self):
return logging.getLogger(__name__)
@default("session")
def _default_session(self):
from jupyter_client.session import Session
return Session(parent=self, key=INPROCESS_KEY)
@default("shell_class")
def _default_shell_class(self):
return InProcessInteractiveShell
@default("stdout")
def _default_stdout(self):
return OutStream(self.session, self.iopub_thread, "stdout", watchfd=False)
@default("stderr")
def _default_stderr(self):
return OutStream(self.session, self.iopub_thread, "stderr", watchfd=False)
# -----------------------------------------------------------------------------
# Interactive shell subclass
# -----------------------------------------------------------------------------
class InProcessInteractiveShell(ZMQInteractiveShell):
"""An in-process interactive shell."""
kernel: InProcessKernel = Instance(
"ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True
) # type:ignore[assignment]
# -------------------------------------------------------------------------
# InteractiveShell interface
# -------------------------------------------------------------------------
def enable_gui(self, gui=None):
"""Enable GUI integration for the kernel."""
if not gui:
gui = self.kernel.gui
self.active_eventloop = gui
def enable_matplotlib(self, gui=None):
"""Enable matplotlib integration for the kernel."""
if not gui:
gui = self.kernel.gui
return super().enable_matplotlib(gui)
def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
"""Activate pylab support at runtime."""
if not gui:
gui = self.kernel.gui
return super().enable_pylab(gui, import_all, welcome_message)
InteractiveShellABC.register(InProcessInteractiveShell)

View File

@@ -0,0 +1,92 @@
"""A kernel manager for in-process kernels."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from jupyter_client.manager import KernelManager
from jupyter_client.managerabc import KernelManagerABC
from jupyter_client.session import Session
from traitlets import DottedObjectName, Instance, default
from .constants import INPROCESS_KEY
class InProcessKernelManager(KernelManager):
"""A manager for an in-process kernel.
This class implements the interface of
`jupyter_client.kernelmanagerabc.KernelManagerABC` and allows
(asynchronous) frontends to be used seamlessly with an in-process kernel.
See `jupyter_client.kernelmanager.KernelManager` for docstrings.
"""
# The kernel process with which the KernelManager is communicating.
kernel = Instance("ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True)
# the client class for KM.client() shortcut
client_class = DottedObjectName("ipykernel.inprocess.BlockingInProcessKernelClient")
@default("blocking_class")
def _default_blocking_class(self):
from .blocking import BlockingInProcessKernelClient
return BlockingInProcessKernelClient
@default("session")
def _default_session(self):
# don't sign in-process messages
return Session(key=INPROCESS_KEY, parent=self)
# --------------------------------------------------------------------------
# Kernel management methods
# --------------------------------------------------------------------------
def start_kernel(self, **kwds):
"""Start the kernel."""
from ipykernel.inprocess.ipkernel import InProcessKernel
self.kernel = InProcessKernel(parent=self, session=self.session)
def shutdown_kernel(self):
"""Shutdown the kernel."""
if self.kernel:
self.kernel.iopub_thread.stop()
self._kill_kernel()
def restart_kernel(self, now=False, **kwds):
"""Restart the kernel."""
self.shutdown_kernel()
self.start_kernel(**kwds)
@property
def has_kernel(self):
return self.kernel is not None
def _kill_kernel(self):
self.kernel = None
def interrupt_kernel(self):
"""Interrupt the kernel."""
msg = "Cannot interrupt in-process kernel."
raise NotImplementedError(msg)
def signal_kernel(self, signum):
"""Send a signal to the kernel."""
msg = "Cannot signal in-process kernel."
raise NotImplementedError(msg)
def is_alive(self):
"""Test if the kernel is alive."""
return self.kernel is not None
def client(self, **kwargs):
"""Get a client for the kernel."""
kwargs["kernel"] = self.kernel
return super().client(**kwargs)
# -----------------------------------------------------------------------------
# ABC Registration
# -----------------------------------------------------------------------------
KernelManagerABC.register(InProcessKernelManager)

View File

@@ -0,0 +1,41 @@
""" Defines a dummy socket implementing (part of) the zmq.Socket interface. """
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from queue import Queue
import zmq
from traitlets import HasTraits, Instance, Int
# -----------------------------------------------------------------------------
# Dummy socket class
# -----------------------------------------------------------------------------
class DummySocket(HasTraits):
"""A dummy socket implementing (part of) the zmq.Socket interface."""
queue = Instance(Queue, ())
message_sent = Int(0) # Should be an Event
context = Instance(zmq.Context)
def _context_default(self):
return zmq.Context()
# -------------------------------------------------------------------------
# Socket interface
# -------------------------------------------------------------------------
def recv_multipart(self, flags=0, copy=True, track=False):
"""Recv a multipart message."""
return self.queue.get_nowait()
def send_multipart(self, msg_parts, flags=0, copy=True, track=False):
"""Send a multipart message."""
msg_parts = list(map(zmq.Message, msg_parts))
self.queue.put_nowait(msg_parts)
self.message_sent += 1
def flush(self, timeout=1.0):
"""no-op to comply with stream API"""