Hello community, here is the log from the commit of package python3-jupyter_client for openSUSE:Factory checked in at 2016-03-26 15:22:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python3-jupyter_client (Old) and /work/SRC/openSUSE:Factory/.python3-jupyter_client.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python3-jupyter_client" Changes: -------- New Changes file: --- /dev/null 2016-01-27 19:41:03.648095915 +0100 +++ /work/SRC/openSUSE:Factory/.python3-jupyter_client.new/python3-jupyter_client-doc.changes 2016-03-26 15:22:53.000000000 +0100 @@ -0,0 +1,78 @@ +------------------------------------------------------------------- +Sat Mar 12 18:57:00 UTC 2016 - [email protected] + +- update to version 4.2.2: + * Another fix for the :func:`start_new_kernel` issue in 4.2.1 + affecting slow-starting kernels. + +------------------------------------------------------------------- +Mon Mar 7 03:51:42 UTC 2016 - [email protected] + +- update to version 4.2.1: + * Fix regression in 4.2 causing :func:`start_new_kernel` to fail + while waiting for kernels to become available. + +------------------------------------------------------------------- +Sat Mar 5 18:48:21 UTC 2016 - [email protected] + +- update to version 4.2.0: + * added :command:`jupyter kernelspec remove` for removing + kernelspecs + * allow specifying the environment for kernel processes via the env + argument + * added name field to connection files identifying the kernelspec + name, so that consumers of connection files (alternate frontends) + can identify the kernelspec in use + * added :meth:`KernelSpecManager.get_all_specs` for getting all + kernelspecs more efficiently + * various improvements to error messages and documentation + +------------------------------------------------------------------- +Wed Feb 17 14:46:15 UTC 2016 - [email protected] + +- PDF docs still broken + +------------------------------------------------------------------- +Wed Feb 17 10:14:51 UTC 2016 - [email protected] + +- Split documentation to speed up building. + This is a dependency of a lot of other packages, and the + dependencies for the documentation are very heavy. So build + the documentation separately to avoid holding up the build + process. + +------------------------------------------------------------------- +Thu Jan 7 14:04:10 UTC 2016 - [email protected] + +- Tests fail with python3-buildservice-tweak + +------------------------------------------------------------------- +Sat Oct 10 16:22:42 UTC 2015 - [email protected] + +- update to version 4.1.1: + * Setuptools fixes for jupyter kernelspec + * jupyter kernelspec list includes paths + * add :meth:`KernelManager.blocking_client` + * provisional implementation of comm_info requests from upcoming 5.1 + release of the protocol + +------------------------------------------------------------------- +Thu Oct 1 12:52:38 UTC 2015 - [email protected] + +- Fix documentation location. + +------------------------------------------------------------------- +Thu Oct 1 12:04:25 UTC 2015 - [email protected] + +- Build documentation + +------------------------------------------------------------------- +Fri Aug 14 07:03:18 UTC 2015 - [email protected] + +- Fix update-alternatives usage + +------------------------------------------------------------------- +Tue Jul 21 14:28:11 UTC 2015 - [email protected] + +- Initial version + --- /work/SRC/openSUSE:Factory/python3-jupyter_client/python3-jupyter_client.changes 2016-01-12 16:12:35.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python3-jupyter_client.new/python3-jupyter_client.changes 2016-03-26 15:22:54.000000000 +0100 @@ -1,0 +2,38 @@ +Sat Mar 12 18:57:00 UTC 2016 - [email protected] + +- update to version 4.2.2: + * Another fix for the :func:`start_new_kernel` issue in 4.2.1 + affecting slow-starting kernels. + +------------------------------------------------------------------- +Mon Mar 7 03:51:42 UTC 2016 - [email protected] + +- update to version 4.2.1: + * Fix regression in 4.2 causing :func:`start_new_kernel` to fail + while waiting for kernels to become available. + +------------------------------------------------------------------- +Sat Mar 5 18:48:21 UTC 2016 - [email protected] + +- update to version 4.2.0: + * added :command:`jupyter kernelspec remove` for removing + kernelspecs + * allow specifying the environment for kernel processes via the env + argument + * added name field to connection files identifying the kernelspec + name, so that consumers of connection files (alternate frontends) + can identify the kernelspec in use + * added :meth:`KernelSpecManager.get_all_specs` for getting all + kernelspecs more efficiently + * various improvements to error messages and documentation + +------------------------------------------------------------------- +Wed Feb 17 10:14:51 UTC 2016 - [email protected] + +- Split documentation to speed up building. + This is a dependency of a lot of other packages, and the + dependencies for the documentation are very heavy. So build + the documentation separately to avoid holding up the build + process. + +------------------------------------------------------------------- Old: ---- jupyter_client-4.1.1.tar.gz New: ---- jupyter_client-4.2.2.tar.gz python3-jupyter_client-doc.changes python3-jupyter_client-doc.spec ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python3-jupyter_client-doc.spec ++++++ # # spec file for package python3-jupyter_client-doc # # Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed # upon. The license for this file, and modifications and additions to the # file, is the same license as for the pristine package itself (unless the # license for the pristine package is not an Open Source License, in which # case the license is the MIT License). An "Open Source License" is a # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. # Please submit bugfixes or comments via http://bugs.opensuse.org/ # %if 0%{?suse_version} && ( 0%{?suse_version} != 1315 && 0%{?suse_version} > 1110 ) # LaTeX is broken in current version, should be fixed in next version #%define build_pdf 1 %define build_pdf 0 %else %define build_pdf 0 %endif Name: python3-jupyter_client-doc Version: 4.2.2 Release: 0 Summary: Documentation for python3-jupyter_client License: BSD-3-Clause Group: Development/Languages/Python Url: http://jupyter.org Source: https://pypi.python.org/packages/source/j/jupyter_client/jupyter_client-%{version}.tar.gz BuildRequires: python3-jupyter_client # Documentation requirements BuildRequires: python3-Sphinx %if %{build_pdf} BuildRequires: python3-Sphinx-latex %endif BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildArch: noarch %description Documentation and help files for python3-jupyter_client. %package html Summary: HTML documentation for python3-jupyter_client Group: Development/Languages/Python Recommends: python3-jupyter_client = %{version} %description html Documentation and help files for python3-jupyter_client in HTML format. %package pdf Summary: PDF documentation for python3-jupyter_client Group: Development/Languages/Python Recommends: python3-jupyter_client = %{version} %description pdf Documentation and help files for python3-jupyter_client in PDF format. %prep %setup -q -n jupyter_client-%{version} %build # Not needed %install # Build the documentation pushd docs %if %{build_pdf} PYTHONPATH=%{buildroot}%{python3_sitelib} make latexpdf %endif PYTHONPATH=%{buildroot}%{python3_sitelib} make html rm -rf _build/html/.buildinfo popd %files html %defattr(-,root,root,-) %doc COPYING.md %doc docs/_build/html/ %if %{build_pdf} %files pdf %defattr(-,root,root,-) %doc COPYING.md %doc docs/_build/latex/jupyter_core.pdf %endif %changelog ++++++ python3-jupyter_client.spec ++++++ --- /var/tmp/diff_new_pack.NCqpB4/_old 2016-03-26 15:22:54.000000000 +0100 +++ /var/tmp/diff_new_pack.NCqpB4/_new 2016-03-26 15:22:54.000000000 +0100 @@ -1,7 +1,7 @@ # # spec file for package python3-jupyter_client # -# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: python3-jupyter_client -Version: 4.1.1 +Version: 4.2.2 Release: 0 Summary: Jupyter protocol implementation and client libraries License: BSD-3-Clause @@ -31,11 +31,6 @@ BuildRequires: python3-traitlets # Test Requirements BuildRequires: python3-nose -# Documentation requirements -BuildRequires: python3-Sphinx -%if 0%{?suse_version} && ( 0%{?suse_version} != 1315 && 0%{?suse_version} > 1110 ) -BuildRequires: python3-Sphinx-latex -%endif Requires: python3-jupyter_core Requires: python3-pyzmq >= 13 Requires: python3-traitlets @@ -52,22 +47,6 @@ It also provides the jupyter kernelspec entrypoint for installing kernelspecs for use with Jupyter frontends. -%package doc-html -Summary: HTML documentation for %{name} -Group: Development/Languages/Python -Recommends: %{name} = %{version} - -%description doc-html -Documentation and help files for %{name} in HTML format - -%package doc-pdf -Summary: HTML documentation for %{name} -Group: Development/Languages/Python -Recommends: %{name} = %{version} - -%description doc-pdf -Documentation and help files for %{name} in PDF format - %prep %setup -q -n jupyter_client-%{version} @@ -86,16 +65,6 @@ # create a dummy target for /etc/alternatives/jupyter-kernelspec touch %{buildroot}%{_sysconfdir}/alternatives/jupyter-kernelspec -# Build the documentation -pushd docs -# LaTeX is broken in current version, should be fixed in next version -# %if 0%{?suse_version} && ( 0%{?suse_version} != 1315 && 0%{?suse_version} > 1110 ) -# PYTHONPATH=%{buildroot}%{python3_sitelib} make latexpdf -# %endif -PYTHONPATH=%{buildroot}%{python3_sitelib} make html -rm -rf _build/html/.buildinfo -popd - %check # We can't test the kernel manager because that requires the ipython kernel, # which requires this package, causing a dependency loop. So those tests are @@ -122,16 +91,4 @@ %ghost %{_sysconfdir}/alternatives/jupyter-kernelspec %{python3_sitelib}/* -%files doc-html -%defattr(-,root,root,-) -%doc COPYING.md -%doc docs/_build/html/ - -# %if 0%{?suse_version} && ( 0%{?suse_version} != 1315 && 0%{?suse_version} > 1110 ) -# %files doc-pdf -# %defattr(-,root,root,-) -# %doc COPYING.md -# %doc docs/_build/latex/jupyter_core.pdf -# %endif - %changelog ++++++ jupyter_client-4.1.1.tar.gz -> jupyter_client-4.2.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/PKG-INFO new/jupyter_client-4.2.2/PKG-INFO --- old/jupyter_client-4.1.1/PKG-INFO 2015-10-08 13:41:25.000000000 +0200 +++ new/jupyter_client-4.2.2/PKG-INFO 2016-03-10 10:13:08.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: jupyter_client -Version: 4.1.1 +Version: 4.2.2 Summary: Jupyter protocol implementation and client libraries Home-page: http://jupyter.org Author: Jupyter Development Team diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/docs/api/kernelspec.rst new/jupyter_client-4.2.2/docs/api/kernelspec.rst --- old/jupyter_client-4.1.1/docs/api/kernelspec.rst 2015-05-27 04:19:02.000000000 +0200 +++ new/jupyter_client-4.2.2/docs/api/kernelspec.rst 2016-03-06 10:43:14.000000000 +0100 @@ -35,6 +35,8 @@ .. automethod:: find_kernel_specs + .. automethod:: get_all_specs + .. automethod:: get_kernel_spec .. automethod:: install_kernel_spec diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/docs/changelog.rst new/jupyter_client-4.2.2/docs/changelog.rst --- old/jupyter_client-4.1.1/docs/changelog.rst 2015-10-08 11:14:48.000000000 +0200 +++ new/jupyter_client-4.2.2/docs/changelog.rst 2016-03-10 10:11:47.000000000 +0100 @@ -4,6 +4,38 @@ Changes in Jupyter Client ========================= +4.2 +=== + +4.2.2 +----- + +`4.2.2 on GitHub <https://github.com/jupyter/jupyter_client/milestones/4.2.2>`__ + +- Another fix for the :func:`start_new_kernel` issue in 4.2.1 affecting slow-starting kernels. + + +4.2.1 +----- + +`4.2.1 on GitHub <https://github.com/jupyter/jupyter_client/milestones/4.2.1>`__ + +- Fix regression in 4.2 causing :func:`start_new_kernel` + to fail while waiting for kernels to become available. + + +4.2.0 +----- + +`4.2.0 on GitHub <https://github.com/jupyter/jupyter_client/milestones/4.2>`__ + +- added :command:`jupyter kernelspec remove` for removing kernelspecs +- allow specifying the environment for kernel processes via the ``env`` argument +- added ``name`` field to connection files identifying the kernelspec name, + so that consumers of connection files (alternate frontends) can identify the kernelspec in use +- added :meth:`KernelSpecManager.get_all_specs` for getting all kernelspecs more efficiently +- various improvements to error messages and documentation + 4.1 === diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/docs/messaging.rst new/jupyter_client-4.2.2/docs/messaging.rst --- old/jupyter_client-4.1.1/docs/messaging.rst 2015-10-08 10:43:27.000000000 +0200 +++ new/jupyter_client-4.2.2/docs/messaging.rst 2016-03-06 10:43:14.000000000 +0100 @@ -75,6 +75,9 @@ 4. **Control**: This channel is identical to Shell, but operates on a separate socket, to allow important messages to avoid queueing behind execution requests (e.g. shutdown or abort). +5. **Heartbeat**: This socket allows for simple bytestring messages to be sent + between the frontend and the kernel to ensure that they are still connected. + The actual format of the messages allowed on each of these channels is specified below. Messages are dicts of dicts with string keys and values that are reasonably representable in JSON. Our current implementation uses JSON @@ -104,9 +107,9 @@ # same kernel simultaneously, so that frontends can label the various # messages in a meaningful way. 'header' : { - 'msg_id' : uuid, + 'msg_id' : str, # typically UUID, must be unique per message 'username' : str, - 'session' : uuid, + 'session' : str, # typically UUID, should be unique per session # ISO 8601 timestamp for when the message is created 'date': str, # All recognized message type strings are listed below. @@ -181,7 +184,7 @@ In most cases, the IOPub topics are irrelevant and completely ignored, because frontends just subscribe to all topics. The convention used in the IPython kernel is to use the msg_type as the topic, - and possibly extra information about the message, e.g. ``execute_result`` or ``stream.stdout`` + and possibly extra information about the message, e.g. ``kernel.{u-u-i-d}.execute_result`` or ``stream.stdout`` After the delimiter is the `HMAC`_ signature of the message, used for authentication. If authentication is disabled, this should be an empty string. @@ -223,11 +226,12 @@ are common alternatives. After the serialized dicts are zero to many raw data buffers, -which can be used by message types that support binary data (mainly apply and data_pub). +which can be used by message types that support binary data, +which can be used in custom messages, such as comms and extensions to the protocol. -Python functional API -===================== +Python API +========== As messages are dicts, they map naturally to a ``func(**kw)`` call form. We should develop, at a few key points, functional forms of all the requests that @@ -241,7 +245,7 @@ 'header' : dict, # The msg's unique identifier and type are always stored in the header, # but the Python implementation copies them to the top level. - 'msg_id' : uuid, + 'msg_id' : str, 'msg_type' : str, 'parent_header' : dict, 'content' : dict, @@ -361,7 +365,7 @@ # One of: 'ok' OR 'error' OR 'abort' 'status' : str, - # The global kernel counter that increases by one with each request that + # The global kernel counter that increases by one with each request that # stores history. This will typically be used by clients to display # prompt numbers to the user. If the request did not store history, this will # be the current value of the counter in the kernel. @@ -777,7 +781,7 @@ # Information about the language of code for the kernel 'language_info': { - # Name of the programming language in which kernel is implemented. + # Name of the programming language that the kernel implements. # Kernel included in IPython returns 'python'. 'name': str, @@ -858,7 +862,8 @@ The client sends a shutdown request to the kernel, and once it receives the reply message (which is otherwise empty), it can assume that the kernel has -completed shutdown safely. +completed shutdown safely. The request can be sent on either the `control` or +`shell` channels. Upon their own shutdown, client applications will typically execute a last minute sanity check and forcefully terminate any kernel that is still alive, to @@ -867,13 +872,13 @@ Message type: ``shutdown_request``:: content = { - 'restart' : bool # whether the shutdown is final, or precedes a restart + 'restart' : bool # False if final shutdown, or True if shutdown precedes a restart } Message type: ``shutdown_reply``:: content = { - 'restart' : bool # whether the shutdown is final, or precedes a restart + 'restart' : bool # False if final shutdown, or True if shutdown precedes a restart } .. Note:: @@ -926,7 +931,8 @@ content = { # Who create the data - 'source' : str, + # Used in V4. Removed in V5. + # 'source' : str, # The data dict contains key/value pairs, where the keys are MIME # types and the values are the raw data of the representation in that @@ -962,42 +968,6 @@ not double-serialized as a JSON string. -Raw Data Publication --------------------- - -``display_data`` lets you publish *representations* of data, such as images and html. -This ``data_pub`` message lets you publish *actual raw data*, sent via message buffers. - -data_pub messages are constructed via the :func:`IPython.lib.datapub.publish_data` function: - -.. sourcecode:: python - - from IPython.kernel.zmq.datapub import publish_data - ns = dict(x=my_array) - publish_data(ns) - - -Message type: ``data_pub``:: - - content = { - # the keys of the data dict, after it has been unserialized - 'keys' : ['a', 'b'] - } - # the namespace dict will be serialized in the message buffers, - # which will have a length of at least one - buffers = [b'pdict', ...] - - -The interpretation of a sequence of data_pub messages for a given parent request should be -to update a single namespace with subsequent results. - -.. note:: - - No frontends directly handle data_pub messages at this time. - It is currently only used by the client/engines in :mod:`IPython.parallel`, - where engines may publish *data* to the Client, - of which the Client can then publish *representations* via ``display_data`` - to various frontends. Code inputs ----------- @@ -1105,7 +1075,7 @@ content = { # Wait to clear the output until new output is available. Clears the - # existing output immediately before the new output is displayed. + # existing output immediately before the new output is displayed. # Useful for creating simple animations with minimal flickering. 'wait' : bool, } @@ -1201,7 +1171,9 @@ { 'comm_id' : 'u-u-i-d', 'target_name' : 'my_comm', - 'data' : {} + 'data' : {}, + # Optional, the target module + 'target_module': 'my_module', } Every Comm has an ID and a target name. @@ -1214,6 +1186,9 @@ If the ``target_name`` key is not found on the receiving side, then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state. +The optional ``target_module`` is used to select a module that is responsible +for handling the ``target_name``. + Comm Messages ------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/docs/wrapperkernels.rst new/jupyter_client-4.2.2/docs/wrapperkernels.rst --- old/jupyter_client-4.1.1/docs/wrapperkernels.rst 2015-09-12 17:22:09.000000000 +0200 +++ new/jupyter_client-4.2.2/docs/wrapperkernels.rst 2016-03-06 10:43:14.000000000 +0100 @@ -36,7 +36,8 @@ Language information for :ref:`msging_kernel_info` replies, in a dictionary. This should contain the key ``mimetype`` with the mimetype of code in the - target language (e.g. ``'text/x-python'``), and ``file_extension`` (e.g. + target language (e.g. ``'text/x-python'``), the ``name`` of the language + being implemented (e.g. ``'python'``), and ``file_extension`` (e.g. ``'py'``). It may also contain keys ``codemirror_mode`` and ``pygments_lexer`` if they need to differ from :attr:`language`. @@ -65,7 +66,7 @@ To launch your kernel, add this at the end of your module:: if __name__ == '__main__': - from ipyernel.kernelapp import IPKernelApp + from ipykernel.kernelapp import IPKernelApp IPKernelApp.launch_instance(kernel_class=MyKernel) Now create a `JSON kernel spec file <http://jupyter-client.readthedocs.org/en/latest/kernels.html#kernel-specs>`_ and install it using ``jupyter kernelspec install </path/to/kernel>``. Place your kernel module anywhere Python can import it (try current directory for testing). Finally, you can run your kernel using ``jupyter console --kernel <mykernelname>``. Note that ``<mykernelname>`` in the below example is ``echo``. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/_version.py new/jupyter_client-4.2.2/jupyter_client/_version.py --- old/jupyter_client-4.1.1/jupyter_client/_version.py 2015-10-08 13:40:39.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/_version.py 2016-03-10 10:12:45.000000000 +0100 @@ -1,4 +1,4 @@ -version_info = (4, 1, 1) +version_info = (4, 2, 2) __version__ = '.'.join(map(str, version_info)) protocol_version_info = (5, 0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/blocking/client.py new/jupyter_client-4.2.2/jupyter_client/blocking/client.py --- old/jupyter_client-4.1.1/jupyter_client/blocking/client.py 2015-04-09 08:11:31.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/blocking/client.py 2016-03-10 10:09:45.000000000 +0100 @@ -9,20 +9,58 @@ from queue import Empty # Python 3 except ImportError: from Queue import Empty # Python 2 +import time from traitlets import Type from jupyter_client.channels import HBChannel from jupyter_client.client import KernelClient from .channels import ZMQSocketChannel + class BlockingKernelClient(KernelClient): - def wait_for_ready(self): + """A BlockingKernelClient """ + + def wait_for_ready(self, timeout=None): + """Waits for a response when a client is blocked + + - Sets future time for timeout + - Blocks on shell channel until a message is received + - Exit if the kernel has died + - If client times out before receiving a message from the kernel, send RuntimeError + - Flush the IOPub channel + """ + if timeout is None: + abs_timeout = float('inf') + else: + abs_timeout = time.time() + timeout + + from ..manager import KernelManager + if not isinstance(self.parent, KernelManager): + # This Client was not created by a KernelManager, + # so wait for kernel to become responsive to heartbeats + # before checking for kernel_info reply + while not self.is_alive(): + if time.time() > abs_timeout: + raise RuntimeError("Kernel didn't respond to heartbeats in %d seconds and timed out" % timeout) + time.sleep(0.2) + # Wait for kernel info reply on shell channel while True: - msg = self.shell_channel.get_msg(block=True) - if msg['msg_type'] == 'kernel_info_reply': - self._handle_kernel_info_reply(msg) - break + try: + msg = self.shell_channel.get_msg(block=True, timeout=1) + except Empty: + pass + else: + if msg['msg_type'] == 'kernel_info_reply': + self._handle_kernel_info_reply(msg) + break + + if not self.is_alive(): + raise RuntimeError('Kernel died before replying to kernel_info') + + # Check if current time is ready check time plus timeout + if time.time() > abs_timeout: + raise RuntimeError("Kernel didn't respond in %d seconds" % timeout) # Flush IOPub channel while True: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/channels.py new/jupyter_client-4.2.2/jupyter_client/channels.py --- old/jupyter_client-4.1.1/jupyter_client/channels.py 2015-09-16 17:15:03.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/channels.py 2016-03-09 13:13:37.000000000 +0100 @@ -72,8 +72,10 @@ self.address = address atexit.register(self._notice_exit) + # running is False until `.start()` is called self._running = False - self._pause = True + # don't start paused + self._pause = False self.poller = zmq.Poller() def _notice_exit(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/client.py new/jupyter_client-4.2.2/jupyter_client/client.py --- old/jupyter_client-4.1.1/jupyter_client/client.py 2015-09-16 17:15:03.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/client.py 2016-03-10 10:09:45.000000000 +0100 @@ -181,9 +181,14 @@ def is_alive(self): """Is the kernel process still running?""" + from .manager import KernelManager + if isinstance(self.parent, KernelManager): + # This KernelClient was created by a KernelManager, + # we can ask the parent KernelManager: + return self.parent.is_alive() if self._hb_channel is not None: - # We didn't start the kernel with this KernelManager so we - # use the heartbeat. + # We don't have access to the KernelManager, + # so we use the heartbeat. return self._hb_channel.is_beating() else: # no heartbeat and not local, we can't tell if it's running, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/connect.py new/jupyter_client-4.2.2/jupyter_client/connect.py --- old/jupyter_client-4.1.1/jupyter_client/connect.py 2015-09-12 17:22:09.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/connect.py 2016-03-06 10:43:14.000000000 +0100 @@ -33,7 +33,7 @@ def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0, control_port=0, ip='', key=b'', transport='tcp', - signature_scheme='hmac-sha256', + signature_scheme='hmac-sha256', kernel_name='' ): """Generates a JSON config file, including the selection of random ports. @@ -72,6 +72,8 @@ Currently, 'hmac' is the only supported digest scheme, and 'sha256' is the default hash function. + kernel_name : str, optional + The name of the kernel currently connected to. """ if not ip: ip = localhost() @@ -93,7 +95,7 @@ sock = socket.socket() # struct.pack('ii', (0,0)) is 8 null bytes sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8) - sock.bind(('', 0)) + sock.bind((ip, 0)) ports.append(sock) for i, sock in enumerate(ports): port = sock.getsockname()[1] @@ -127,6 +129,7 @@ cfg['key'] = bytes_to_str(key) cfg['transport'] = transport cfg['signature_scheme'] = signature_scheme + cfg['kernel_name'] = kernel_name with open(fname, 'w') as f: f.write(json.dumps(cfg, indent=2)) @@ -320,9 +323,22 @@ # Connection and ipc file management #-------------------------------------------------------------------------- - def get_connection_info(self): - """return the connection info as a dict""" - return dict( + def get_connection_info(self, session=False): + """Return the connection info as a dict + + Parameters + ---------- + session : bool [default: False] + If True, return our session object will be included in the connection info. + If False (default), the configuration parameters of our session object will be included, + rather than the session object itself. + + Returns + ------- + connect_info : dict + dictionary of connection information. + """ + info = dict( transport=self.transport, ip=self.ip, shell_port=self.shell_port, @@ -330,9 +346,17 @@ stdin_port=self.stdin_port, hb_port=self.hb_port, control_port=self.control_port, - signature_scheme=self.session.signature_scheme, - key=self.session.key, ) + if session: + # add session + info['session'] = self.session + else: + # add session info + info.update(dict( + signature_scheme=self.session.signature_scheme, + key=self.session.key, + )) + return info # factory for blocking clients blocking_class = Type(klass=object, default_value='jupyter_client.BlockingKernelClient') @@ -379,6 +403,7 @@ shell_port=self.shell_port, hb_port=self.hb_port, control_port=self.control_port, signature_scheme=self.session.signature_scheme, + kernel_name=self.kernel_name ) # write_connection_file also sets default ports: for name in port_names: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/consoleapp.py new/jupyter_client-4.2.2/jupyter_client/consoleapp.py --- old/jupyter_client-4.1.1/jupyter_client/consoleapp.py 2015-09-12 22:25:09.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/consoleapp.py 2016-03-06 10:43:14.000000000 +0100 @@ -24,6 +24,7 @@ from jupyter_core.application import base_flags, base_aliases from .blocking import BlockingKernelClient +from .restarter import KernelRestarter from . import KernelManager, tunnel_to_kernel, find_connection_file, connect from .kernelspec import NoSuchKernel from .session import Session @@ -47,11 +48,15 @@ } app_flags.update(boolean_flag( 'confirm-exit', 'JupyterConsoleApp.confirm_exit', - """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit', - to force a direct exit without any confirmation. + """Set to display confirmation dialog on exit. You can always use 'exit' or + 'quit', to force a direct exit without any confirmation. This can also + be set in the config file by setting + `c.JupyterConsoleApp.confirm_exit`. """, """Don't prompt the user when exiting. This will terminate the kernel if it is owned by the frontend, and leave it alive if it is external. + This can also be set in the config file by setting + `c.JupyterConsoleApp.confirm_exit`. """ )) flags.update(app_flags) @@ -80,7 +85,7 @@ # Classes #----------------------------------------------------------------------------- -classes = [KernelManager, Session] +classes = [KernelManager, KernelRestarter, Session] class JupyterConsoleApp(ConnectionFileMixin): name = 'jupyter-console-mixin' @@ -230,11 +235,11 @@ self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports cf = self.connection_file - base,ext = os.path.splitext(cf) - base = os.path.basename(base) - self.connection_file = os.path.basename(base)+'-ssh'+ext + root, ext = os.path.splitext(cf) + self.connection_file = root + '-ssh' + ext + self.write_connection_file() # write the new connection file self.log.info("To connect another client via this tunnel, use:") - self.log.info("--existing %s" % self.connection_file) + self.log.info("--existing %s" % os.path.basename(self.connection_file)) def _new_connection_file(self): cf = '' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/kernelspec.py new/jupyter_client-4.2.2/jupyter_client/kernelspec.py --- old/jupyter_client-4.1.1/jupyter_client/kernelspec.py 2015-10-08 10:15:39.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/kernelspec.py 2016-03-06 10:43:14.000000000 +0100 @@ -12,7 +12,7 @@ pjoin = os.path.join from ipython_genutils.py3compat import PY3 -from traitlets import HasTraits, List, Unicode, Dict, Set +from traitlets import HasTraits, List, Unicode, Dict, Set, Bool, Type from traitlets.config import LoggingConfigurable from jupyter_core.paths import jupyter_data_dir, jupyter_path, SYSTEM_JUPYTER_PATH @@ -72,7 +72,23 @@ def __init__(self, name): self.name = name + def __str__(self): + return "No such kernel named {}".format(self.name) + class KernelSpecManager(LoggingConfigurable): + + kernel_spec_class = Type(KernelSpec, config=True, + help="""The kernel spec class. This is configurable to allow + subclassing of the KernelSpecManager for customized behavior. + """ + ) + + ensure_native_kernel = Bool(True, config=True, + help="""If there is no Python kernelspec registered and the IPython + kernel is available, ensure it is added to the spec list. + """ + ) + data_dir = Unicode() def _data_dir_default(self): return jupyter_data_dir() @@ -116,7 +132,7 @@ self.log.debug("Found kernel %s in %s", kname, kernel_dir) d[kname] = spec - if NATIVE_KERNEL_NAME not in d: + if self.ensure_native_kernel and NATIVE_KERNEL_NAME not in d: try: from ipykernel.kernelspec import RESOURCES self.log.debug("Native kernel (%s) available from %s", @@ -131,6 +147,22 @@ return d # TODO: Caching? + def _get_kernel_spec_by_name(self, kernel_name, resource_dir): + """ Returns a :class:`KernelSpec` instance for a given kernel_name + and resource_dir. + """ + if kernel_name == NATIVE_KERNEL_NAME: + try: + from ipykernel.kernelspec import RESOURCES, get_kernel_dict + except ImportError: + # It should be impossible to reach this, but let's play it safe + pass + else: + if resource_dir == RESOURCES: + return self.kernel_spec_class(resource_dir=resource_dir, **get_kernel_dict()) + + return self.kernel_spec_class.from_resource_dir(resource_dir) + def get_kernel_spec(self, kernel_name): """Returns a :class:`KernelSpec` instance for the given kernel_name. @@ -142,17 +174,45 @@ except KeyError: raise NoSuchKernel(kernel_name) - if kernel_name == NATIVE_KERNEL_NAME: - try: - from ipykernel.kernelspec import RESOURCES, get_kernel_dict - except ImportError: - # It should be impossible to reach this, but let's play it safe - pass - else: - if resource_dir == RESOURCES: - return KernelSpec(resource_dir=resource_dir, **get_kernel_dict()) + return self._get_kernel_spec_by_name(kernel_name, resource_dir) - return KernelSpec.from_resource_dir(resource_dir) + def get_all_specs(self): + """Returns a dict mapping kernel names to kernelspecs. + + Returns a dict of the form:: + + { + 'kernel_name': { + 'resource_dir': '/path/to/kernel_name', + 'spec': {"the spec itself": ...} + }, + ... + } + """ + 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} + + 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 + try: + self.ensure_native_kernel = False + specs = self.find_kernel_specs() + finally: + self.ensure_native_kernel = save_native + spec_dir = specs[name] + self.log.debug("Removing %s", spec_dir) + if os.path.islink(spec_dir): + os.remove(spec_dir) + else: + shutil.rmtree(spec_dir) + return spec_dir def _get_destination_dir(self, kernel_name, user=False, prefix=None): if user: @@ -178,6 +238,7 @@ PREFIX/share/jupyter/kernels/KERNEL_NAME. This can be sys.prefix for installation inside virtual or conda envs. """ + source_dir = source_dir.rstrip('/\\') if not kernel_name: kernel_name = os.path.basename(source_dir) kernel_name = kernel_name.lower() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/kernelspecapp.py new/jupyter_client-4.2.2/jupyter_client/kernelspecapp.py --- old/jupyter_client-4.1.1/jupyter_client/kernelspecapp.py 2015-10-08 11:14:48.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/kernelspecapp.py 2016-03-06 10:38:36.000000000 +0100 @@ -13,11 +13,17 @@ from jupyter_core.application import ( JupyterApp, base_flags, base_aliases ) -from traitlets import Instance, Dict, Unicode, Bool +from traitlets import Instance, Dict, Unicode, Bool, List from . import __version__ from .kernelspec import KernelSpecManager +try: + raw_input +except NameError: + # py3 + raw_input = input + class ListKernelSpecs(JupyterApp): version = __version__ description = """List installed kernel specifications.""" @@ -35,10 +41,7 @@ def start(self): paths = self.kernel_spec_manager.find_kernel_specs() - specs = {kname: { - "resources_dir": paths[kname], - "spec": self.kernel_spec_manager.get_kernel_spec(kname).to_dict() - } for kname in paths} + specs = self.kernel_spec_manager.get_all_specs() if not self.json_output: if not specs: print("No kernels available") @@ -148,6 +151,61 @@ self.exit(1) raise +class RemoveKernelSpec(JupyterApp): + version = __version__ + description = """Remove one or more Jupyter kernelspecs by name.""" + examples = """jupyter kernelspec remove python2 [my_kernel ...]""" + + force = Bool(False, config=True, + help="""Force removal, don't prompt for confirmation.""" + ) + spec_names = List(Unicode()) + + kernel_spec_manager = Instance(KernelSpecManager) + def _kernel_spec_manager_default(self): + return KernelSpecManager(data_dir=self.data_dir, parent=self) + + flags = { + 'f': ({'RemoveKernelSpec': {'force': True}}, force.get_metadata('help')), + } + flags.update(JupyterApp.flags) + + def parse_command_line(self, argv): + super(RemoveKernelSpec, self).parse_command_line(argv) + # accept positional arg as profile name + if self.extra_args: + self.spec_names = sorted(set(self.extra_args)) # remove duplicates + else: + self.exit("No kernelspec specified.") + + def start(self): + self.kernel_spec_manager.ensure_native_kernel = False + spec_paths = self.kernel_spec_manager.find_kernel_specs() + missing = set(self.spec_names).difference(set(spec_paths)) + if missing: + self.exit("Couldn't find kernel spec(s): %s" % ', '.join(missing)) + + if not self.force: + print("Kernel specs to remove:") + for name in self.spec_names: + print(" %s\t%s" % (name.ljust(20), spec_paths[name])) + answer = raw_input("Remove %i kernel specs [y/N]: " % len(self.spec_names)) + if not answer.lower().startswith('y'): + return + + for kernel_name in self.spec_names: + try: + path = self.kernel_spec_manager.remove_kernel_spec(kernel_name) + except OSError as e: + if e.errno == errno.EACCES: + print(e, file=sys.stderr) + print("Perhaps you want sudo?", file=sys.stderr) + self.exit(1) + else: + raise + self.log.info("Removed %s", path) + + class InstallNativeKernelSpec(JupyterApp): version = __version__ description = """[DEPRECATED] Install the IPython kernel spec directory for this Python.""" @@ -192,6 +250,8 @@ subcommands = Dict({ 'list': (ListKernelSpecs, ListKernelSpecs.description.splitlines()[0]), 'install': (InstallKernelSpec, InstallKernelSpec.description.splitlines()[0]), + 'uninstall': (RemoveKernelSpec, "Alias for remove"), + 'remove': (RemoveKernelSpec, RemoveKernelSpec.description.splitlines()[0]), 'install-self': (InstallNativeKernelSpec, InstallNativeKernelSpec.description.splitlines()[0]), }) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/launcher.py new/jupyter_client-4.2.2/jupyter_client/launcher.py --- old/jupyter_client-4.1.1/jupyter_client/launcher.py 2015-09-17 13:35:34.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/launcher.py 2016-03-06 10:39:27.000000000 +0100 @@ -8,7 +8,8 @@ from subprocess import Popen, PIPE from ipython_genutils.encoding import getdefaultencoding -from ipython_genutils.py3compat import cast_bytes_py2 +from ipython_genutils.py3compat import cast_bytes_py2, PY3 +from traitlets.log import get_logger def launch_kernel(cmd, stdin=None, stdout=None, stderr=None, env=None, @@ -105,22 +106,33 @@ 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() + # Create a new session. + # This makes it easier to interrupt the kernel, + # because we want to interrupt the whole process group. + # We don't use setpgrp, which is known to cause problems for kernels starting + # certain interactive subprocesses, such as bash -i. + if PY3: + kwargs['start_new_session'] = True else: - # Create a new process group. This makes it easier to - # interrupt the kernel, because we want to interrupt the - # children of the kernel process also. - kwargs['preexec_fn'] = lambda: os.setpgrp() + kwargs['preexec_fn'] = lambda: os.setsid() + if not independent: env['JPY_PARENT_PID'] = str(os.getpid()) + try: proc = Popen(cmd, **kwargs) + except Exception as exc: + msg = ( + "Failed to run command:\n{}\n" + "with kwargs:\n{!r}\n" + ) + msg = msg.format(cmd, kwargs) + get_logger().error(msg) + raise + + if sys.platform == 'win32': + # Attach the interrupt event to the Popen objet so it can be used later. + proc.win32_interrupt_event = interrupt_event # Clean up pipes created to work around Popen bug. if redirect_in: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/localinterfaces.py new/jupyter_client-4.2.2/jupyter_client/localinterfaces.py --- old/jupyter_client-4.1.1/jupyter_client/localinterfaces.py 2015-06-05 23:46:18.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/localinterfaces.py 2016-03-06 10:38:36.000000000 +0100 @@ -6,6 +6,7 @@ import os import re import socket +import subprocess from subprocess import Popen, PIPE from warnings import warn @@ -30,7 +31,11 @@ def _get_output(cmd): """Get output of a command, raising IOError if it fails""" - p = Popen(cmd, stdout=PIPE, stderr=PIPE) + startupinfo = None + if os.name == 'nt': + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + p = Popen(cmd, stdout=PIPE, stderr=PIPE, startupinfo=startupinfo) stdout, stderr = p.communicate() if p.returncode: raise IOError("Failed to run %s: %s" % (cmd, stderr.decode('utf8', 'replace'))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/manager.py new/jupyter_client-4.2.2/jupyter_client/manager.py --- old/jupyter_client-4.1.1/jupyter_client/manager.py 2015-09-17 13:35:34.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/manager.py 2016-03-09 13:13:37.000000000 +0100 @@ -110,7 +110,7 @@ _restarter = Any() - autorestart = Bool(False, config=True, + autorestart = Bool(True, config=True, help="""Should we autorestart the kernel if it dies.""" ) @@ -147,10 +147,9 @@ def client(self, **kwargs): """Create a client configured to connect to our kernel""" kw = {} - kw.update(self.get_connection_info()) + kw.update(self.get_connection_info(session=True)) kw.update(dict( connection_file=self.connection_file, - session=self.session, parent=self, )) @@ -229,7 +228,7 @@ # build the Popen cmd extra_arguments = kw.pop('extra_arguments', []) kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments) - env = os.environ.copy() + env = kw.pop('env', os.environ).copy() # Don't allow PYTHONEXECUTABLE to be passed to kernel process. # If set, it can bork all the things. env.pop('PYTHONEXECUTABLE', None) @@ -430,7 +429,12 @@ km.start_kernel(**kwargs) kc = km.client() kc.start_channels() - kc.wait_for_ready() + try: + kc.wait_for_ready(timeout=startup_timeout) + except RuntimeError: + kc.stop_channels() + km.shutdown_kernel() + raise return km, kc diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/multikernelmanager.py new/jupyter_client-4.2.2/jupyter_client/multikernelmanager.py --- old/jupyter_client-4.1.1/jupyter_client/multikernelmanager.py 2015-05-27 04:19:02.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/multikernelmanager.py 2016-03-06 10:43:14.000000000 +0100 @@ -99,12 +99,13 @@ # kernel_manager_factory is the constructor for the KernelManager # subclass we are using. It can be configured as any Configurable, # including things like its transport and ip. + constructor_kwargs = {} if self.kernel_spec_manager: - kwargs['kernel_spec_manager'] = self.kernel_spec_manager + constructor_kwargs['kernel_spec_manager'] = self.kernel_spec_manager km = self.kernel_manager_factory(connection_file=os.path.join( self.connection_dir, "kernel-%s.json" % kernel_id), - parent=self, autorestart=True, log=self.log, kernel_name=kernel_name, - **kwargs + parent=self, log=self.log, kernel_name=kernel_name, + **constructor_kwargs ) km.start_kernel(**kwargs) self._kernels[kernel_id] = km diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/session.py new/jupyter_client-4.2.2/jupyter_client/session.py --- old/jupyter_client-4.1.1/jupyter_client/session.py 2015-09-03 11:35:14.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/session.py 2016-03-06 10:43:14.000000000 +0100 @@ -268,7 +268,13 @@ """ - debug=Bool(False, config=True, help="""Debug output in the Session""") + debug = Bool(False, config=True, help="""Debug output in the Session""") + + check_pid = Bool(True, config=True, + help="""Whether to check PID to protect against calls after fork. + + This check can be disabled if fork-safety is handled elsewhere. + """) packer = DottedObjectName('json',config=True, help="""The name of the packer for serializing messages. @@ -448,6 +454,8 @@ self.session self.pid = os.getpid() self._new_auth() + if not self.key: + get_logger().warning("Message signing is disabled. This is insecure and not recommended!") @property def msg_id(self): @@ -654,8 +662,8 @@ else: msg = self.msg(msg_or_type, content=content, parent=parent, header=header, metadata=metadata) - if not os.getpid() == self.pid: - get_logger().warn("WARNING: attempted to send message from fork\n%s", + if self.check_pid and not os.getpid() == self.pid: + get_logger().warning("WARNING: attempted to send message from fork\n%s", msg ) return @@ -843,7 +851,9 @@ raise ValueError("Unsigned Message") if signature in self.digest_history: raise ValueError("Duplicate Signature: %r" % signature) - self._add_digest(signature) + if content: + # Only store signature if we are unpacking content, don't store if just peeking. + self._add_digest(signature) check = self.sign(msg_list[1:5]) if not compare_digest(signature, check): raise ValueError("Invalid Signature: %r" % signature) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/tests/signalkernel.py new/jupyter_client-4.2.2/jupyter_client/tests/signalkernel.py --- old/jupyter_client-4.1.1/jupyter_client/tests/signalkernel.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jupyter_client-4.2.2/jupyter_client/tests/signalkernel.py 2016-03-10 10:09:45.000000000 +0100 @@ -0,0 +1,70 @@ +"""Test kernel for signalling subprocesses""" + +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +from __future__ import print_function + +from subprocess import Popen, PIPE +import sys +import time + +from ipykernel.kernelbase import Kernel +from ipykernel.kernelapp import IPKernelApp + + +class SignalTestKernel(Kernel): + """Kernel for testing subprocess signaling""" + implementation = 'signaltest' + implementation_version = '0.0' + banner = '' + + def __init__(self, **kwargs): + kwargs.pop('user_ns', None) + super(SignalTestKernel, self).__init__(**kwargs) + self.children = [] + + def do_execute(self, code, silent, store_history=True, user_expressions=None, + allow_stdin=False): + code = code.strip() + reply = { + 'status': 'ok', + 'user_expressions': {}, + } + if code == 'start': + child = Popen(['bash', '-i', '-c', 'sleep 30'], stderr=PIPE) + self.children.append(child) + reply['user_expressions']['pid'] = self.children[-1].pid + elif code == 'check': + reply['user_expressions']['poll'] = [ child.poll() for child in self.children ] + elif code == 'sleep': + try: + time.sleep(10) + except KeyboardInterrupt: + reply['user_expressions']['interrupted'] = True + else: + reply['user_expressions']['interrupted'] = False + else: + reply['status'] = 'error' + reply['ename'] = 'Error' + reply['evalue'] = code + reply['traceback'] = ['no such command: %s' % code] + return reply + + def kernel_info_request(self, *args, **kwargs): + """Add delay to kernel_info_request + + triggers slow-response code in KernelClient.wait_for_ready + """ + return super(SignalTestKernel, self).kernel_info_request(*args, **kwargs) + +class SignalTestApp(IPKernelApp): + kernel_class = SignalTestKernel + def init_io(self): + pass # disable stdout/stderr capture + +if __name__ == '__main__': + # make startup artificially slow, + # so that we exercise client logic for slow-starting kernels + time.sleep(2) + SignalTestApp.launch_instance() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/tests/test_connect.py new/jupyter_client-4.2.2/jupyter_client/tests/test_connect.py --- old/jupyter_client-4.1.1/jupyter_client/tests/test_connect.py 2015-04-09 08:44:50.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/tests/test_connect.py 2016-03-06 10:38:36.000000000 +0100 @@ -24,7 +24,12 @@ sample_info = dict(ip='1.2.3.4', transport='ipc', shell_port=1, hb_port=2, iopub_port=3, stdin_port=4, control_port=5, - key=b'abc123', signature_scheme='hmac-md5', + key=b'abc123', signature_scheme='hmac-md5', kernel_name='python' + ) + +sample_info_kn = dict(ip='1.2.3.4', transport='ipc', + shell_port=1, hb_port=2, iopub_port=3, stdin_port=4, control_port=5, + key=b'abc123', signature_scheme='hmac-md5', kernel_name='test' ) def test_write_connection_file(): @@ -55,6 +60,23 @@ nt.assert_equal(session.signature_scheme, sample_info['signature_scheme']) +def test_load_connection_file_session_with_kn(): + """test load_connection_file() after """ + session = Session() + app = DummyConsoleApp(session=Session()) + app.initialize(argv=[]) + session = app.session + + with TemporaryDirectory() as d: + cf = os.path.join(d, 'kernel.json') + connect.write_connection_file(cf, **sample_info_kn) + app.connection_file = cf + app.load_connection_file() + + nt.assert_equal(session.key, sample_info_kn['key']) + nt.assert_equal(session.signature_scheme, sample_info_kn['signature_scheme']) + + def test_app_load_connection_file(): """test `ipython console --existing` loads a connection file""" with TemporaryDirectory() as d: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/tests/test_kernelmanager.py new/jupyter_client-4.2.2/jupyter_client/tests/test_kernelmanager.py --- old/jupyter_client-4.1.1/jupyter_client/tests/test_kernelmanager.py 2015-04-09 08:20:10.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/tests/test_kernelmanager.py 2016-03-06 17:04:05.000000000 +0100 @@ -1,15 +1,46 @@ -"""Tests for the notebook kernel and session manager""" +"""Tests for the KernelManager""" +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + + +import json +import os +pjoin = os.path.join +import signal from subprocess import PIPE +import sys import time from unittest import TestCase from ipython_genutils.testing import decorators as dec from traitlets.config.loader import Config +from jupyter_core import paths from jupyter_client import KernelManager +from ..manager import start_new_kernel +from .utils import test_env + +TIMEOUT = 30 class TestKernelManager(TestCase): + def setUp(self): + self.env_patch = test_env() + self.env_patch.start() + + def tearDown(self): + self.env_patch.stop() + + def _install_test_kernel(self): + kernel_dir = pjoin(paths.jupyter_data_dir(), 'kernels', 'signaltest') + os.makedirs(kernel_dir) + with open(pjoin(kernel_dir, 'kernel.json'), 'w') as f: + f.write(json.dumps({ + 'argv': [sys.executable, + '-m', 'jupyter_client.tests.signalkernel', + '-f', '{connection_file}'], + 'display_name': "Signal Test Kernel", + })) def _get_tcp_km(self): c = Config() @@ -51,3 +82,50 @@ 'key', 'signature_scheme', ]) self.assertEqual(keys, expected) + + @dec.skip_win32 + def test_signal_kernel_subprocesses(self): + self._install_test_kernel() + km, kc = start_new_kernel(kernel_name='signaltest') + def execute(cmd): + kc.execute(cmd) + reply = kc.get_shell_msg(TIMEOUT) + content = reply['content'] + self.assertEqual(content['status'], 'ok') + return content + + self.addCleanup(kc.stop_channels) + self.addCleanup(km.shutdown_kernel) + N = 5 + for i in range(N): + execute("start") + time.sleep(1) # make sure subprocs stay up + reply = execute('check') + self.assertEqual(reply['user_expressions']['poll'], [None] * N) + + # start a job on the kernel to be interrupted + kc.execute('sleep') + time.sleep(1) # ensure sleep message has been handled before we interrupt + km.interrupt_kernel() + reply = kc.get_shell_msg(TIMEOUT) + content = reply['content'] + self.assertEqual(content['status'], 'ok') + self.assertEqual(content['user_expressions']['interrupted'], True) + # wait up to 5s for subprocesses to handle signal + for i in range(50): + reply = execute('check') + if reply['user_expressions']['poll'] != [-signal.SIGINT] * N: + time.sleep(0.1) + else: + break + # verify that subprocesses were interrupted + self.assertEqual(reply['user_expressions']['poll'], [-signal.SIGINT] * N) + + def test_start_new_kernel(self): + self._install_test_kernel() + km, kc = start_new_kernel(kernel_name='signaltest') + self.addCleanup(kc.stop_channels) + self.addCleanup(km.shutdown_kernel) + + self.assertTrue(km.is_alive()) + self.assertTrue(kc.is_alive()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/tests/test_kernelspec.py new/jupyter_client-4.2.2/jupyter_client/tests/test_kernelspec.py --- old/jupyter_client-4.1.1/jupyter_client/tests/test_kernelspec.py 2015-10-08 10:15:39.000000000 +0200 +++ new/jupyter_client-4.2.2/jupyter_client/tests/test_kernelspec.py 2016-03-06 10:39:27.000000000 +0100 @@ -1,15 +1,17 @@ +"""Tests for the KernelSpecManager""" + +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + import io import json from logging import StreamHandler import os from os.path import join as pjoin +from subprocess import Popen, PIPE, STDOUT +import sys import unittest -try: - from unittest.mock import patch -except ImportError: - from mock import patch - if str is bytes: # py2 StringIO = io.BytesIO else: @@ -18,6 +20,8 @@ from ipython_genutils.testing.decorators import onlyif from ipython_genutils.tempdir import TemporaryDirectory from jupyter_client import kernelspec +from jupyter_core import paths +from .utils import test_env sample_kernel_json = {'argv':['cat', '{connection_file}'], 'display_name':'Test kernel', @@ -27,7 +31,7 @@ def _install_sample_kernel(self, kernels_dir): """install a sample kernel in a kernels directory""" - sample_kernel_dir = pjoin(kernels_dir, 'Sample') + sample_kernel_dir = pjoin(kernels_dir, 'sample') os.makedirs(sample_kernel_dir) json_file = pjoin(sample_kernel_dir, 'kernel.json') with open(json_file, 'w') as f: @@ -35,16 +39,10 @@ return sample_kernel_dir def setUp(self): - td = TemporaryDirectory() - self.env_patch = patch.dict(os.environ, { - 'JUPYTER_CONFIG_DIR': pjoin(td.name, 'jupyter'), - 'JUPYTER_DATA_DIR': pjoin(td.name, 'jupyter_data'), - 'JUPYTER_RUNTIME_DIR': pjoin(td.name, 'jupyter_runtime'), - }) + self.env_patch = test_env() self.env_patch.start() - self.addCleanup(td.cleanup) self.sample_kernel_dir = self._install_sample_kernel( - pjoin(td.name, 'jupyter_data', 'kernels')) + pjoin(paths.jupyter_data_dir(), 'kernels')) self.ksm = kernelspec.KernelSpecManager() @@ -67,6 +65,11 @@ self.assertEqual(ks.argv, sample_kernel_json['argv']) self.assertEqual(ks.display_name, sample_kernel_json['display_name']) self.assertEqual(ks.env, {}) + + def test_find_all_specs(self): + kernels = self.ksm.get_all_specs() + self.assertEqual(kernels['sample']['resource_dir'], self.sample_kernel_dir) + self.assertIsNotNone(kernels['sample']['spec']) def test_kernel_spec_priority(self): td = TemporaryDirectory() @@ -125,3 +128,16 @@ self.ksm.install_kernel_spec(self.installable_kernel, kernel_name='tstinstalled', user=False) + + def test_remove_kernel_spec(self): + path = self.ksm.remove_kernel_spec('sample') + self.assertEqual(path, self.sample_kernel_dir) + + def test_remove_kernel_spec_app(self): + p = Popen( + [sys.executable, '-m', 'jupyter_client.kernelspecapp', 'remove', 'sample', '-f'], + stdout=PIPE, stderr=STDOUT, + env=os.environ, + ) + out, _ = p.communicate() + self.assertEqual(p.returncode, 0, out.decode('utf8', 'replace')) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_client-4.1.1/jupyter_client/tests/utils.py new/jupyter_client-4.2.2/jupyter_client/tests/utils.py --- old/jupyter_client-4.1.1/jupyter_client/tests/utils.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jupyter_client-4.2.2/jupyter_client/tests/utils.py 2016-03-06 10:39:27.000000000 +0100 @@ -0,0 +1,56 @@ +"""Testing utils for jupyter_client tests + +""" +import os +pjoin = os.path.join +try: + from unittest.mock import patch +except ImportError: + from mock import patch + +from ipython_genutils.tempdir import TemporaryDirectory + +class test_env(object): + """Set Jupyter path variables to a temporary directory + + Useful as a context manager or with explicit start/stop + """ + def start(self): + self.test_dir = td = TemporaryDirectory() + self.env_patch = patch.dict(os.environ, { + 'JUPYTER_CONFIG_DIR': pjoin(td.name, 'jupyter'), + 'JUPYTER_DATA_DIR': pjoin(td.name, 'jupyter_data'), + 'JUPYTER_RUNTIME_DIR': pjoin(td.name, 'jupyter_runtime'), + 'IPYTHONDIR': pjoin(td.name, 'ipython'), + }) + self.env_patch.start() + + def stop(self): + self.env_patch.stop() + self.test_dir.cleanup() + + def __enter__(self): + self.start() + return self.test_dir.name + + def __exit__(self, *exc_info): + self.stop() + +def execute(code='', kc=None, **kwargs): + """wrapper for doing common steps for validating an execution request""" + from .test_message_spec import validate_message + if kc is None: + kc = KC + msg_id = kc.execute(code=code, **kwargs) + reply = kc.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'execute_reply', msg_id) + busy = kc.get_iopub_msg(timeout=TIMEOUT) + validate_message(busy, 'status', msg_id) + nt.assert_equal(busy['content']['execution_state'], 'busy') + + if not kwargs.get('silent'): + execute_input = kc.get_iopub_msg(timeout=TIMEOUT) + validate_message(execute_input, 'execute_input', msg_id) + nt.assert_equal(execute_input['content']['code'], code) + + return msg_id, reply['content']
