diff --git a/IPython/consoleapp.py b/IPython/consoleapp.py index bbcbce3..f04e05b 100644 --- a/IPython/consoleapp.py +++ b/IPython/consoleapp.py @@ -25,7 +25,8 @@ from IPython.utils.traitlets import ( Dict, List, Unicode, CUnicode, CBool, Any ) from IPython.kernel.zmq.session import Session -from IPython.kernel.connect import ConnectionFileMixin +from IPython.kernel import connect +ConnectionFileMixin = connect.ConnectionFileMixin from IPython.utils.localinterfaces import localhost diff --git a/IPython/kernel/__init__.py b/IPython/kernel/__init__.py index 0cce18e..52f736e 100644 --- a/IPython/kernel/__init__.py +++ b/IPython/kernel/__init__.py @@ -1,67 +1,29 @@ -"""IPython kernels and associated utilities - -For connecting to kernels, use jupyter_client """ - +Shim to maintain backwards compatibility with old IPython.kernel imports. +""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -# Shim to maintain backwards compatibility with old IPython.kernel imports. - import sys from warnings import warn warn("The `IPython.kernel` package has been deprecated. " "You should import from ipython_kernel or jupyter_client instead.") -from IPython.utils.shimmodule import ShimModule - -# Shims for jupyter_client -# Can't do a single shim, because the package didn't move all together -for name in ( - 'adapter', - 'blocking', - 'channels', - 'channelsabc', - 'client', - 'clientabc', - 'connect', - 'ioloop', - 'kernelspec', - 'kernelspecapp', - 'launcher', - 'manager', - 'managerabc', - 'multikernelmanager', - 'restarter', - 'threaded', - 'tests.test_adapter', - 'tests.test_connect', - 'tests.test_kernelmanager', - 'tests.test_kernelspec', - 'tests.test_launcher', - 'tests.test_multikernelmanager', - 'tests.test_public_api', -): - sys.modules['IPython.kernel.%s' % name] = \ - ShimModule(name, mirror='jupyter_client.%s' % name) +from IPython.utils.shimmodule import ShimModule -# some files moved out of the zmq prefix -for name in ( - 'session', - 'tests.test_session', -): - sys.modules['IPython.kernel.zmq.%s' % name] = \ - ShimModule(name, mirror='jupyter_client.%s' % name) -# preserve top-level API modules, all from jupyter_client +# session moved relative to top-level +sys.modules['IPython.kernel.zmq.session'] = ShimModule('session', mirror='jupyter_client.session') -# just for friendlier zmq version check -from . import zmq +for pkg in ('comm', 'inprocess', 'resources', 'zmq'): + sys.modules['IPython.kernel.%s' % pkg] = ShimModule(pkg, mirror='ipython_kernel.%s' % pkg) +for pkg in ('ioloop', 'blocking'): + sys.modules['IPython.kernel.%s' % pkg] = ShimModule(pkg, mirror='jupyter_client.%s' % pkg) -from jupyter_client.connect import * -from jupyter_client.launcher import * -from jupyter_client.client import KernelClient -from jupyter_client.manager import KernelManager, run_kernel -from jupyter_client.blocking import BlockingKernelClient -from jupyter_client.multikernelmanager import MultiKernelManager +# required for `from IPython.kernel import PKG` +from ipython_kernel import comm, inprocess, resources, zmq +from jupyter_client import ioloop, blocking +# public API +from ipython_kernel.connect import * +from jupyter_client import * diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index 6b20cf2..3a679d0 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -172,6 +172,8 @@ class TestSection(object): shims = { 'parallel': 'ipython_parallel', + 'kernel': 'ipython_kernel', + 'kernel.inprocess': 'ipython_kernel.inprocess', } # Name -> (include, exclude, dependencies_met) diff --git a/ipython_kernel/launcher.py b/ipython_kernel/launcher.py new file mode 100644 index 0000000..13492d3 --- /dev/null +++ b/ipython_kernel/launcher.py @@ -0,0 +1,226 @@ +"""Utilities for launching kernels +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import os +import sys +from subprocess import Popen, PIPE + +from IPython.utils.encoding import getdefaultencoding +from IPython.utils.py3compat import cast_bytes_py2 + + +def swallow_argv(argv, aliases=None, flags=None): + """strip frontend-specific aliases and flags from an argument list + + For use primarily in frontend apps that want to pass a subset of command-line + arguments through to a subprocess, where frontend-specific flags and aliases + should be removed from the list. + + Parameters + ---------- + + argv : list(str) + The starting argv, to be filtered + aliases : container of aliases (dict, list, set, etc.) + The frontend-specific aliases to be removed + flags : container of flags (dict, list, set, etc.) + The frontend-specific flags to be removed + + Returns + ------- + + argv : list(str) + The argv list, excluding flags and aliases that have been stripped + """ + + if aliases is None: + aliases = set() + if flags is None: + flags = set() + + stripped = list(argv) # copy + + swallow_next = False + was_flag = False + for a in argv: + if a == '--': + break + if swallow_next: + swallow_next = False + # last arg was an alias, remove the next one + # *unless* the last alias has a no-arg flag version, in which + # case, don't swallow the next arg if it's also a flag: + if not (was_flag and a.startswith('-')): + stripped.remove(a) + continue + if a.startswith('-'): + split = a.lstrip('-').split('=') + name = split[0] + # we use startswith because argparse accepts any arg to be specified + # by any leading section, as long as it is unique, + # so `--no-br` means `--no-browser` in the notebook, etc. + if any(alias.startswith(name) for alias in aliases): + stripped.remove(a) + if len(split) == 1: + # alias passed with arg via space + swallow_next = True + # could have been a flag that matches an alias, e.g. `existing` + # in which case, we might not swallow the next arg + was_flag = name in flags + elif len(split) == 1 and any(flag.startswith(name) for flag in flags): + # strip flag, but don't swallow next, as flags don't take args + stripped.remove(a) + + # return shortened list + return stripped + + +def make_ipkernel_cmd(mod='ipython_kernel', executable=None, extra_arguments=[], **kw): + """Build Popen command list for launching an IPython kernel. + + Parameters + ---------- + mod : str, optional (default 'ipython_kernel') + A string of an IPython module whose __main__ starts an IPython kernel + + executable : str, optional (default sys.executable) + The Python executable to use for the kernel process. + + extra_arguments : list, optional + A list of extra arguments to pass when executing the launch code. + + Returns + ------- + + A Popen command list + """ + if executable is None: + executable = sys.executable + arguments = [ executable, '-m', mod, '-f', '{connection_file}' ] + arguments.extend(extra_arguments) + + return arguments + + +def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None, + independent=False, + cwd=None, + **kw + ): + """ Launches a localhost kernel, binding to the specified ports. + + Parameters + ---------- + cmd : Popen list, + A string of Python code that imports and executes a kernel entry point. + + stdin, stdout, stderr : optional (default None) + Standards streams, as defined in subprocess.Popen. + + independent : bool, optional (default False) + If set, the kernel process is guaranteed to survive if this process + dies. If not set, an effort is made to ensure that the kernel is killed + when this process dies. Note that in this case it is still good practice + to kill kernels manually before exiting. + + cwd : path, optional + The working dir of the kernel process (default: cwd of this process). + + Returns + ------- + + Popen instance for the kernel subprocess + """ + + # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr + # are invalid. Unfortunately, there is in general no way to detect whether + # they are valid. The following two blocks redirect them to (temporary) + # pipes in certain important cases. + + # If this process has been backgrounded, our stdin is invalid. Since there + # is no compelling reason for the kernel to inherit our stdin anyway, we'll + # place this one safe and always redirect. + redirect_in = True + _stdin = PIPE if stdin is None else stdin + + # If this process in running on pythonw, we know that stdin, stdout, and + # stderr are all invalid. + redirect_out = sys.executable.endswith('pythonw.exe') + if redirect_out: + blackhole = open(os.devnull, 'w') + _stdout = blackhole if stdout is None else stdout + _stderr = blackhole if stderr is None else stderr + else: + _stdout, _stderr = stdout, stderr + + env = env if (env is not None) else os.environ.copy() + + encoding = getdefaultencoding(prefer_stream=False) + kwargs = dict( + stdin=_stdin, + stdout=_stdout, + stderr=_stderr, + cwd=cwd, + env=env, + ) + + # Spawn a kernel. + if sys.platform == 'win32': + # Popen on Python 2 on Windows cannot handle unicode args or cwd + cmd = [ cast_bytes_py2(c, encoding) for c in cmd ] + if cwd: + cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii') + kwargs['cwd'] = cwd + + from jupyter_client.parentpoller import ParentPollerWindows + # Create a Win32 event for interrupting the kernel + # and store it in an environment variable. + interrupt_event = ParentPollerWindows.create_interrupt_event() + env["JPY_INTERRUPT_EVENT"] = str(interrupt_event) + # deprecated old env name: + env["IPY_INTERRUPT_EVENT"] = env["JPY_INTERRUPT_EVENT"] + + try: + from _winapi import DuplicateHandle, GetCurrentProcess, \ + DUPLICATE_SAME_ACCESS, CREATE_NEW_PROCESS_GROUP + except: + from _subprocess import DuplicateHandle, GetCurrentProcess, \ + DUPLICATE_SAME_ACCESS, CREATE_NEW_PROCESS_GROUP + # Launch the kernel process + if independent: + kwargs['creationflags'] = CREATE_NEW_PROCESS_GROUP + else: + pid = GetCurrentProcess() + handle = DuplicateHandle(pid, pid, pid, 0, + True, # Inheritable by new processes. + DUPLICATE_SAME_ACCESS) + env['JPY_PARENT_PID'] = str(int(handle)) + + proc = Popen(cmd, **kwargs) + + # Attach the interrupt event to the Popen objet so it can be used later. + proc.win32_interrupt_event = interrupt_event + + else: + if independent: + kwargs['preexec_fn'] = lambda: os.setsid() + else: + env['JPY_PARENT_PID'] = str(os.getpid()) + + proc = Popen(cmd, **kwargs) + + # Clean up pipes created to work around Popen bug. + if redirect_in: + if stdin is None: + proc.stdin.close() + + return proc + +__all__ = [ + 'swallow_argv', + 'make_ipkernel_cmd', + 'launch_kernel', +] diff --git a/jupyter_client/launcher.py b/jupyter_client/launcher.py index e3c7cff..13492d3 100644 --- a/jupyter_client/launcher.py +++ b/jupyter_client/launcher.py @@ -78,12 +78,12 @@ def swallow_argv(argv, aliases=None, flags=None): return stripped -def make_ipkernel_cmd(mod='IPython.kernel', executable=None, extra_arguments=[], **kw): +def make_ipkernel_cmd(mod='ipython_kernel', executable=None, extra_arguments=[], **kw): """Build Popen command list for launching an IPython kernel. Parameters ---------- - mod : str, optional (default 'IPython.kernel') + mod : str, optional (default 'ipython_kernel') A string of an IPython module whose __main__ starts an IPython kernel executable : str, optional (default sys.executable)