| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774 |
- """An Application for launching a kernel"""
- # Copyright (c) IPython Development Team.
- # Distributed under the terms of the Modified BSD License.
- from __future__ import annotations
- import atexit
- import errno
- import logging
- import os
- import signal
- import sys
- import traceback
- import typing as t
- from functools import partial
- from io import FileIO, TextIOWrapper
- from logging import StreamHandler
- from pathlib import Path
- import zmq
- from IPython.core.application import ( # type:ignore[attr-defined]
- BaseIPythonApplication,
- base_aliases,
- base_flags,
- catch_config_error,
- )
- from IPython.core.profiledir import ProfileDir
- from IPython.core.shellapp import InteractiveShellApp, shell_aliases, shell_flags
- from jupyter_client.connect import ConnectionFileMixin
- from jupyter_client.session import Session, session_aliases, session_flags
- from jupyter_core.paths import jupyter_runtime_dir
- from tornado import ioloop
- from traitlets.traitlets import (
- Any,
- Bool,
- Dict,
- DottedObjectName,
- Instance,
- Integer,
- Type,
- Unicode,
- default,
- )
- from traitlets.utils import filefind
- from traitlets.utils.importstring import import_item
- from zmq.eventloop.zmqstream import ZMQStream
- from .connect import get_connection_info, write_connection_file
- # local imports
- from .control import ControlThread
- from .heartbeat import Heartbeat
- from .iostream import IOPubThread
- from .ipkernel import IPythonKernel
- from .parentpoller import ParentPollerUnix, ParentPollerWindows
- from .shellchannel import ShellChannelThread
- from .zmqshell import ZMQInteractiveShell
- # -----------------------------------------------------------------------------
- # Flags and Aliases
- # -----------------------------------------------------------------------------
- kernel_aliases = dict(base_aliases)
- kernel_aliases.update(
- {
- "ip": "IPKernelApp.ip",
- "hb": "IPKernelApp.hb_port",
- "shell": "IPKernelApp.shell_port",
- "iopub": "IPKernelApp.iopub_port",
- "stdin": "IPKernelApp.stdin_port",
- "control": "IPKernelApp.control_port",
- "f": "IPKernelApp.connection_file",
- "transport": "IPKernelApp.transport",
- }
- )
- kernel_flags = dict(base_flags)
- kernel_flags.update(
- {
- "no-stdout": ({"IPKernelApp": {"no_stdout": True}}, "redirect stdout to the null device"),
- "no-stderr": ({"IPKernelApp": {"no_stderr": True}}, "redirect stderr to the null device"),
- "pylab": (
- {"IPKernelApp": {"pylab": "auto"}},
- """Pre-load matplotlib and numpy for interactive use with
- the default matplotlib backend.""",
- ),
- "trio-loop": (
- {"InteractiveShell": {"trio_loop": False}},
- "Enable Trio as main event loop.",
- ),
- }
- )
- # inherit flags&aliases for any IPython shell apps
- kernel_aliases.update(shell_aliases)
- kernel_flags.update(shell_flags)
- # inherit flags&aliases for Sessions
- kernel_aliases.update(session_aliases)
- kernel_flags.update(session_flags)
- _ctrl_c_message = """\
- NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
- To exit, you will have to explicitly quit this process, by either sending
- "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
- To read more about this, see https://github.com/ipython/ipython/issues/2049
- """
- # -----------------------------------------------------------------------------
- # Application class for starting an IPython Kernel
- # -----------------------------------------------------------------------------
- class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMixin):
- """The IPYKernel application class."""
- name = "ipython-kernel"
- aliases = Dict(kernel_aliases)
- flags = Dict(kernel_flags)
- classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session] # type:ignore[assignment]
- # the kernel class, as an importstring
- kernel_class = Type(
- "ipykernel.ipkernel.IPythonKernel",
- klass="ipykernel.kernelbase.Kernel",
- help="""The Kernel subclass to be used.
- This should allow easy reuse of the IPKernelApp entry point
- to configure and launch kernels other than IPython's own.
- """,
- ).tag(config=True)
- kernel = Any()
- poller = Any() # don't restrict this even though current pollers are all Threads
- heartbeat = Instance(Heartbeat, allow_none=True)
- context: zmq.Context[t.Any] | None = Any() # type:ignore[assignment]
- shell_socket = Any()
- control_socket = Any()
- debugpy_socket = Any()
- debug_shell_socket = Any()
- stdin_socket = Any()
- iopub_socket = Any()
- iopub_thread = Any()
- control_thread = Any()
- shell_channel_thread = Any()
- _ports = Dict()
- subcommands = {
- "install": (
- "ipykernel.kernelspec.InstallIPythonKernelSpecApp",
- "Install the IPython kernel",
- ),
- }
- # connection info:
- connection_dir = Unicode()
- @default("connection_dir")
- def _default_connection_dir(self):
- return jupyter_runtime_dir()
- @property
- def abs_connection_file(self):
- if Path(self.connection_file).name == self.connection_file and self.connection_dir:
- return str(Path(str(self.connection_dir)) / self.connection_file)
- return self.connection_file
- # streams, etc.
- no_stdout = Bool(False, help="redirect stdout to the null device").tag(config=True)
- no_stderr = Bool(False, help="redirect stderr to the null device").tag(config=True)
- trio_loop = Bool(False, help="Set main event loop.").tag(config=True)
- quiet = Bool(True, help="Only send stdout/stderr to output stream").tag(config=True)
- outstream_class = DottedObjectName(
- "ipykernel.iostream.OutStream",
- help="The importstring for the OutStream factory",
- allow_none=True,
- ).tag(config=True)
- displayhook_class = DottedObjectName(
- "ipykernel.displayhook.ZMQDisplayHook", help="The importstring for the DisplayHook factory"
- ).tag(config=True)
- capture_fd_output = Bool(
- True,
- help="""Attempt to capture and forward low-level output, e.g. produced by Extension libraries.
- """,
- ).tag(config=True)
- # polling
- parent_handle = Integer(
- int(os.environ.get("JPY_PARENT_PID") or 0),
- help="""kill this process if its parent dies. On Windows, the argument
- specifies the HANDLE of the parent process, otherwise it is simply boolean.
- """,
- ).tag(config=True)
- interrupt = Integer(
- int(os.environ.get("JPY_INTERRUPT_EVENT") or 0),
- help="""ONLY USED ON WINDOWS
- Interrupt this process when the parent is signaled.
- """,
- ).tag(config=True)
- def init_crash_handler(self):
- """Initialize the crash handler."""
- sys.excepthook = self.excepthook
- def excepthook(self, etype, evalue, tb):
- """Handle an exception."""
- # write uncaught traceback to 'real' stderr, not zmq-forwarder
- traceback.print_exception(etype, evalue, tb, file=sys.__stderr__)
- def init_poller(self):
- """Initialize the poller."""
- if sys.platform == "win32":
- if self.interrupt or self.parent_handle:
- self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
- elif self.parent_handle and self.parent_handle != 1:
- # PID 1 (init) is special and will never go away,
- # only be reassigned.
- # Parent polling doesn't work if ppid == 1 to start with.
- self.poller = ParentPollerUnix(parent_pid=self.parent_handle)
- def _try_bind_socket(self, s, port):
- iface = f"{self.transport}://{self.ip}"
- if self.transport == "tcp":
- if port <= 0:
- port = s.bind_to_random_port(iface)
- else:
- s.bind("tcp://%s:%i" % (self.ip, port))
- elif self.transport == "ipc":
- if port <= 0:
- port = 1
- path = "%s-%i" % (self.ip, port)
- while Path(path).exists():
- port = port + 1
- path = "%s-%i" % (self.ip, port)
- else:
- path = "%s-%i" % (self.ip, port)
- s.bind("ipc://%s" % path)
- return port
- def _bind_socket(self, s, port):
- try:
- win_in_use = errno.WSAEADDRINUSE # type:ignore[attr-defined]
- except AttributeError:
- win_in_use = None
- # Try up to 100 times to bind a port when in conflict to avoid
- # infinite attempts in bad setups
- max_attempts = 1 if port else 100
- for attempt in range(max_attempts):
- try:
- return self._try_bind_socket(s, port)
- except zmq.ZMQError as ze:
- # Raise if we have any error not related to socket binding
- if ze.errno != errno.EADDRINUSE and ze.errno != win_in_use:
- raise
- if attempt == max_attempts - 1:
- raise
- return None
- def write_connection_file(self, **kwargs: Any) -> None:
- """write connection info to JSON file"""
- cf = self.abs_connection_file
- connection_info = dict(
- ip=self.ip,
- key=self.session.key,
- transport=self.transport,
- shell_port=self.shell_port,
- stdin_port=self.stdin_port,
- hb_port=self.hb_port,
- iopub_port=self.iopub_port,
- control_port=self.control_port,
- )
- if Path(cf).exists():
- # If the file exists, merge our info into it. For example, if the
- # original file had port number 0, we update with the actual port
- # used.
- existing_connection_info = get_connection_info(cf, unpack=True)
- assert isinstance(existing_connection_info, dict)
- connection_info = dict(existing_connection_info, **connection_info)
- if connection_info == existing_connection_info:
- self.log.debug("Connection file %s with current information already exists", cf)
- return
- self.log.debug("Writing connection file: %s", cf)
- write_connection_file(cf, **connection_info)
- def cleanup_connection_file(self):
- """Clean up our connection file."""
- cf = self.abs_connection_file
- self.log.debug("Cleaning up connection file: %s", cf)
- try:
- Path(cf).unlink()
- except OSError:
- pass
- self.cleanup_ipc_files()
- def init_connection_file(self):
- """Initialize our connection file."""
- if not self.connection_file:
- self.connection_file = "kernel-%s.json" % os.getpid()
- try:
- self.connection_file = filefind(self.connection_file, [".", self.connection_dir])
- except OSError:
- self.log.debug("Connection file not found: %s", self.connection_file)
- # This means I own it, and I'll create it in this directory:
- Path(self.abs_connection_file).parent.mkdir(mode=0o700, exist_ok=True, parents=True)
- # Also, I will clean it up:
- atexit.register(self.cleanup_connection_file)
- return
- try:
- self.load_connection_file()
- except Exception:
- self.log.error( # noqa: G201
- "Failed to load connection file: %r", self.connection_file, exc_info=True
- )
- self.exit(1)
- def init_sockets(self):
- """Create a context, a session, and the kernel sockets."""
- self.log.info("Starting the kernel at pid: %i", os.getpid())
- assert self.context is None, "init_sockets cannot be called twice!"
- self.context = context = zmq.Context()
- atexit.register(self.close)
- self.shell_socket = context.socket(zmq.ROUTER)
- self.shell_socket.linger = 1000
- self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
- self.log.debug("shell ROUTER Channel on port: %i", self.shell_port)
- self.stdin_socket = context.socket(zmq.ROUTER)
- self.stdin_socket.linger = 1000
- self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
- self.log.debug("stdin ROUTER Channel on port: %i", self.stdin_port)
- if hasattr(zmq, "ROUTER_HANDOVER"):
- # set router-handover to workaround zeromq reconnect problems
- # in certain rare circumstances
- # see ipython/ipykernel#270 and zeromq/libzmq#2892
- self.shell_socket.router_handover = self.stdin_socket.router_handover = 1
- self.init_control(context)
- self.init_iopub(context)
- def init_control(self, context):
- """Initialize the control channel."""
- self.control_socket = context.socket(zmq.ROUTER)
- self.control_socket.linger = 1000
- self.control_port = self._bind_socket(self.control_socket, self.control_port)
- self.log.debug("control ROUTER Channel on port: %i", self.control_port)
- self.debugpy_socket = context.socket(zmq.STREAM)
- self.debugpy_socket.linger = 1000
- self.debug_shell_socket = context.socket(zmq.DEALER)
- self.debug_shell_socket.linger = 1000
- if self.shell_socket.getsockopt(zmq.LAST_ENDPOINT):
- self.debug_shell_socket.connect(self.shell_socket.getsockopt(zmq.LAST_ENDPOINT))
- if hasattr(zmq, "ROUTER_HANDOVER"):
- # set router-handover to workaround zeromq reconnect problems
- # in certain rare circumstances
- # see ipython/ipykernel#270 and zeromq/libzmq#2892
- self.control_socket.router_handover = 1
- self.control_thread = ControlThread(daemon=True)
- self.shell_channel_thread = ShellChannelThread(
- context,
- self.shell_socket,
- daemon=True,
- )
- def init_iopub(self, context):
- """Initialize the iopub channel."""
- self.iopub_socket = context.socket(zmq.XPUB)
- self.iopub_socket.linger = 1000
- self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
- self.log.debug("iopub PUB Channel on port: %i", self.iopub_port)
- self.configure_tornado_logger()
- self.iopub_thread = IOPubThread(self.iopub_socket, pipe=True, session=self.session)
- self.iopub_thread.start()
- # backward-compat: wrap iopub socket API in background thread
- self.iopub_socket = self.iopub_thread.background_socket
- def init_heartbeat(self):
- """start the heart beating"""
- # heartbeat doesn't share context, because it mustn't be blocked
- # by the GIL, which is accessed by libzmq when freeing zero-copy messages
- hb_ctx = zmq.Context()
- self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
- self.hb_port = self.heartbeat.port
- self.log.debug("Heartbeat REP Channel on port: %i", self.hb_port)
- self.heartbeat.start()
- def close(self):
- """Close zmq sockets in an orderly fashion"""
- # un-capture IO before we start closing channels
- self.reset_io()
- self.log.info("Cleaning up sockets")
- if self.heartbeat:
- self.log.debug("Closing heartbeat channel")
- self.heartbeat.context.term()
- if self.iopub_thread:
- self.log.debug("Closing iopub channel")
- self.iopub_thread.stop()
- self.iopub_thread.close()
- if self.control_thread and self.control_thread.is_alive():
- self.log.debug("Closing control thread")
- self.control_thread.stop()
- self.control_thread.join()
- if self.shell_channel_thread and self.shell_channel_thread.is_alive():
- self.log.debug("Closing shell channel thread")
- self.shell_channel_thread.stop()
- self.shell_channel_thread.join()
- if self.debugpy_socket and not self.debugpy_socket.closed:
- self.debugpy_socket.close()
- if self.debug_shell_socket and not self.debug_shell_socket.closed:
- self.debug_shell_socket.close()
- for channel in ("shell", "control", "stdin"):
- self.log.debug("Closing %s channel", channel)
- socket = getattr(self, channel + "_socket", None)
- if socket and not socket.closed:
- socket.close()
- self.log.debug("Terminating zmq context")
- if self.context:
- self.context.term()
- self.log.debug("Terminated zmq context")
- def log_connection_info(self):
- """display connection info, and store ports"""
- basename = Path(self.connection_file).name
- if (
- basename == self.connection_file
- or str(Path(self.connection_file).parent) == self.connection_dir
- ):
- # use shortname
- tail = basename
- else:
- tail = self.connection_file
- lines = [
- "To connect another client to this kernel, use:",
- " --existing %s" % tail,
- ]
- # log connection info
- # info-level, so often not shown.
- # frontends should use the %connect_info magic
- # to see the connection info
- for line in lines:
- self.log.info(line)
- # also raw print to the terminal if no parent_handle (`ipython kernel`)
- # unless log-level is CRITICAL (--quiet)
- if not self.parent_handle and int(self.log_level) < logging.CRITICAL: # type:ignore[call-overload]
- print(_ctrl_c_message, file=sys.__stdout__)
- for line in lines:
- print(line, file=sys.__stdout__)
- self._ports = dict(
- shell=self.shell_port,
- iopub=self.iopub_port,
- stdin=self.stdin_port,
- hb=self.hb_port,
- control=self.control_port,
- )
- def init_blackhole(self):
- """redirects stdout/stderr to devnull if necessary"""
- if self.no_stdout or self.no_stderr:
- blackhole = open(os.devnull, "w") # noqa: SIM115
- if self.no_stdout:
- sys.stdout = sys.__stdout__ = blackhole # type:ignore[misc]
- if self.no_stderr:
- sys.stderr = sys.__stderr__ = blackhole # type:ignore[misc]
- def init_io(self):
- """Redirect input streams and set a display hook."""
- if self.outstream_class:
- outstream_factory = import_item(str(self.outstream_class))
- if sys.stdout is not None:
- sys.stdout.flush()
- e_stdout = None if self.quiet else sys.__stdout__
- e_stderr = None if self.quiet else sys.__stderr__
- if not self.capture_fd_output:
- outstream_factory = partial(outstream_factory, watchfd=False)
- sys.stdout = outstream_factory(self.session, self.iopub_thread, "stdout", echo=e_stdout)
- if sys.stderr is not None:
- sys.stderr.flush()
- sys.stderr = outstream_factory(self.session, self.iopub_thread, "stderr", echo=e_stderr)
- if hasattr(sys.stderr, "_original_stdstream_copy"):
- for handler in self.log.handlers:
- if isinstance(handler, StreamHandler) and (handler.stream.buffer.fileno() == 2):
- self.log.debug("Seeing logger to stderr, rerouting to raw filedescriptor.")
- handler.stream = TextIOWrapper(
- FileIO(
- sys.stderr._original_stdstream_copy,
- "w",
- )
- )
- if self.displayhook_class:
- displayhook_factory = import_item(str(self.displayhook_class))
- self.displayhook = displayhook_factory(self.session, self.iopub_socket)
- sys.displayhook = self.displayhook
- self.patch_io()
- def reset_io(self):
- """restore original io
- restores state after init_io
- """
- sys.stdout = sys.__stdout__
- sys.stderr = sys.__stderr__
- sys.displayhook = sys.__displayhook__
- def patch_io(self):
- """Patch important libraries that can't handle sys.stdout forwarding"""
- try:
- import faulthandler
- except ImportError:
- pass
- else:
- # Warning: this is a monkeypatch of `faulthandler.enable`, watch for possible
- # updates to the upstream API and update accordingly (up-to-date as of Python 3.5):
- # https://docs.python.org/3/library/faulthandler.html#faulthandler.enable
- # change default file to __stderr__ from forwarded stderr
- faulthandler_enable = faulthandler.enable
- def enable(file=sys.__stderr__, all_threads=True, **kwargs):
- return faulthandler_enable(file=file, all_threads=all_threads, **kwargs)
- faulthandler.enable = enable
- if hasattr(faulthandler, "register"):
- faulthandler_register = faulthandler.register
- def register(signum, file=sys.__stderr__, all_threads=True, chain=False, **kwargs):
- return faulthandler_register(
- signum, file=file, all_threads=all_threads, chain=chain, **kwargs
- )
- faulthandler.register = register
- def init_signal(self):
- """Initialize the signal handler."""
- signal.signal(signal.SIGINT, signal.SIG_IGN)
- def init_kernel(self):
- """Create the Kernel object itself"""
- if self.shell_channel_thread:
- shell_stream = ZMQStream(self.shell_socket, self.shell_channel_thread.io_loop)
- else:
- shell_stream = ZMQStream(self.shell_socket)
- control_stream = ZMQStream(self.control_socket, self.control_thread.io_loop)
- debugpy_stream = ZMQStream(self.debugpy_socket, self.control_thread.io_loop)
- self.control_thread.start()
- if self.shell_channel_thread:
- self.shell_channel_thread.start()
- kernel_factory = self.kernel_class.instance # type:ignore[attr-defined]
- kernel = kernel_factory(
- parent=self,
- session=self.session,
- control_stream=control_stream,
- debugpy_stream=debugpy_stream,
- debug_shell_socket=self.debug_shell_socket,
- shell_stream=shell_stream,
- control_thread=self.control_thread,
- shell_channel_thread=self.shell_channel_thread,
- iopub_thread=self.iopub_thread,
- iopub_socket=self.iopub_socket,
- stdin_socket=self.stdin_socket,
- log=self.log,
- profile_dir=self.profile_dir,
- user_ns=self.user_ns,
- )
- kernel.record_ports({name + "_port": port for name, port in self._ports.items()})
- self.kernel = kernel
- # Allow the displayhook to get the execution count
- self.displayhook.get_execution_count = lambda: kernel.execution_count
- def init_gui_pylab(self):
- """Enable GUI event loop integration, taking pylab into account."""
- # Register inline backend as default
- # this is higher priority than matplotlibrc,
- # but lower priority than anything else (mpl.use() for instance).
- # This only affects matplotlib >= 1.5
- if not os.environ.get("MPLBACKEND"):
- os.environ["MPLBACKEND"] = "module://matplotlib_inline.backend_inline"
- # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
- # to ensure that any exception is printed straight to stderr.
- # Normally _showtraceback associates the reply with an execution,
- # which means frontends will never draw it, as this exception
- # is not associated with any execute request.
- shell = self.shell
- assert shell is not None
- _showtraceback = shell._showtraceback
- try:
- # replace error-sending traceback with stderr
- def print_tb(etype, evalue, stb):
- print("GUI event loop or pylab initialization failed", file=sys.stderr)
- assert shell is not None
- print(shell.InteractiveTB.stb2text(stb), file=sys.stderr)
- shell._showtraceback = print_tb
- InteractiveShellApp.init_gui_pylab(self)
- finally:
- shell._showtraceback = _showtraceback
- def init_shell(self):
- """Initialize the shell channel."""
- self.shell = getattr(self.kernel, "shell", None)
- if self.shell:
- self.shell.configurables.append(self)
- def configure_tornado_logger(self):
- """Configure the tornado logging.Logger.
- Must set up the tornado logger or else tornado will call
- basicConfig for the root logger which makes the root logger
- go to the real sys.stderr instead of the capture streams.
- This function mimics the setup of logging.basicConfig.
- """
- logger = logging.getLogger("tornado")
- handler = logging.StreamHandler()
- formatter = logging.Formatter(logging.BASIC_FORMAT)
- handler.setFormatter(formatter)
- logger.addHandler(handler)
- def _init_asyncio_patch(self):
- """set default asyncio policy to be compatible with tornado
- Tornado 6 (at least) is not compatible with the default
- asyncio implementation on Windows
- Pick the older SelectorEventLoopPolicy on Windows
- if the known-incompatible default policy is in use.
- Support for Proactor via a background thread is available in tornado 6.1,
- but it is still preferable to run the Selector in the main thread
- instead of the background.
- do this as early as possible to make it a low priority and overridable
- ref: https://github.com/tornadoweb/tornado/issues/2608
- FIXME: if/when tornado supports the defaults in asyncio without threads,
- remove and bump tornado requirement for py38.
- Most likely, this will mean a new Python version
- where asyncio.ProactorEventLoop supports add_reader and friends.
- """
- if sys.platform.startswith("win"):
- import asyncio
- try:
- from asyncio import WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy
- except ImportError:
- pass
- # not affected
- else:
- if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy:
- # WindowsProactorEventLoopPolicy is not compatible with tornado 6
- # fallback to the pre-3.8 default of Selector
- asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
- def init_pdb(self):
- """Replace pdb with IPython's version that is interruptible.
- With the non-interruptible version, stopping pdb() locks up the kernel in a
- non-recoverable state.
- """
- import pdb
- from IPython.core import debugger
- if hasattr(debugger, "InterruptiblePdb"):
- # Only available in newer IPython releases:
- debugger.Pdb = debugger.InterruptiblePdb # type:ignore[misc]
- pdb.Pdb = debugger.Pdb # type:ignore[assignment,misc]
- pdb.set_trace = debugger.set_trace
- @catch_config_error
- def initialize(self, argv=None):
- """Initialize the application."""
- self._init_asyncio_patch()
- super().initialize(argv)
- if self.subapp is not None:
- return
- self.init_pdb()
- self.init_blackhole()
- self.init_connection_file()
- self.init_poller()
- self.init_sockets()
- self.init_heartbeat()
- # writing/displaying connection info must be *after* init_sockets/heartbeat
- self.write_connection_file()
- # Log connection info after writing connection file, so that the connection
- # file is definitely available at the time someone reads the log.
- self.log_connection_info()
- self.init_io()
- try:
- self.init_signal()
- except Exception:
- # Catch exception when initializing signal fails, eg when running the
- # kernel on a separate thread
- if int(self.log_level) < logging.CRITICAL: # type:ignore[call-overload]
- self.log.error("Unable to initialize signal:", exc_info=True) # noqa: G201
- self.init_kernel()
- # shell init steps
- self.init_path()
- self.init_shell()
- if self.shell:
- self.init_gui_pylab()
- self.init_extensions()
- self.init_code()
- # flush stdout/stderr, so that anything written to these streams during
- # initialization do not get associated with the first execution request
- sys.stdout.flush()
- sys.stderr.flush()
- def start(self):
- """Start the application."""
- if self.subapp is not None:
- return self.subapp.start()
- if self.poller is not None:
- self.poller.start()
- self.kernel.start()
- self.io_loop = ioloop.IOLoop.current()
- if self.trio_loop:
- from ipykernel.trio_runner import TrioRunner
- tr = TrioRunner()
- tr.initialize(self.kernel, self.io_loop)
- try:
- tr.run()
- except KeyboardInterrupt:
- pass
- else:
- try:
- self.io_loop.start()
- except KeyboardInterrupt:
- pass
- launch_new_instance = IPKernelApp.launch_instance
- def main(): # pragma: no cover
- """Run an IPKernel as an application"""
- app = IPKernelApp.instance()
- app.initialize()
- app.start()
- if __name__ == "__main__":
- main()
|