diff --git a/jupyter_client/manager.py b/jupyter_client/manager.py index 088acd6c..2c571262 100644 --- a/jupyter_client/manager.py +++ b/jupyter_client/manager.py @@ -36,6 +36,7 @@ from .asynchronous import AsyncKernelClient from .blocking import BlockingKernelClient from .client import KernelClient +from .clientabc import KernelClientABC from .connect import ConnectionFileMixin from .managerabc import KernelManagerABC from .provisioning import KernelProvisionerBase @@ -58,6 +59,7 @@ class _ShutdownStatus(Enum): F = t.TypeVar("F", bound=t.Callable[..., t.Any]) +KernelClientBound = t.TypeVar("KernelClientBound", bound=KernelClientABC) def _get_future() -> t.Union[Future, CFuture]: @@ -98,7 +100,7 @@ async def wrapper(self: t.Any, *args: t.Any, **kwargs: t.Any) -> t.Any: return t.cast(F, wrapper) -class KernelManager(ConnectionFileMixin): +class _KernelManagerBase(ConnectionFileMixin, t.Generic[KernelClientBound]): """Manages a single kernel in a subprocess on this host. This version starts kernels with Popen. @@ -126,18 +128,15 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: _created_context: Bool = Bool(False) # The PyZMQ Context to use for communication with the kernel. - context: Instance = Instance(zmq.Context) + context: Instance @default("context") def _context_default(self) -> zmq.Context: - self._created_context = True - return zmq.Context() + raise NotImplementedError # the class to create with our `client` method - client_class: DottedObjectName = DottedObjectName( - "jupyter_client.blocking.BlockingKernelClient" - ) - client_factory: Type = Type(klass=KernelClient) + client_class: DottedObjectName + client_factory: Type @default("client_factory") def _client_factory_default(self) -> Type: @@ -260,7 +259,7 @@ def remove_restart_callback(self, callback: t.Callable, event: str = "restart") # create a Client connected to our Kernel # -------------------------------------------------------------------------- - def client(self, **kwargs: t.Any) -> BlockingKernelClient: + def client(self, **kwargs: t.Any) -> KernelClientBound: """Create a client configured to connect to our kernel""" kw: dict = {} kw.update(self.get_connection_info(session=True)) @@ -357,8 +356,6 @@ async def _async_launch_kernel(self, kernel_cmd: t.List[str], **kw: t.Any) -> No # and write the connection file, if not already done. self._reconcile_connection_info(connection_info) - _launch_kernel = run_sync(_async_launch_kernel) - # Control socket used for polite kernel shutdown def _connect_control_socket(self) -> None: @@ -401,8 +398,6 @@ async def _async_pre_start_kernel( kernel_cmd = kw.pop("cmd") return kernel_cmd, kw - pre_start_kernel = run_sync(_async_pre_start_kernel) - async def _async_post_start_kernel(self, **kw: t.Any) -> None: """Performs any post startup tasks relative to the kernel. @@ -416,8 +411,6 @@ async def _async_post_start_kernel(self, **kw: t.Any) -> None: assert self.provisioner is not None await self.provisioner.post_launch(**kw) - post_start_kernel = run_sync(_async_post_start_kernel) - @in_pending_state async def _async_start_kernel(self, **kw: t.Any) -> None: """Starts a kernel on this host in a separate process. @@ -439,8 +432,6 @@ async def _async_start_kernel(self, **kw: t.Any) -> None: await self._async_launch_kernel(kernel_cmd, **kw) await self._async_post_start_kernel(**kw) - start_kernel = run_sync(_async_start_kernel) - async def _async_request_shutdown(self, restart: bool = False) -> None: """Send a shutdown request via control channel""" content = {"restart": restart} @@ -452,8 +443,6 @@ async def _async_request_shutdown(self, restart: bool = False) -> None: await self.provisioner.shutdown_requested(restart=restart) self._shutdown_status = _ShutdownStatus.ShutdownRequest - request_shutdown = run_sync(_async_request_shutdown) - async def _async_finish_shutdown( self, waittime: t.Optional[float] = None, @@ -493,8 +482,6 @@ async def _async_finish_shutdown( assert self.provisioner is not None await self.provisioner.wait() - finish_shutdown = run_sync(_async_finish_shutdown) - async def _async_cleanup_resources(self, restart: bool = False) -> None: """Clean up resources when the kernel is shut down""" if not restart: @@ -510,8 +497,6 @@ async def _async_cleanup_resources(self, restart: bool = False) -> None: if self.provisioner: await self.provisioner.cleanup(restart=restart) - cleanup_resources = run_sync(_async_cleanup_resources) - @in_pending_state async def _async_shutdown_kernel(self, now: bool = False, restart: bool = False) -> None: """Attempts to stop the kernel process cleanly. @@ -552,8 +537,6 @@ async def _async_shutdown_kernel(self, now: bool = False, restart: bool = False) await self._async_cleanup_resources(restart=restart) - shutdown_kernel = run_sync(_async_shutdown_kernel) - async def _async_restart_kernel( self, now: bool = False, newports: bool = False, **kw: t.Any ) -> None: @@ -595,8 +578,6 @@ async def _async_restart_kernel( self._launch_args.update(kw) await self._async_start_kernel(**self._launch_args) - restart_kernel = run_sync(_async_restart_kernel) - @property def owns_kernel(self) -> bool: return self._owns_kernel @@ -612,8 +593,6 @@ async def _async_send_kernel_sigterm(self, restart: bool = False) -> None: assert self.provisioner is not None await self.provisioner.terminate(restart=restart) - _send_kernel_sigterm = run_sync(_async_send_kernel_sigterm) - async def _async_kill_kernel(self, restart: bool = False) -> None: """Kill the running kernel. @@ -635,8 +614,6 @@ async def _async_kill_kernel(self, restart: bool = False) -> None: if self.has_kernel: await self.provisioner.wait() - _kill_kernel = run_sync(_async_kill_kernel) - async def _async_interrupt_kernel(self) -> None: """Interrupts the kernel by sending it a signal. @@ -668,8 +645,6 @@ async def _async_interrupt_kernel(self) -> None: msg = "Cannot interrupt kernel. No kernel is running!" raise RuntimeError(msg) - interrupt_kernel = run_sync(_async_interrupt_kernel) - async def _async_signal_kernel(self, signum: int) -> None: """Sends a signal to the process group of the kernel (this usually includes the kernel and any subprocesses spawned by @@ -685,8 +660,6 @@ async def _async_signal_kernel(self, signum: int) -> None: msg = "Cannot signal kernel. No kernel is running!" raise RuntimeError(msg) - signal_kernel = run_sync(_async_signal_kernel) - async def _async_is_alive(self) -> bool: """Is the kernel process still running?""" if not self.owns_kernel: @@ -699,8 +672,6 @@ async def _async_is_alive(self) -> bool: return True return False - is_alive = run_sync(_async_is_alive) - async def _async_wait(self, pollinterval: float = 0.1) -> None: # Use busy loop at 100ms intervals, polling until the process is # not alive. If we find the process is no longer alive, complete @@ -710,7 +681,40 @@ async def _async_wait(self, pollinterval: float = 0.1) -> None: await asyncio.sleep(pollinterval) -class AsyncKernelManager(KernelManager): +class KernelManager(_KernelManagerBase[BlockingKernelClient]): + """A blocking kernel manager.""" + + # the class to create with our `client` method + client_class: DottedObjectName = DottedObjectName( + "jupyter_client.blocking.BlockingKernelClient" + ) + client_factory: Type = Type(klass=BlockingKernelClient) + + # The PyZMQ Context to use for communication with the kernel. + context: Instance = Instance(zmq.Context) + + @default("context") + def _context_default(self) -> zmq.Context: + self._created_context = True + return zmq.Context() + + _launch_kernel = run_sync(_KernelManagerBase._async_launch_kernel) + start_kernel = run_sync(_KernelManagerBase._async_start_kernel) + pre_start_kernel = run_sync(_KernelManagerBase._async_pre_start_kernel) + post_start_kernel = run_sync(_KernelManagerBase._async_post_start_kernel) + request_shutdown = run_sync(_KernelManagerBase._async_request_shutdown) + finish_shutdown = run_sync(_KernelManagerBase._async_finish_shutdown) + cleanup_resources = run_sync(_KernelManagerBase._async_cleanup_resources) + shutdown_kernel = run_sync(_KernelManagerBase._async_shutdown_kernel) + restart_kernel = run_sync(_KernelManagerBase._async_restart_kernel) + _send_kernel_sigterm = run_sync(_KernelManagerBase._async_send_kernel_sigterm) + _kill_kernel = run_sync(_KernelManagerBase._async_kill_kernel) + interrupt_kernel = run_sync(_KernelManagerBase._async_interrupt_kernel) + signal_kernel = run_sync(_KernelManagerBase._async_signal_kernel) + is_alive = run_sync(_KernelManagerBase._async_is_alive) + + +class AsyncKernelManager(_KernelManagerBase[AsyncKernelClient]): """An async kernel manager.""" # the class to create with our `client` method @@ -727,29 +731,24 @@ def _context_default(self) -> zmq.asyncio.Context: self._created_context = True return zmq.asyncio.Context() - def client( # type:ignore[override] - self, **kwargs: t.Any - ) -> AsyncKernelClient: - """Get a client for the manager.""" - return super().client(**kwargs) # type:ignore[return-value] - - _launch_kernel = KernelManager._async_launch_kernel # type:ignore[assignment] - start_kernel: t.Callable[..., t.Awaitable] = KernelManager._async_start_kernel # type:ignore[assignment] - pre_start_kernel: t.Callable[..., t.Awaitable] = KernelManager._async_pre_start_kernel # type:ignore[assignment] - post_start_kernel: t.Callable[..., t.Awaitable] = KernelManager._async_post_start_kernel # type:ignore[assignment] - request_shutdown: t.Callable[..., t.Awaitable] = KernelManager._async_request_shutdown # type:ignore[assignment] - finish_shutdown: t.Callable[..., t.Awaitable] = KernelManager._async_finish_shutdown # type:ignore[assignment] - cleanup_resources: t.Callable[..., t.Awaitable] = KernelManager._async_cleanup_resources # type:ignore[assignment] - shutdown_kernel: t.Callable[..., t.Awaitable] = KernelManager._async_shutdown_kernel # type:ignore[assignment] - restart_kernel: t.Callable[..., t.Awaitable] = KernelManager._async_restart_kernel # type:ignore[assignment] - _send_kernel_sigterm = KernelManager._async_send_kernel_sigterm # type:ignore[assignment] - _kill_kernel = KernelManager._async_kill_kernel # type:ignore[assignment] - interrupt_kernel: t.Callable[..., t.Awaitable] = KernelManager._async_interrupt_kernel # type:ignore[assignment] - signal_kernel: t.Callable[..., t.Awaitable] = KernelManager._async_signal_kernel # type:ignore[assignment] - is_alive: t.Callable[..., t.Awaitable] = KernelManager._async_is_alive # type:ignore[assignment] + _launch_kernel = _KernelManagerBase._async_launch_kernel + start_kernel = _KernelManagerBase._async_start_kernel + pre_start_kernel = _KernelManagerBase._async_pre_start_kernel + post_start_kernel = _KernelManagerBase._async_post_start_kernel + request_shutdown = _KernelManagerBase._async_request_shutdown + finish_shutdown = _KernelManagerBase._async_finish_shutdown + cleanup_resources = _KernelManagerBase._async_cleanup_resources + shutdown_kernel = _KernelManagerBase._async_shutdown_kernel + restart_kernel = _KernelManagerBase._async_restart_kernel + _send_kernel_sigterm = _KernelManagerBase._async_send_kernel_sigterm + _kill_kernel = _KernelManagerBase._async_kill_kernel + interrupt_kernel = _KernelManagerBase._async_interrupt_kernel + signal_kernel = _KernelManagerBase._async_signal_kernel + is_alive = _KernelManagerBase._async_is_alive KernelManagerABC.register(KernelManager) +KernelManagerABC.register(AsyncKernelManager) def start_new_kernel( diff --git a/jupyter_client/multikernelmanager.py b/jupyter_client/multikernelmanager.py index d14a3f84..18841d2a 100644 --- a/jupyter_client/multikernelmanager.py +++ b/jupyter_client/multikernelmanager.py @@ -48,7 +48,7 @@ def wrapped( return wrapped -class MultiKernelManager(LoggingConfigurable): +class _MultiKernelManagerBase(LoggingConfigurable): """A class for managing multiple kernels.""" default_kernel_name = Unicode( @@ -226,7 +226,7 @@ def update_env(self, *, kernel_id: str, env: t.Dict[str, str]) -> None: self._kernels[kernel_id].update_env(env=env) async def _add_kernel_when_ready( - self, kernel_id: str, km: KernelManager, kernel_awaitable: t.Awaitable + self, kernel_id: str, km: KernelManager, kernel_awaitable: t.Awaitable[t.Any] ) -> None: try: await kernel_awaitable @@ -236,7 +236,7 @@ async def _add_kernel_when_ready( self.log.exception(e) async def _remove_kernel_when_ready( - self, kernel_id: str, kernel_awaitable: t.Awaitable + self, kernel_id: str, kernel_awaitable: t.Awaitable[t.Any] ) -> None: try: await kernel_awaitable @@ -284,8 +284,6 @@ async def _async_start_kernel(self, *, kernel_name: str | None = None, **kwargs: return kernel_id - start_kernel = run_sync(_async_start_kernel) - async def _async_shutdown_kernel( self, kernel_id: str, @@ -331,8 +329,6 @@ async def _async_shutdown_kernel( if km.ready.exception(): raise km.ready.exception() # type: ignore[misc] - shutdown_kernel = run_sync(_async_shutdown_kernel) - @kernel_method def request_shutdown(self, kernel_id: str, restart: bool | None = False) -> None: """Ask a kernel to shut down by its kernel uuid""" @@ -379,8 +375,6 @@ async def _async_shutdown_all(self, now: bool = False) -> None: # Will have been logged in _add_kernel_when_ready pass - shutdown_all = run_sync(_async_shutdown_all) - def interrupt_kernel(self, kernel_id: str) -> None: """Interrupt (SIGINT) the kernel by its uuid. @@ -435,8 +429,6 @@ async def _async_restart_kernel(self, kernel_id: str, now: bool = False) -> None await ensure_async(kernel.restart_kernel(now=now)) self.log.info("Kernel restarted: %s", kernel_id) - restart_kernel = run_sync(_async_restart_kernel) - @kernel_method def is_alive(self, kernel_id: str) -> bool: # type:ignore[empty-body] """Is the kernel alive. @@ -596,7 +588,14 @@ def new_kernel_id(self, **kwargs: t.Any) -> str: return str(uuid.uuid4()) -class AsyncMultiKernelManager(MultiKernelManager): +class MultiKernelManager(_MultiKernelManagerBase): + start_kernel = run_sync(_MultiKernelManagerBase._async_start_kernel) + restart_kernel = run_sync(_MultiKernelManagerBase._async_restart_kernel) + shutdown_kernel = run_sync(_MultiKernelManagerBase._async_shutdown_kernel) + shutdown_all = run_sync(_MultiKernelManagerBase._async_shutdown_all) + + +class AsyncMultiKernelManager(_MultiKernelManagerBase): kernel_manager_class = DottedObjectName( "jupyter_client.ioloop.AsyncIOLoopKernelManager", config=True, @@ -618,7 +617,7 @@ def _context_default(self) -> zmq.asyncio.Context: self._created_context = True return zmq.asyncio.Context() - start_kernel: t.Callable[..., t.Awaitable] = MultiKernelManager._async_start_kernel # type:ignore[assignment] - restart_kernel: t.Callable[..., t.Awaitable] = MultiKernelManager._async_restart_kernel # type:ignore[assignment] - shutdown_kernel: t.Callable[..., t.Awaitable] = MultiKernelManager._async_shutdown_kernel # type:ignore[assignment] - shutdown_all: t.Callable[..., t.Awaitable] = MultiKernelManager._async_shutdown_all # type:ignore[assignment] + start_kernel = _MultiKernelManagerBase._async_start_kernel + restart_kernel = _MultiKernelManagerBase._async_restart_kernel + shutdown_kernel = _MultiKernelManagerBase._async_shutdown_kernel + shutdown_all = _MultiKernelManagerBase._async_shutdown_all diff --git a/jupyter_client/restarter.py b/jupyter_client/restarter.py index d41890f6..b7230559 100644 --- a/jupyter_client/restarter.py +++ b/jupyter_client/restarter.py @@ -19,7 +19,7 @@ class KernelRestarter(LoggingConfigurable): """Monitor and autorestart a kernel.""" - kernel_manager = Instance("jupyter_client.KernelManager") + kernel_manager = Instance("jupyter_client.managerabc.KernelManagerABC") debug = Bool( False, diff --git a/jupyter_client/ssh/tunnel.py b/jupyter_client/ssh/tunnel.py index 3b1b533c..c95e2790 100644 --- a/jupyter_client/ssh/tunnel.py +++ b/jupyter_client/ssh/tunnel.py @@ -27,7 +27,7 @@ except ImportError: paramiko = None # type:ignore[assignment] - class SSHException(Exception): # type:ignore[no-redef] # noqa + class SSHException(Exception): # type:ignore[no-redef] # noqa: N818 pass else: diff --git a/tests/problemkernel.py b/tests/problemkernel.py index a20cf708..a55648f5 100644 --- a/tests/problemkernel.py +++ b/tests/problemkernel.py @@ -18,7 +18,7 @@ class ProblemTestKernel(Kernel): class ProblemTestApp(IPKernelApp): - kernel_class = ProblemTestKernel # type:ignore[assignment] + kernel_class = ProblemTestKernel def init_io(self): # Overridden to disable stdout/stderr capture diff --git a/tests/signalkernel.py b/tests/signalkernel.py index 65fdb687..887d4d32 100644 --- a/tests/signalkernel.py +++ b/tests/signalkernel.py @@ -62,7 +62,7 @@ def do_execute( class SignalTestApp(IPKernelApp): - kernel_class = SignalTestKernel # type:ignore[assignment] + kernel_class = SignalTestKernel def init_io(self): # Overridden to disable stdout/stderr capture