[Python-modules-commits] [jupyter-client] 02/10: Import jupyter-client_5.2.0.orig.tar.gz

Gordon Ball chronitis-guest at moszumanska.debian.org
Mon Dec 18 16:36:11 UTC 2017


This is an automated email from the git hooks/post-receive script.

chronitis-guest pushed a commit to branch master
in repository jupyter-client.

commit f13881542dc2997f49c02b2834a1de2fb06b79aa
Author: Gordon Ball <gordon at chronitis.net>
Date:   Mon Dec 18 16:14:29 2017 +0000

    Import jupyter-client_5.2.0.orig.tar.gz
---
 .gitignore                              |  2 +
 CONTRIBUTING.md                         |  2 +
 README.md                               | 34 ++++++++++++++
 docs/api/kernelspec.rst                 | 11 +++++
 docs/changelog.rst                      | 42 +++++++++++++++++
 docs/conf.py                            |  5 +-
 docs/environment.yml                    |  2 +
 docs/kernels.rst                        | 12 ++++-
 docs/messaging.rst                      | 29 ++++++++++--
 jupyter_client/_version.py              |  4 +-
 jupyter_client/connect.py               | 57 ++++++++++++++++++++---
 jupyter_client/ioloop/manager.py        | 37 ++++++++-------
 jupyter_client/ioloop/restarter.py      | 27 ++++-------
 jupyter_client/kernelapp.py             | 81 +++++++++++++++++++++++++++++++++
 jupyter_client/kernelspec.py            | 68 +++++++++++++++++++++------
 jupyter_client/manager.py               | 56 +++++++++++++++--------
 jupyter_client/restarter.py             | 15 ++++--
 jupyter_client/session.py               | 17 +++++--
 jupyter_client/tests/test_connect.py    | 27 +++++++++++
 jupyter_client/tests/test_kernelapp.py  | 67 +++++++++++++++++++++++++++
 jupyter_client/tests/test_kernelspec.py |  1 +
 jupyter_client/tests/test_session.py    | 21 ++++++++-
 jupyter_client/threaded.py              |  2 +-
 scripts/jupyter-kernel                  |  5 ++
 scripts/jupyter-kernelspec              |  8 ----
 setup.py                                | 60 +++++++++++++-----------
 26 files changed, 562 insertions(+), 130 deletions(-)

diff --git a/.gitignore b/.gitignore
index 69cad1f..8e3f39a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,5 @@ __pycache__
 \#*#
 .#*
 .coverage
+.cache
+absolute.json
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6b73dbf..f6799bb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,5 @@
 # Contributing
 
 We follow the [IPython Contributing Guide](https://github.com/ipython/ipython/blob/master/CONTRIBUTING.md).
+
+See the [README](https://github.com/jupyter/jupyter_client/blob/master/README.md) on how to set up a development environment.
diff --git a/README.md b/README.md
index d15eda9..7d9612b 100644
--- a/README.md
+++ b/README.md
@@ -10,3 +10,37 @@ It also provides the `jupyter kernelspec` entrypoint
 for installing kernelspecs for use with Jupyter frontends.
 
 [Jupyter protocol]: https://jupyter-client.readthedocs.io/en/latest/messaging.html
+
+
+# Development Setup
+
+The [Jupyter Contributor Guides](http://jupyter.readthedocs.io/en/latest/contributor/content-contributor.html) provide extensive information on contributing code or documentation to Jupyter projects. The limited instructions below for setting up a development environment are for your convenience.
+
+## Coding
+
+You'll need Python and `pip` on the search path. Clone the Jupyter Client git repository to your computer, for example in `/my/project/jupyter_client`.
+Now create an [editable install](https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs)
+and download the dependencies of code and test suite by executing:
+
+    cd /my/projects/jupyter_client/
+    pip install -e .[test]
+    py.test
+
+The last command runs the test suite to verify the setup. During development, you can pass filenames to `py.test`, and it will execute only those tests.
+
+## Documentation
+
+The documentation of Jupyter Client is generated from the files in `docs/` using Sphinx. Instructions for setting up Sphinx with a selection of optional modules are in the [Documentation Guide](http://jupyter.readthedocs.io/en/latest/contrib_docs/index.html). You'll also need the `make` command.
+For a minimal Sphinx installation to process the Jupyter Client docs, execute:
+
+    pip install ipykernel sphinx sphinx_rtd_theme
+
+The following commands build the documentation in HTML format and check for broken links:
+
+    cd /my/projects/jupyter_client/docs/
+    make html linkcheck
+
+Point your browser to the following URL to access the generated documentation:
+
+_file:///my/projects/jupyter\_client/docs/\_build/html/index.html_
+
diff --git a/docs/api/kernelspec.rst b/docs/api/kernelspec.rst
index c2164ea..6bccc08 100644
--- a/docs/api/kernelspec.rst
+++ b/docs/api/kernelspec.rst
@@ -25,6 +25,17 @@ kernelspec - discovering kernels
       The name of the language the kernel implements, to help with picking
       appropriate kernels when loading notebooks.
 
+   .. attribute:: metadata
+
+      Additional kernel-specific metadata; clients can use this as needed,
+      for instance to aid in kernel selection and filtering.
+
+      Metadata added here should be namespaced for the tool reading and
+      writing that metadata. Concretely, if you're adding a new field called
+      :code:`supported_versions` which your tool recognizes, then you should
+      add it as :code:`"mytool": {"supported_versions": [1, 2]}`, **not** as a
+      top-level field called :code:`supported_versions`.
+
    .. attribute:: resource_dir
 
       The path to the directory with this kernel's resources, such as icons.
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 35e21b5..23cc0c9 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,6 +4,48 @@
 Changes in Jupyter Client
 =========================
 
+5.2
+===
+
+`5.2 on GitHub <https://github.com/jupyter/jupyter_client/milestones/5.2>`__
+
+- Define Jupyter protocol version 5.3:
+
+  - Kernels can now opt to be interrupted by a message sent on the control channel
+    instead of a system signal. See :ref:`kernelspecs` and :ref:`msging_interrupt`
+    (:ghpull:`294`).
+
+- New ``jupyter kernel`` command to launch an installed kernel by name
+  (:ghpull:`240`).
+- Kernelspecs where the command starts with e.g. ``python3`` or
+  ``python3.6``—matching the version ``jupyter_client`` is running on—are now
+  launched with the same Python executable as the launching process (:ghpull:`306`).
+  This extends the special handling of ``python`` added in 5.0.
+- Command line arguments specified by a kernelspec can now include
+  ``{resource_dir}``, which will be substituted with the kernelspec resource
+  directory path when the kernel is launched (:ghpull:`289`).
+- Kernelspecs now have an optional ``metadata`` field to hold arbitrary metadata
+  about kernels—see :ref:`kernelspecs` (:ghpull:`274`).
+- Make the ``KernelRestarter`` class used by a ``KernelManager`` configurable
+  (:ghpull:`290`).
+- When killing a kernel on Unix, kill its process group (:ghpull:`314`).
+- If a kernel dies soon after starting, reassign random ports before restarting
+  it, in case one of the previously chosen ports has been bound by another
+  process (:ghpull:`279`).
+- Avoid unnecessary filesystem operations when finding a kernelspec with
+  :meth:`.KernelSpecManager.get_kernel_spec` (:ghpull:`311`).
+- :meth:`.KernelSpecManager.get_all_specs` will no longer raise an exception on
+  encountering an invalid ``kernel.json`` file. It will raise a warning and
+  continue (:ghpull:`310`).
+- Check for non-contiguous buffers before trying to send them through ZMQ
+  (:ghpull:`258`).
+- Compatibility with upcoming Tornado version 5.0 (:ghpull:`304`).
+- Simplify setup code by always using setuptools (:ghpull:`284`).
+- Soften warnings when setting the sticky bit on runtime files fails
+  (:ghpull:`286`).
+- Various corrections and improvements to documentation.
+
+
 5.1
 ===
 
diff --git a/docs/conf.py b/docs/conf.py
index ea850c5..c3de08e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -34,6 +34,7 @@ extensions = [
     'sphinx.ext.autodoc',
     'sphinx.ext.intersphinx',
     'sphinx.ext.napoleon',
+    'sphinxcontrib_github_alt',
 ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -55,6 +56,8 @@ project = 'jupyter_client'
 copyright = '2015, Jupyter Development Team'
 author = 'Jupyter Development Team'
 
+github_project_url = "https://github.com/jupyter/jupyter_client"
+
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
@@ -296,7 +299,7 @@ texinfo_documents = [
 
 
 # Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'ipython': ('http://ipython.org/ipython-doc/dev/', None)}
+intersphinx_mapping = {'ipython': ('http://ipython.readthedocs.io/en/stable/', None)}
 
 # Read The Docs
 # on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org
diff --git a/docs/environment.yml b/docs/environment.yml
index 3690c73..459e7ab 100644
--- a/docs/environment.yml
+++ b/docs/environment.yml
@@ -8,3 +8,5 @@ dependencies:
 - jupyter_core
 - sphinx>=1.3.6
 - sphinx_rtd_theme
+- pip:
+  - sphinxcontrib_github_alt
diff --git a/docs/kernels.rst b/docs/kernels.rst
index 42101c3..5308c60 100644
--- a/docs/kernels.rst
+++ b/docs/kernels.rst
@@ -6,7 +6,7 @@ Making kernels for Jupyter
 
 A 'kernel' is a program that runs and introspects the user's code. IPython
 includes a kernel for Python code, and people have written kernels for
-`several other languages <https://github.com/ipython/ipython/wiki/IPython-kernels-for-other-languages>`_.
+`several other languages <https://github.com/jupyter/jupyter/wiki/Jupyter-kernels>`_.
 
 When Jupyter starts a kernel, it passes it a connection file. This specifies
 how to set up communications with the frontend.
@@ -132,9 +132,19 @@ JSON serialised dictionary containing the following keys and values:
   is found, a kernel with a matching `language` will be used.
   This allows a notebook written on any Python or Julia kernel to be properly associated
   with the user's Python or Julia kernel, even if they aren't listed under the same name as the author's.
+- **interrupt_mode** (optional): May be either ``signal`` or ``message`` and
+  specifies how a client is supposed to interrupt cell execution on this kernel,
+  either by sending an interrupt ``signal`` via the operating system's
+  signalling facilities (e.g. `SIGINT` on POSIX systems), or by sending an
+  ``interrupt_request`` message on the control channel (see
+  :ref:`msging_interrupt`). If this is not specified
+  the client will default to ``signal`` mode.
 - **env** (optional): A dictionary of environment variables to set for the kernel.
   These will be added to the current environment variables before the kernel is
   started.
+- **metadata** (optional): A dictionary of additional attributes about this
+  kernel; used by clients to aid clients in kernel selection. Metadata added
+  here should be namespaced for the tool reading and writing that metadata.
 
 For example, the kernel.json file for IPython looks like this::
 
diff --git a/docs/messaging.rst b/docs/messaging.rst
index 642b6b0..7c533a7 100644
--- a/docs/messaging.rst
+++ b/docs/messaging.rst
@@ -21,7 +21,7 @@ Versioning
 
 The Jupyter message specification is versioned independently of the packages
 that use it.
-The current version of the specification is 5.2.
+The current version of the specification is 5.3.
 
 .. note::
    *New in* and *Changed in* messages in this document refer to versions of the
@@ -39,7 +39,7 @@ The basic design is explained in the following diagram:
    :target: ../_images/frontend-kernel.png
 
 A single kernel can be simultaneously connected to one or more frontends.  The
-kernel has four sockets that serve the following functions:
+kernel has dedicated sockets for the following functions:
 
 1. **Shell**: this single ROUTER socket allows multiple incoming connections from
    frontends, and this is the socket where requests for code execution, object
@@ -959,6 +959,27 @@ Message type: ``shutdown_reply``::
    socket, they simply send a forceful process termination signal, since a dead
    process is unlikely to respond in any useful way to messages.
 
+.. _msging_interrupt:
+
+Kernel interrupt
+----------------
+
+In case a kernel can not catch operating system interrupt signals (e.g. the used
+runtime handles signals and does not allow a user program to define a callback),
+a kernel can choose to be notified using a message instead. For this to work,
+the kernels kernelspec must set `interrupt_mode` to ``message``. An interruption
+will then result in the following message on the `control` channel:
+
+Message type: ``interrupt_request``::
+
+    content = {}
+
+Message type: ``interrupt_reply``::
+
+    content = {}
+
+.. versionadded:: 5.3
+
 
 Messages on the IOPub (PUB/SUB) channel
 =======================================
@@ -1415,7 +1436,7 @@ Known affected frontends (as of 2017-06):
 
 - Jupyter Notebook < 5.1
 - JupyterLab < 0.24
-- nteract
+- nteract < 0.2.0
 - CoCalc
 - Jupyter Console and QtConsole with Python 2 on macOS and Windows
 
@@ -1423,7 +1444,7 @@ Known *not* affected frontends:
 
 - QtConsole, Jupyter Console with Python 3 or Python 2 on Linux
 
-.. see-also::
+.. seealso::
 
     `Discussion on GitHub <https://github.com/jupyter/jupyter_client/issues/259>`_
 
diff --git a/jupyter_client/_version.py b/jupyter_client/_version.py
index 90dd2e9..b04acc3 100644
--- a/jupyter_client/_version.py
+++ b/jupyter_client/_version.py
@@ -1,5 +1,5 @@
-version_info = (5, 1, 0)
+version_info = (5, 2, 0)
 __version__ = '.'.join(map(str, version_info))
 
-protocol_version_info = (5, 2)
+protocol_version_info = (5, 3)
 protocol_version = "%i.%i" % protocol_version_info
diff --git a/jupyter_client/connect.py b/jupyter_client/connect.py
index 042904f..91efbc4 100644
--- a/jupyter_client/connect.py
+++ b/jupyter_client/connect.py
@@ -10,6 +10,7 @@ related to writing and reading connections files.
 
 from __future__ import absolute_import
 
+import errno
 import glob
 import json
 import os
@@ -148,14 +149,19 @@ def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0,
             new_permissions = permissions | stat.S_ISVTX
             if new_permissions != permissions:
                 try:
-                    os.chmod(path, permissions)
+                    os.chmod(path, new_permissions)
                 except OSError as e:
-                    # failed to set sticky bit,
-                    # probably not a big deal
-                    warnings.warn(
-                        "Failed to set sticky bit on %r: %s" % (path, e),
-                        RuntimeWarning,
-                    )
+                    if e.errno == errno.EPERM and path == runtime_dir:
+                        # suppress permission errors setting sticky bit on runtime_dir,
+                        # which we may not own.
+                        pass
+                    else:
+                        # failed to set sticky bit, probably not a big deal
+                        warnings.warn(
+                            "Failed to set sticky bit on %r: %s"
+                            "\nProbably not a big deal, but runtime files may be cleaned up periodically." % (path, e),
+                            RuntimeWarning,
+                        )
 
     return fname, cfg
 
@@ -299,6 +305,7 @@ class ConnectionFileMixin(LoggingConfigurable):
     _connection_file_written = Bool(False)
 
     transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
+    kernel_name = Unicode()
 
     ip = Unicode(config=True,
         help="""Set the kernel\'s IP address [default localhost].
@@ -333,6 +340,9 @@ class ConnectionFileMixin(LoggingConfigurable):
     control_port = Integer(0, config=True,
             help="set the control (ROUTER) port [default: random]")
 
+    # names of the ports with random assignment
+    _random_port_names = None
+
     @property
     def ports(self):
         return [ getattr(self, name) for name in port_names ]
@@ -417,6 +427,37 @@ class ConnectionFileMixin(LoggingConfigurable):
             except (IOError, OSError):
                 pass
 
+    def _record_random_port_names(self):
+        """Records which of the ports are randomly assigned.
+
+        Records on first invocation, if the transport is tcp.
+        Does nothing on later invocations."""
+
+        if self.transport != 'tcp':
+            return
+        if self._random_port_names is not None:
+            return
+
+        self._random_port_names = []
+        for name in port_names:
+            if getattr(self, name) <= 0:
+                self._random_port_names.append(name)
+
+    def cleanup_random_ports(self):
+        """Forgets randomly assigned port numbers and cleans up the connection file.
+
+        Does nothing if no port numbers have been randomly assigned.
+        In particular, does nothing unless the transport is tcp.
+        """
+
+        if not self._random_port_names:
+            return
+
+        for name in self._random_port_names:
+            setattr(self, name, 0)
+
+        self.cleanup_connection_file()
+
     def write_connection_file(self):
         """Write connection info to JSON dict in self.connection_file."""
         if self._connection_file_written and os.path.exists(self.connection_file):
@@ -431,6 +472,7 @@ class ConnectionFileMixin(LoggingConfigurable):
             kernel_name=self.kernel_name
         )
         # write_connection_file also sets default ports:
+        self._record_random_port_names()
         for name in port_names:
             setattr(self, name, cfg[name])
 
@@ -467,6 +509,7 @@ class ConnectionFileMixin(LoggingConfigurable):
         self.transport = info.get('transport', self.transport)
         self.ip = info.get('ip', self._ip_default())
 
+        self._record_random_port_names()
         for name in port_names:
             if getattr(self, name) == 0 and name in info:
                 # not overridden by config or cl_args
diff --git a/jupyter_client/ioloop/manager.py b/jupyter_client/ioloop/manager.py
index e033f58..cc28529 100644
--- a/jupyter_client/ioloop/manager.py
+++ b/jupyter_client/ioloop/manager.py
@@ -1,15 +1,7 @@
 """A kernel manager with a tornado IOLoop"""
 
-#-----------------------------------------------------------------------------
-#  Copyright (c) The Jupyter Development Team
-#
-#  Distributed under the terms of the BSD License.  The full license is in
-#  the file COPYING, distributed as part of this software.
-#-----------------------------------------------------------------------------
-
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
 
 from __future__ import absolute_import
 
@@ -17,16 +9,13 @@ from zmq.eventloop import ioloop
 from zmq.eventloop.zmqstream import ZMQStream
 
 from traitlets import (
-    Instance
+    Instance,
+    Type,
 )
 
 from jupyter_client.manager import KernelManager
 from .restarter import IOLoopKernelRestarter
 
-#-----------------------------------------------------------------------------
-# Code
-#-----------------------------------------------------------------------------
-
 
 def as_zmqstream(f):
     def wrapped(self, *args, **kwargs):
@@ -36,16 +25,26 @@ def as_zmqstream(f):
 
 class IOLoopKernelManager(KernelManager):
 
-    loop = Instance('zmq.eventloop.ioloop.IOLoop')
+    loop = Instance('tornado.ioloop.IOLoop')
     def _loop_default(self):
-        return ioloop.IOLoop.instance()
-
+        return ioloop.IOLoop.current()
+
+    restarter_class = Type(
+        default_value=IOLoopKernelRestarter,
+        klass=IOLoopKernelRestarter,
+        help=(
+            'Type of KernelRestarter to use. '
+            'Must be a subclass of IOLoopKernelRestarter.\n'
+            'Override this to customize how kernel restarts are managed.'
+        ),
+        config=True,
+    )
     _restarter = Instance('jupyter_client.ioloop.IOLoopKernelRestarter', allow_none=True)
 
     def start_restarter(self):
         if self.autorestart and self.has_kernel:
             if self._restarter is None:
-                self._restarter = IOLoopKernelRestarter(
+                self._restarter = self.restarter_class(
                     kernel_manager=self, loop=self.loop,
                     parent=self, log=self.log
                 )
diff --git a/jupyter_client/ioloop/restarter.py b/jupyter_client/ioloop/restarter.py
index 6f53174..69079ee 100644
--- a/jupyter_client/ioloop/restarter.py
+++ b/jupyter_client/ioloop/restarter.py
@@ -4,37 +4,28 @@ This watches a kernel's state using KernelManager.is_alive and auto
 restarts the kernel if it dies.
 """
 
-#-----------------------------------------------------------------------------
-#  Copyright (c) The Jupyter Development Team
-#
-#  Distributed under the terms of the BSD License.  The full license is in
-#  the file COPYING, distributed as part of this software.
-#-----------------------------------------------------------------------------
-
-#-----------------------------------------------------------------------------
-# Imports
-#-----------------------------------------------------------------------------
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
 
 from __future__ import absolute_import
+import warnings
 
 from zmq.eventloop import ioloop
 
-
 from jupyter_client.restarter import KernelRestarter
 from traitlets import (
     Instance,
 )
 
-#-----------------------------------------------------------------------------
-# Code
-#-----------------------------------------------------------------------------
-
 class IOLoopKernelRestarter(KernelRestarter):
     """Monitor and autorestart a kernel."""
 
-    loop = Instance('zmq.eventloop.ioloop.IOLoop')
+    loop = Instance('tornado.ioloop.IOLoop')
     def _loop_default(self):
-        return ioloop.IOLoop.instance()
+        warnings.warn("IOLoopKernelRestarter.loop is deprecated in jupyter-client 5.2",
+            DeprecationWarning, stacklevel=4,
+        )
+        return ioloop.IOLoop.current()
 
     _pcallback = None
 
@@ -42,7 +33,7 @@ class IOLoopKernelRestarter(KernelRestarter):
         """Start the polling of the kernel."""
         if self._pcallback is None:
             self._pcallback = ioloop.PeriodicCallback(
-                self.poll, 1000*self.time_to_dead, self.loop
+                self.poll, 1000*self.time_to_dead,
             )
             self._pcallback.start()
 
diff --git a/jupyter_client/kernelapp.py b/jupyter_client/kernelapp.py
new file mode 100644
index 0000000..a2ab178
--- /dev/null
+++ b/jupyter_client/kernelapp.py
@@ -0,0 +1,81 @@
+import os
+import signal
+import uuid
+
+from jupyter_core.application import JupyterApp, base_flags
+from tornado.ioloop import IOLoop
+from traitlets import Unicode
+
+from . import __version__
+from .kernelspec import KernelSpecManager, NATIVE_KERNEL_NAME
+from .manager import KernelManager
+
+class KernelApp(JupyterApp):
+    """Launch a kernel by name in a local subprocess.
+    """
+    version = __version__
+    description = "Run a kernel locally in a subprocess"
+
+    classes = [KernelManager, KernelSpecManager]
+
+    aliases = {
+        'kernel': 'KernelApp.kernel_name',
+        'ip': 'KernelManager.ip',
+    }
+    flags = {'debug': base_flags['debug']}
+
+    kernel_name = Unicode(NATIVE_KERNEL_NAME,
+        help = 'The name of a kernel type to start'
+    ).tag(config=True)
+
+    def initialize(self, argv=None):
+        super(KernelApp, self).initialize(argv)
+        self.km = KernelManager(kernel_name=self.kernel_name,
+                                config=self.config)
+        cf_basename = 'kernel-%s.json' % uuid.uuid4()
+        self.km.connection_file = os.path.join(self.runtime_dir, cf_basename)
+        self.loop = IOLoop.current()
+        self.loop.add_callback(self._record_started)
+
+    def setup_signals(self):
+        """Shutdown on SIGTERM or SIGINT (Ctrl-C)"""
+        if os.name == 'nt':
+            return
+
+        def shutdown_handler(signo, frame):
+            self.loop.add_callback_from_signal(self.shutdown, signo)
+        for sig in [signal.SIGTERM, signal.SIGINT]:
+            signal.signal(sig, shutdown_handler)
+
+    def shutdown(self, signo):
+        self.log.info('Shutting down on signal %d' % signo)
+        self.km.shutdown_kernel()
+        self.loop.stop()
+
+    def log_connection_info(self):
+        cf = self.km.connection_file
+        self.log.info('Connection file: %s', cf)
+        self.log.info("To connect a client: --existing %s", os.path.basename(cf))
+
+    def _record_started(self):
+        """For tests, create a file to indicate that we've started
+
+        Do not rely on this except in our own tests!
+        """
+        fn = os.environ.get('JUPYTER_CLIENT_TEST_RECORD_STARTUP_PRIVATE')
+        if fn is not None:
+            with open(fn, 'wb'):
+                pass
+
+    def start(self):
+        self.log.info('Starting kernel %r', self.kernel_name)
+        try:
+            self.km.start_kernel()
+            self.log_connection_info()
+            self.setup_signals()
+            self.loop.start()
+        finally:
+            self.km.cleanup()
+
+
+main = KernelApp.launch_instance
diff --git a/jupyter_client/kernelspec.py b/jupyter_client/kernelspec.py
index 0b882ad..3e05d29 100644
--- a/jupyter_client/kernelspec.py
+++ b/jupyter_client/kernelspec.py
@@ -3,6 +3,7 @@
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
 
+import errno
 import io
 import json
 import os
@@ -13,7 +14,9 @@ import warnings
 pjoin = os.path.join
 
 from ipython_genutils.py3compat import PY3
-from traitlets import HasTraits, List, Unicode, Dict, Set, Bool, Type
+from traitlets import (
+    HasTraits, List, Unicode, Dict, Set, Bool, Type, CaselessStrEnum
+)
 from traitlets.config import LoggingConfigurable
 
 from jupyter_core.paths import jupyter_data_dir, jupyter_path, SYSTEM_JUPYTER_PATH
@@ -28,6 +31,10 @@ class KernelSpec(HasTraits):
     language = Unicode()
     env = Dict()
     resource_dir = Unicode()
+    interrupt_mode = CaselessStrEnum(
+        ['message', 'signal'], default_value='signal'
+    )
+    metadata = Dict()
 
     @classmethod
     def from_resource_dir(cls, resource_dir):
@@ -45,6 +52,8 @@ class KernelSpec(HasTraits):
                  env=self.env,
                  display_name=self.display_name,
                  language=self.language,
+                 interrupt_mode=self.interrupt_mode,
+                 metadata=self.metadata,
                 )
 
         return d
@@ -191,15 +200,39 @@ class KernelSpecManager(LoggingConfigurable):
 
         return self.kernel_spec_class.from_resource_dir(resource_dir)
 
+    def _find_spec_directory(self, kernel_name):
+        """Find the resource directory of a named kernel spec"""
+        for kernel_dir in self.kernel_dirs:
+            try:
+                files = os.listdir(kernel_dir)
+            except OSError as e:
+                if e.errno in (errno.ENOTDIR, errno.ENOENT):
+                    continue
+                raise
+            for f in files:
+                path = pjoin(kernel_dir, f)
+                if f.lower() == kernel_name and _is_kernel_dir(path):
+                    return path
+
+        if kernel_name == NATIVE_KERNEL_NAME:
+            try:
+                from ipykernel.kernelspec import RESOURCES
+            except ImportError:
+                pass
+            else:
+                return RESOURCES
+
     def get_kernel_spec(self, kernel_name):
         """Returns a :class:`KernelSpec` instance for the given kernel_name.
 
         Raises :exc:`NoSuchKernel` if the given kernel name is not found.
         """
-        d = self.find_kernel_specs()
-        try:
-            resource_dir = d[kernel_name.lower()]
-        except KeyError:
+        if not _is_valid_kernel_name(kernel_name):
+            self.log.warning("Kernelspec name %r is invalid: %s", kernel_name,
+                             _kernel_name_description)
+
+        resource_dir = self._find_spec_directory(kernel_name.lower())
+        if resource_dir is None:
             raise NoSuchKernel(kernel_name)
 
         return self._get_kernel_spec_by_name(kernel_name, resource_dir)
@@ -218,14 +251,21 @@ class KernelSpecManager(LoggingConfigurable):
             }
         """
         d = self.find_kernel_specs()
-        return {kname: {
-                "resource_dir": d[kname],
-                "spec": self._get_kernel_spec_by_name(kname, d[kname]).to_dict()
-                } for kname in d}
+        res = {}
+        for kname, resource_dir in d.items():
+            try:
+                spec = self._get_kernel_spec_by_name(kname, resource_dir)
+                res[kname] = {
+                    "resource_dir": resource_dir,
+                    "spec": spec.to_dict()
+                }
+            except Exception:
+                self.log.warning("Error loading kernelspec %r", kname, exc_info=True)
+        return res
 
     def remove_kernel_spec(self, name):
         """Remove a kernel spec directory by name.
-        
+
         Returns the path that was deleted.
         """
         save_native = self.ensure_native_kernel
@@ -261,7 +301,7 @@ class KernelSpecManager(LoggingConfigurable):
         If ``user`` is False, it will attempt to install into the systemwide
         kernel registry. If the process does not have appropriate permissions,
         an :exc:`OSError` will be raised.
-        
+
         If ``prefix`` is given, the kernelspec will be installed to
         PREFIX/share/jupyter/kernels/KERNEL_NAME. This can be sys.prefix
         for installation inside virtual or conda envs.
@@ -282,16 +322,16 @@ class KernelSpecManager(LoggingConfigurable):
                 DeprecationWarning,
                 stacklevel=2,
             )
-        
+
         destination = self._get_destination_dir(kernel_name, user=user, prefix=prefix)
         self.log.debug('Installing kernelspec in %s', destination)
-        
+
         kernel_dir = os.path.dirname(destination)
         if kernel_dir not in self.kernel_dirs:
             self.log.warning("Installing to %s, which is not in %s. The kernelspec may not be found.",
                 kernel_dir, self.kernel_dirs,
             )
-        
+
         if os.path.isdir(destination):
             self.log.info('Removing existing kernelspec in %s', destination)
             shutil.rmtree(destination)
diff --git a/jupyter_client/manager.py b/jupyter_client/manager.py
index 373105e..1b60b65 100644
--- a/jupyter_client/manager.py
+++ b/jupyter_client/manager.py
@@ -11,11 +11,6 @@ import re
 import signal
 import sys
 import time
-import warnings
-try:
-    from queue import Empty  # Py 3
-except ImportError:
-    from Queue import Empty  # Py 2
 
 import zmq
 
@@ -29,7 +24,6 @@ from jupyter_client import (
     kernelspec,
 )
 from .connect import ConnectionFileMixin
-from .session import Session
 from .managerabc import (
     KernelManagerABC
 )
@@ -174,8 +168,10 @@ class KernelManager(ConnectionFileMixin):
         else:
             cmd = self.kernel_spec.argv + extra_arguments
 
-        if cmd and cmd[0] == 'python':
-            # executable is 'python', use sys.executable.
+        if cmd and cmd[0] in {'python',
+                              'python%i' % sys.version_info[0],
+                              'python%i.%i' % sys.version_info[:2]}:
+            # executable is 'python' or 'python3', use sys.executable.
             # These will typically be the same,
             # but if the current process is in an env
             # and has been launched by abspath without
@@ -186,6 +182,10 @@ class KernelManager(ConnectionFileMixin):
         ns = dict(connection_file=self.connection_file,
                   prefix=sys.prefix,
                  )
+
+        if self.kernel_spec:
+            ns["resource_dir"] = self.kernel_spec.resource_dir
+
         ns.update(self._launch_args)
 
         pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
@@ -250,7 +250,7 @@ class KernelManager(ConnectionFileMixin):
             # If kernel_cmd has been set manually, don't refer to a kernel spec
             # Environment variables from kernel spec are added to os.environ
             env.update(self.kernel_spec.env or {})
-        
+
         # launch the kernel subprocess
         self.log.debug("Starting kernel: %s", kernel_cmd)
         self.kernel = self._launch_kernel(kernel_cmd, env=env,
@@ -326,12 +326,9 @@ class KernelManager(ConnectionFileMixin):
 
         self.cleanup(connection_file=not restart)
 
-    def restart_kernel(self, now=False, **kw):
+    def restart_kernel(self, now=False, newports=False, **kw):
         """Restarts a kernel with the arguments that were used to launch it.
 
-        If the old kernel was launched with random ports, the same ports will be
-        used for the new kernel. The same connection file is used again.
-
         Parameters
         ----------
         now : bool, optional
@@ -342,6 +339,14 @@ class KernelManager(ConnectionFileMixin):
             In all cases the kernel is restarted, the only difference is whether
             it is given a chance to perform a clean shutdown or not.
 
+        newports : bool, optional
+            If the old kernel was launched with random ports, this flag decides
+            whether the same ports and connection file will be used again.
+            If False, the same ports and connection file are used. This is
+            the default. If True, new random port numbers are chosen and a
+            new connection file is written. It is still possible that the newly
+            chosen random port numbers happen to be the same as the old ones.
+
         `**kw` : optional
             Any options specified here will overwrite those used to launch the
             kernel.
@@ -353,6 +358,9 @@ class KernelManager(ConnectionFileMixin):
             # Stop currently running kernel.
             self.shutdown_kernel(now=now, restart=True)
 
+            if newports:
+                self.cleanup_random_ports()
+
             # Start new kernel.
             self._launch_args.update(kw)
             self.start_kernel(**self._launch_args)
@@ -372,7 +380,10 @@ class KernelManager(ConnectionFileMixin):
             # Signal the kernel to terminate (sends SIGKILL on Unix and calls
             # TerminateProcess() on Win32).
             try:
-                self.kernel.kill()
+                if hasattr(signal, 'SIGKILL'):
+                    self.signal_kernel(signal.SIGKILL)
+                else:
+                    self.kernel.kill()
             except OSError as e:
                 # In Windows, we will get an Access Denied error if the process
                 # has already terminated. Ignore it.
@@ -399,11 +410,18 @@ class KernelManager(ConnectionFileMixin):
         platforms.
         """
         if self.has_kernel:
-            if sys.platform == 'win32':
-                from .win_interrupt import send_interrupt
-                send_interrupt(self.kernel.win32_interrupt_event)
-            else:
-                self.signal_kernel(signal.SIGINT)
+            interrupt_mode = self.kernel_spec.interrupt_mode
+            if interrupt_mode == 'signal':
+                if sys.platform == 'win32':
+                    from .win_interrupt import send_interrupt
+                    send_interrupt(self.kernel.win32_interrupt_event)
+                else:
+                    self.signal_kernel(signal.SIGINT)
+
+            elif interrupt_mode == 'message':
+                msg = self.session.msg("interrupt_request", content={})
+                self._connect_control_socket()
+                self.session.send(self._control_socket, msg)
         else:
             raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
 
diff --git a/jupyter_client/restarter.py b/jupyter_client/restarter.py
index 1c30fca..83d4356 100644
--- a/jupyter_client/restarter.py
+++ b/jupyter_client/restarter.py
@@ -34,8 +34,13 @@ class KernelRestarter(LoggingConfigurable):
     restart_limit = Integer(5, config=True,
         help="""The number of consecutive autorestarts before the kernel is presumed dead."""
     )
+
+    random_ports_until_alive = Bool(True, config=True,
+        help="""Whether to choose new random ports when restarting before the kernel is alive."""
+    )
     _restarting = Bool(False)
     _restart_count = Integer(0)
+    _initial_startup = Bool(True)
 
     callbacks = Dict()
     def _callbacks_default(self):
@@ -98,14 +103,18 @@ class KernelRestarter(LoggingConfigurable):
                 self._restart_count = 0
                 self.stop()
             else:
-                self.log.info('KernelRestarter: restarting kernel (%i/%i)',
+                newports = self.random_ports_until_alive and self._initial_startup
+                self.log.info('KernelRestarter: restarting kernel (%i/%i), %s random ports',
                     self._restart_count,
-                    self.restart_limit
+                    self.restart_limit,
+                    'new' if newports else 'keep'
                 )
                 self._fire_callbacks('restart')
-                self.kernel_manager.restart_kernel(now=True)
+                self.kernel_manager.restart_kernel(now=True, newports=newports)
                 self._restarting = True
         else:
+            if self._initial_startup:
+                self._initial_startup = False
             if self._restarting:
                 self.log.debug("KernelRestarter: restart apparently succeeded")
             self._restarting = False
diff --git a/jupyter_client/session.py b/jupyter_client/session.py
index b172b63..33b1c0b 100644
--- a/jupyter_client/session.py
+++ b/jupyter_client/session.py
@@ -191,9 +191,9 @@ class SessionFactory(LoggingConfigurable):
     session = Instance('jupyter_client.session.Session',
                        allow_none=True)
 
-    loop = Instance('zmq.eventloop.ioloop.IOLoop')
+    loop = Instance('tornado.ioloop.IOLoop')
     def _loop_default(self):
-        return IOLoop.instance()
+        return IOLoop.current()
 
     def __init__(self, **kwargs):
         super(SessionFactory, self).__init__(**kwargs)
@@ -717,13 +717,20 @@ class Session(Configurable):
             )
             return
         buffers = [] if buffers is None else buffers
-        for buf in buffers:
-            if not isinstance(buf, memoryview):
+        for idx, buf in enumerate(buffers):
+            if isinstance(buf, memoryview):
+                view = buf
+            else:
                 try:
                     # check to see if buf supports the buffer protocol.
-                    memoryview(buf)
+                    view = memoryview(buf)
                 except TypeError:
                     raise TypeError("Buffer objects must support the buffer protocol.")
... 328 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/jupyter-client.git



More information about the Python-modules-commits mailing list