[Python-modules-commits] [jupyter-client] 01/05: Import jupyter-client_5.1.0.orig.tar.gz

Gordon Ball chronitis-guest at moszumanska.debian.org
Fri Jun 23 11:52:04 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 79f89b801f8fe1112867307413a9c82103d30ae9
Author: Gordon Ball <gordon at chronitis.net>
Date:   Fri Jun 23 13:32:50 2017 +0200

    Import jupyter-client_5.1.0.orig.tar.gz
---
 .travis.yml                                     | 12 ++---
 docs/changelog.rst                              | 20 ++++++++
 docs/environment.yml                            |  1 -
 docs/messaging.rst                              | 59 ++++++++++++++++++++--
 jupyter_client/_version.py                      |  4 +-
 jupyter_client/blocking/client.py               |  5 ++
 jupyter_client/channels.py                      |  7 +--
 jupyter_client/connect.py                       |  6 ++-
 jupyter_client/consoleapp.py                    |  2 +-
 jupyter_client/manager.py                       |  2 +-
 jupyter_client/session.py                       | 18 +++++++
 jupyter_client/tests/test_adapter.py            |  9 ++--
 jupyter_client/tests/test_client.py             |  6 +--
 jupyter_client/tests/test_connect.py            | 65 +++++++++++++++++++------
 jupyter_client/tests/test_jsonutil.py           | 32 ++++++------
 jupyter_client/tests/test_kernelmanager.py      | 10 ++--
 jupyter_client/tests/test_kernelspec.py         |  7 ++-
 jupyter_client/tests/test_multikernelmanager.py |  7 ++-
 jupyter_client/tests/test_public_api.py         | 10 ++--
 jupyter_client/tests/test_session.py            | 18 +++++--
 jupyter_client/tests/utils.py                   | 11 ++++-
 jupyter_client/threaded.py                      |  9 ++--
 setup.py                                        |  2 +-
 23 files changed, 234 insertions(+), 88 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index edb33b1..0a3a969 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,17 +7,13 @@ python:
     - 3.3
     - 2.7
 sudo: false
-before_install:
-    - git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels
-    
 install:
-    - pip install -U setuptools
-    - pip install -f travis-wheels/wheelhouse -e .[test] codecov
-    - python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)'
+  - pip install --upgrade setuptools pip
+  - pip install --upgrade --pre -e .[test] pytest-cov pytest-warnings codecov
 script:
-    - nosetests -v --with-coverage --cover-package jupyter_client jupyter_client
+  - py.test --cov jupyter_client jupyter_client
 after_success:
-    - codecov
+  - codecov
 matrix:
     allow_failures:
         - python: nightly
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 1220f57..35e21b5 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,6 +4,26 @@
 Changes in Jupyter Client
 =========================
 
+5.1
+===
+
+`5.1 on GitHub <https://github.com/jupyter/jupyter_client/milestones/5.1>`__
+
+- Define Jupyter protocol version 5.2,
+  resolving ambiguity of ``cursor_pos`` field in the presence
+  of unicode surrogate pairs.
+  
+  .. seealso::
+  
+      :ref:`cursor_pos_unicode_note`
+
+- Add :meth:`Session.clone` for making a copy of a Session object
+  without sharing the digest history.
+  Reusing a single Session object to connect multiple sockets
+  to the same IOPub peer can cause digest collisions.
+- Avoid global references preventing garbage collection of background threads.
+
+
 5.0
 ===
 
diff --git a/docs/environment.yml b/docs/environment.yml
index 9240fbc..3690c73 100644
--- a/docs/environment.yml
+++ b/docs/environment.yml
@@ -8,4 +8,3 @@ dependencies:
 - jupyter_core
 - sphinx>=1.3.6
 - sphinx_rtd_theme
-- ipykernel
diff --git a/docs/messaging.rst b/docs/messaging.rst
index efe73f2..642b6b0 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.1.
+The current version of the specification is 5.2.
 
 .. note::
    *New in* and *Changed in* messages in this document refer to versions of the
@@ -547,6 +547,14 @@ Message type: ``inspect_request``::
     ``name`` key replaced with ``code`` and ``cursor_pos``,
     moving the lexing responsibility to the kernel.
 
+.. versionchanged:: 5.2
+
+    Due to a widespread bug in many frontends, ``cursor_pos``
+    in versions prior to 5.2 is ambiguous in the presence of "astral-plane" characters.
+    In 5.2, cursor_pos **must be** the actual encoding-independent offset in unicode codepoints.
+    See :ref:`cursor_pos_unicode_note` for more.
+
+
 The reply is a mime-bundle, like a `display_data`_ message,
 which should be a formatted representation of information about the context.
 In the notebook, this is used to show tooltips over function calls, etc.
@@ -595,6 +603,13 @@ Message type: ``complete_request``::
     ``line``, ``block``, and ``text`` keys are removed in favor of a single ``code`` for context.
     Lexing is up to the kernel.
 
+.. versionchanged:: 5.2
+
+    Due to a widespread bug in many frontends, ``cursor_pos``
+    in versions prior to 5.2 is ambiguous in the presence of "astral-plane" characters.
+    In 5.2, cursor_pos **must be** the actual encoding-independent offset in unicode codepoints.
+    See :ref:`cursor_pos_unicode_note` for more.
+
 
 Message type: ``complete_reply``::
 
@@ -1370,12 +1385,48 @@ handlers should set the parent header and publish status busy / idle,
 just like an execute request.
 
 
-To Do
+Notes
 =====
 
-Missing things include:
+.. _cursor_pos_unicode_note:
+
+``cursor_pos`` and unicode offsets
+----------------------------------
+
+Many frontends, especially those implemented in javascript,
+reported cursor_pos as the interpreter's string index,
+which is not the same as the unicode character offset if the interpreter uses UTF-16 (e.g. javascript or Python 2 on macOS),
+which stores "astral-plane" characters such as ``𝐚 (U+1D41A)`` as surrogate pairs,
+taking up two indices instead of one, causing a unicode offset
+drift of one per astral-plane character.
+Not all frontends have this behavior, however,
+and after JSON serialization information about which encoding was used
+when calculating the offset is lost,
+so assuming ``cursor_pos`` is calculated in UTF-16 could result in a similarly incorrect offset
+for frontends that did the right thing.
+
+For this reason, in protocol versions prior to 5.2, ``cursor_pos``
+is officially ambiguous in the presence of astral plane unicode characters.
+Frontends claiming to implement protocol 5.2 **MUST** identify cursor_pos as the encoding-independent unicode character offset.
+Kernels may choose to expect the UTF-16 offset from requests implementing protocol 5.1 and earlier, in order to behave correctly with the most popular frontends.
+But they should know that doing so *introduces* the inverse bug for the frontends that do not have this bug.
+
+Known affected frontends (as of 2017-06):
+
+- Jupyter Notebook < 5.1
+- JupyterLab < 0.24
+- nteract
+- CoCalc
+- Jupyter Console and QtConsole with Python 2 on macOS and Windows
+
+Known *not* affected frontends:
+
+- QtConsole, Jupyter Console with Python 3 or Python 2 on Linux
+
+.. see-also::
+
+    `Discussion on GitHub <https://github.com/jupyter/jupyter_client/issues/259>`_
 
-* Important: finish thinking through the payload concept and API.
 
 .. _ZeroMQ: http://zeromq.org
 .. _nteract: https://nteract.io
diff --git a/jupyter_client/_version.py b/jupyter_client/_version.py
index 81f3781..90dd2e9 100644
--- a/jupyter_client/_version.py
+++ b/jupyter_client/_version.py
@@ -1,5 +1,5 @@
-version_info = (5, 0, 1)
+version_info = (5, 1, 0)
 __version__ = '.'.join(map(str, version_info))
 
-protocol_version_info = (5, 1)
+protocol_version_info = (5, 2)
 protocol_version = "%i.%i" % protocol_version_info
diff --git a/jupyter_client/blocking/client.py b/jupyter_client/blocking/client.py
index e0c688a..c0196ba 100644
--- a/jupyter_client/blocking/client.py
+++ b/jupyter_client/blocking/client.py
@@ -46,6 +46,11 @@ def reqrep(meth):
 
         return self._recv_reply(msg_id, timeout=timeout)
     
+    if not meth.__doc__:
+        # python -OO removes docstrings,
+        # so don't bother building the wrapped docstring
+        return wrapped
+    
     basedoc, _ = meth.__doc__.split('Returns\n', 1)
     parts = [basedoc.strip()]
     if 'Parameters' not in basedoc:
diff --git a/jupyter_client/channels.py b/jupyter_client/channels.py
index d7b0836..dd99067 100644
--- a/jupyter_client/channels.py
+++ b/jupyter_client/channels.py
@@ -70,7 +70,6 @@ class HBChannel(Thread):
                 raise InvalidPortNumber(message)
             address = "tcp://%s:%i" % address
         self.address = address
-        atexit.register(self._notice_exit)
 
         # running is False until `.start()` is called
         self._running = False
@@ -78,8 +77,10 @@ class HBChannel(Thread):
         self._pause = False
         self.poller = zmq.Poller()
 
-    def _notice_exit(self):
-        self._exiting = True
+    @staticmethod
+    @atexit.register
+    def _notice_exit():
+        HBChannel._exiting = True
 
     def _create_socket(self):
         if self.socket is not None:
diff --git a/jupyter_client/connect.py b/jupyter_client/connect.py
index 0ee55b1..042904f 100644
--- a/jupyter_client/connect.py
+++ b/jupyter_client/connect.py
@@ -207,6 +207,7 @@ def find_connection_file(filename='kernel-*.json', path=None, profile=None):
     for p in path:
         matches.extend(glob.glob(os.path.join(p, pat)))
     
+    matches = [ os.path.abspath(m) for m in matches ]
     if not matches:
         raise IOError("Could not find %r in %r" % (filename, path))
     elif len(matches) == 1:
@@ -371,8 +372,9 @@ class ConnectionFileMixin(LoggingConfigurable):
             control_port=self.control_port,
         )
         if session:
-            # add session
-            info['session'] = self.session
+            # add *clone* of my session,
+            # so that state such as digest_history is not shared.
+            info['session'] = self.session.clone()
         else:
             # add session info
             info.update(dict(
diff --git a/jupyter_client/consoleapp.py b/jupyter_client/consoleapp.py
index 2c57840..ce2ead4 100644
--- a/jupyter_client/consoleapp.py
+++ b/jupyter_client/consoleapp.py
@@ -161,7 +161,7 @@ class JupyterConsoleApp(ConnectionFileMixin):
         """
         if self.existing:
             try:
-                cf = find_connection_file(self.existing, [self.runtime_dir])
+                cf = find_connection_file(self.existing, ['.', self.runtime_dir])
             except Exception:
                 self.log.critical("Could not find existing kernel connection file %s", self.existing)
                 self.exit(1)
diff --git a/jupyter_client/manager.py b/jupyter_client/manager.py
index 54a8520..373105e 100644
--- a/jupyter_client/manager.py
+++ b/jupyter_client/manager.py
@@ -295,7 +295,7 @@ class KernelManager(ConnectionFileMixin):
         self._close_control_socket()
 
     def shutdown_kernel(self, now=False, restart=False):
-        """Attempts to the stop the kernel process cleanly.
+        """Attempts to stop the kernel process cleanly.
 
         This attempts to shutdown the kernels cleanly by:
 
diff --git a/jupyter_client/session.py b/jupyter_client/session.py
index eedaeeb..b172b63 100644
--- a/jupyter_client/session.py
+++ b/jupyter_client/session.py
@@ -488,6 +488,24 @@ class Session(Configurable):
         if not self.key:
             get_logger().warning("Message signing is disabled.  This is insecure and not recommended!")
 
+    def clone(self):
+        """Create a copy of this Session
+
+        Useful when connecting multiple times to a given kernel.
+        This prevents a shared digest_history warning about duplicate digests
+        due to multiple connections to IOPub in the same process.
+
+        .. versionadded:: 5.1
+        """
+        # make a copy
+        new_session = type(self)()
+        for name in self.traits():
+            setattr(new_session, name, getattr(self, name))
+        # fork digest_history
+        new_session.digest_history = set()
+        new_session.digest_history.update(self.digest_history)
+        return new_session
+
     @property
     def msg_id(self):
         """always return new uuid"""
diff --git a/jupyter_client/tests/test_adapter.py b/jupyter_client/tests/test_adapter.py
index ae7791c..dae0207 100644
--- a/jupyter_client/tests/test_adapter.py
+++ b/jupyter_client/tests/test_adapter.py
@@ -6,7 +6,6 @@
 import copy
 import json
 from unittest import TestCase
-import nose.tools as nt
 
 from jupyter_client.adapter import adapt, V4toV5, V5toV4, code_to_line
 from jupyter_client.session import Session
@@ -18,12 +17,12 @@ def test_default_version():
     msg['header'].pop('version')
     original = copy.deepcopy(msg)
     adapted = adapt(original)
-    nt.assert_equal(adapted['header']['version'], V4toV5.version)
+    assert adapted['header']['version'] == V4toV5.version
 
 def test_code_to_line_no_code():
     line, pos = code_to_line("", 0)
-    nt.assert_equal(line, "")
-    nt.assert_equal(pos, 0)
+    assert line == ""
+    assert pos == 0
 
 class AdapterTest(TestCase):
 
@@ -263,7 +262,7 @@ class V5toV4TestCase(AdapterTest):
             msg = self.msg(v5_type, {'key' : 'value'})
             v5, v4 = self.adapt(msg)
             self.assertEqual(v4['header']['msg_type'], v4_type)
-            nt.assert_not_in('version', v4['header'])
+            assert 'version' not in v4['header']
             self.assertEqual(v4['content'], v5['content'])
 
     def test_execute_request(self):
diff --git a/jupyter_client/tests/test_client.py b/jupyter_client/tests/test_client.py
index 8375e6a..4181640 100644
--- a/jupyter_client/tests/test_client.py
+++ b/jupyter_client/tests/test_client.py
@@ -8,12 +8,12 @@ import os
 pjoin = os.path.join
 from unittest import TestCase
 
-from nose import SkipTest
-
 from jupyter_client.kernelspec import KernelSpecManager, NoSuchKernel, NATIVE_KERNEL_NAME
 from ..manager import start_new_kernel
 from .utils import test_env
 
+import pytest
+
 from ipython_genutils.py3compat import string_types
 from IPython.utils.capture import capture_output
 
@@ -27,7 +27,7 @@ class TestKernelClient(TestCase):
         try:
             KernelSpecManager().get_kernel_spec(NATIVE_KERNEL_NAME)
         except NoSuchKernel:
-            raise SkipTest()
+            pytest.skip()
         self.km, self.kc = start_new_kernel(kernel_name=NATIVE_KERNEL_NAME)
         self.addCleanup(self.kc.stop_channels)
         self.addCleanup(self.km.shutdown_kernel)
diff --git a/jupyter_client/tests/test_connect.py b/jupyter_client/tests/test_connect.py
index c84ee66..e1985e7 100644
--- a/jupyter_client/tests/test_connect.py
+++ b/jupyter_client/tests/test_connect.py
@@ -6,10 +6,9 @@
 import json
 import os
 
-import nose.tools as nt
-
 from traitlets.config import Config
 from jupyter_core.application import JupyterApp
+from jupyter_core.paths import jupyter_runtime_dir
 from ipython_genutils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
 from ipython_genutils.py3compat import str_to_bytes
 from jupyter_client import connect, KernelClient
@@ -36,11 +35,11 @@ def test_write_connection_file():
     with TemporaryDirectory() as d:
         cf = os.path.join(d, 'kernel.json')
         connect.write_connection_file(cf, **sample_info)
-        nt.assert_true(os.path.exists(cf))
+        assert os.path.exists(cf)
         with open(cf, 'r') as f:
             info = json.load(f)
     info['key'] = str_to_bytes(info['key'])
-    nt.assert_equal(info, sample_info)
+    assert info == sample_info
 
 
 def test_load_connection_file_session():
@@ -56,8 +55,8 @@ def test_load_connection_file_session():
         app.connection_file = cf
         app.load_connection_file()
 
-    nt.assert_equal(session.key, sample_info['key'])
-    nt.assert_equal(session.signature_scheme, sample_info['signature_scheme'])
+    assert session.key == sample_info['key']
+    assert session.signature_scheme == sample_info['signature_scheme']
 
 
 def test_load_connection_file_session_with_kn():
@@ -73,8 +72,8 @@ def test_load_connection_file_session_with_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'])
+    assert session.key == sample_info_kn['key']
+    assert session.signature_scheme == sample_info_kn['signature_scheme']
 
 
 def test_app_load_connection_file():
@@ -89,7 +88,7 @@ def test_app_load_connection_file():
         if attr in ('key', 'signature_scheme'):
             continue
         value = getattr(app, attr)
-        nt.assert_equal(value, expected, "app.%s = %s != %s" % (attr, value, expected))
+        assert value == expected, "app.%s = %s != %s" % (attr, value, expected)
 
 
 def test_load_connection_info():
@@ -112,11 +111,9 @@ def test_load_connection_info():
 
 
 def test_find_connection_file():
-    cfg = Config()
     with TemporaryDirectory() as d:
-        cfg.ProfileDir.location = d
         cf = 'kernel.json'
-        app = DummyConsoleApp(config=cfg, connection_file=cf)
+        app = DummyConsoleApp(runtime_dir=d, connection_file=cf)
         app.initialize()
 
         security_dir = app.runtime_dir
@@ -131,7 +128,47 @@ def test_find_connection_file():
             '*ernel*',
             'k*',
             ):
-            nt.assert_equal(connect.find_connection_file(query, path=security_dir), profile_cf)
+            assert connect.find_connection_file(query, path=security_dir) == profile_cf
+
 
-        JupyterApp._instance = None
+def test_find_connection_file_local():
+    with TemporaryWorkingDirectory() as d:
+        cf = 'test.json'
+        abs_cf = os.path.abspath(cf)
+        with open(cf, 'w') as f:
+            f.write('{}')
+        
+        for query in (
+            'test.json',
+            'test',
+            abs_cf,
+            os.path.join('.', 'test.json'),
+        ):
+            assert connect.find_connection_file(query, path=['.', jupyter_runtime_dir()]) == abs_cf
+
+
+def test_find_connection_file_relative():
+    with TemporaryWorkingDirectory() as d:
+        cf = 'test.json'
+        os.mkdir('subdir')
+        cf = os.path.join('subdir', 'test.json')
+        abs_cf = os.path.abspath(cf)
+        with open(cf, 'w') as f:
+            f.write('{}')
+        
+        for query in (
+            os.path.join('.', 'subdir', 'test.json'),
+            os.path.join('subdir', 'test.json'),
+            abs_cf,
+        ):
+            assert connect.find_connection_file(query, path=['.', jupyter_runtime_dir()]) == abs_cf
+
+
+def test_find_connection_file_abspath():
+    with TemporaryDirectory() as d:
+        cf = 'absolute.json'
+        abs_cf = os.path.abspath(cf)
+        with open(cf, 'w') as f:
+            f.write('{}')
+        assert connect.find_connection_file(abs_cf, path=jupyter_runtime_dir()) == abs_cf
 
diff --git a/jupyter_client/tests/test_jsonutil.py b/jupyter_client/tests/test_jsonutil.py
index 834824e..9583a22 100644
--- a/jupyter_client/tests/test_jsonutil.py
+++ b/jupyter_client/tests/test_jsonutil.py
@@ -14,8 +14,6 @@ except ImportError:
     # py2
     import mock
 
-import nose.tools as nt
-
 from dateutil.tz import tzlocal, tzoffset
 from jupyter_client import jsonutil
 from jupyter_client.session import utcnow
@@ -33,29 +31,29 @@ def test_extract_dates():
     extracted = jsonutil.extract_dates(timestamps)
     ref = extracted[0]
     for dt in extracted:
-        nt.assert_true(isinstance(dt, datetime.datetime))
-        nt.assert_not_equal(dt.tzinfo, None)
+        assert isinstance(dt, datetime.datetime)
+        assert dt.tzinfo != None
 
-    nt.assert_equal(extracted[0].tzinfo.utcoffset(ref), tzlocal().utcoffset(ref))
-    nt.assert_equal(extracted[1].tzinfo.utcoffset(ref), timedelta(0))
-    nt.assert_equal(extracted[2].tzinfo.utcoffset(ref), timedelta(hours=-8))
-    nt.assert_equal(extracted[3].tzinfo.utcoffset(ref), timedelta(hours=8))
-    nt.assert_equal(extracted[4].tzinfo.utcoffset(ref), timedelta(hours=-8))
-    nt.assert_equal(extracted[5].tzinfo.utcoffset(ref), timedelta(hours=8))
+    assert extracted[0].tzinfo.utcoffset(ref) == tzlocal().utcoffset(ref)
+    assert extracted[1].tzinfo.utcoffset(ref) == timedelta(0)
+    assert extracted[2].tzinfo.utcoffset(ref) == timedelta(hours=-8)
+    assert extracted[3].tzinfo.utcoffset(ref) == timedelta(hours=8)
+    assert extracted[4].tzinfo.utcoffset(ref) == timedelta(hours=-8)
+    assert extracted[5].tzinfo.utcoffset(ref) == timedelta(hours=8)
 
 def test_parse_ms_precision():
     base = '2013-07-03T16:34:52'
     digits = '1234567890'
     
     parsed = jsonutil.parse_date(base)
-    nt.assert_is_instance(parsed, datetime.datetime)
+    assert isinstance(parsed, datetime.datetime)
     for i in range(len(digits)):
         ts = base + '.' + digits[:i]
         parsed = jsonutil.parse_date(ts)
         if i >= 1 and i <= 6:
-            nt.assert_is_instance(parsed, datetime.datetime)
+            assert isinstance(parsed, datetime.datetime)
         else:
-            nt.assert_is_instance(parsed, str)
+            assert isinstance(parsed, str)
 
 
 
@@ -66,10 +64,10 @@ def test_date_default():
     data = dict(naive=naive, utc=utcnow(), withtz=naive.replace(tzinfo=other))
     with mock.patch.object(jsonutil, 'tzlocal', lambda : local):
         jsondata = json.dumps(data, default=jsonutil.date_default)
-    nt.assert_in("Z", jsondata)
-    nt.assert_equal(jsondata.count("Z"), 1)
+    assert "Z" in jsondata
+    assert jsondata.count("Z") == 1
     extracted = jsonutil.extract_dates(json.loads(jsondata))
     for dt in extracted.values():
-        nt.assert_is_instance(dt, datetime.datetime)
-        nt.assert_not_equal(dt.tzinfo, None)
+        assert isinstance(dt, datetime.datetime)
+        assert dt.tzinfo != None
 
diff --git a/jupyter_client/tests/test_kernelmanager.py b/jupyter_client/tests/test_kernelmanager.py
index 231ce00..a23b33f 100644
--- a/jupyter_client/tests/test_kernelmanager.py
+++ b/jupyter_client/tests/test_kernelmanager.py
@@ -13,13 +13,11 @@ 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
+from .utils import test_env, skip_win32
 
 TIMEOUT = 30
 
@@ -67,7 +65,7 @@ class TestKernelManager(TestCase):
         km = self._get_tcp_km()
         self._run_lifecycle(km)
 
-    @dec.skip_win32
+    @skip_win32
     def test_ipc_lifecycle(self):
         km = self._get_ipc_km()
         self._run_lifecycle(km)
@@ -82,8 +80,8 @@ class TestKernelManager(TestCase):
             'key', 'signature_scheme',
         ])
         self.assertEqual(keys, expected)
-    
-    @dec.skip_win32
+
+    @skip_win32
     def test_signal_kernel_subprocesses(self):
         self._install_test_kernel()
         km, kc = start_new_kernel(kernel_name='signaltest')
diff --git a/jupyter_client/tests/test_kernelspec.py b/jupyter_client/tests/test_kernelspec.py
index 5d6d4d1..b02dd75 100644
--- a/jupyter_client/tests/test_kernelspec.py
+++ b/jupyter_client/tests/test_kernelspec.py
@@ -13,12 +13,13 @@ from subprocess import Popen, PIPE, STDOUT
 import sys
 import unittest
 
+import pytest
+
 if str is bytes: # py2
     StringIO = io.BytesIO
 else:
     StringIO = io.StringIO
 
-from ipython_genutils.testing.decorators import onlyif
 from ipython_genutils.tempdir import TemporaryDirectory
 from jupyter_client import kernelspec
 from jupyter_core import paths
@@ -123,7 +124,9 @@ class KernelSpecTests(unittest.TestCase):
         self.ksm.log.removeHandler(handler)
         self.assertNotIn("may not be found", captured)
 
-    @onlyif(os.name != 'nt' and not os.access('/usr/local/share', os.W_OK), "needs Unix system without root privileges")
+    @pytest.mark.skipif(
+        not (os.name != 'nt' and not os.access('/usr/local/share', os.W_OK)),
+        reason="needs Unix system without root privileges")
     def test_cant_install_kernel_spec(self):
         with self.assertRaises(OSError):
             self.ksm.install_kernel_spec(self.installable_kernel,
diff --git a/jupyter_client/tests/test_multikernelmanager.py b/jupyter_client/tests/test_multikernelmanager.py
index ad9b5d4..2ca2ea4 100644
--- a/jupyter_client/tests/test_multikernelmanager.py
+++ b/jupyter_client/tests/test_multikernelmanager.py
@@ -4,12 +4,11 @@ from subprocess import PIPE
 import time
 from unittest import TestCase
 
-from ipython_genutils.testing import decorators as dec
-
 from traitlets.config.loader import Config
 from ..localinterfaces import localhost
 from jupyter_client import KernelManager
 from jupyter_client.multikernelmanager import MultiKernelManager
+from .utils import skip_win32
 
 class TestKernelManager(TestCase):
 
@@ -75,12 +74,12 @@ class TestKernelManager(TestCase):
         km = self._get_tcp_km()
         self._run_cinfo(km, 'tcp', localhost())
 
-    @dec.skip_win32
+    @skip_win32
     def test_ipc_lifecycle(self):
         km = self._get_ipc_km()
         self._run_lifecycle(km)
 
-    @dec.skip_win32
+    @skip_win32
     def test_ipc_cinfo(self):
         km = self._get_ipc_km()
         self._run_cinfo(km, 'ipc', 'test')
diff --git a/jupyter_client/tests/test_public_api.py b/jupyter_client/tests/test_public_api.py
index 6b6512d..ab3883d 100644
--- a/jupyter_client/tests/test_public_api.py
+++ b/jupyter_client/tests/test_public_api.py
@@ -4,8 +4,6 @@
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
 
-import nose.tools as nt
-
 from jupyter_client import launcher, connect
 import jupyter_client
 
@@ -13,17 +11,17 @@ import jupyter_client
 def test_kms():
     for base in ("", "Multi"):
         KM = base + "KernelManager"
-        nt.assert_in(KM, dir(jupyter_client))
+        assert KM in dir(jupyter_client)
 
 def test_kcs():
     for base in ("", "Blocking"):
         KM = base + "KernelClient"
-        nt.assert_in(KM, dir(jupyter_client))
+        assert KM in dir(jupyter_client)
 
 def test_launcher():
     for name in launcher.__all__:
-        nt.assert_in(name, dir(jupyter_client))
+        assert name in dir(jupyter_client)
 
 def test_connect():
     for name in connect.__all__:
-        nt.assert_in(name, dir(jupyter_client))
+        assert name in dir(jupyter_client)
diff --git a/jupyter_client/tests/test_session.py b/jupyter_client/tests/test_session.py
index 1719814..2d1ef98 100644
--- a/jupyter_client/tests/test_session.py
+++ b/jupyter_client/tests/test_session.py
@@ -8,6 +8,8 @@ import os
 import uuid
 from datetime import datetime
 
+import pytest
+
 import zmq
 
 from zmq.tests import BaseZMQTestCase
@@ -16,7 +18,6 @@ from zmq.eventloop.zmqstream import ZMQStream
 from jupyter_client import session as ss
 from jupyter_client import jsonutil
 
-from ipython_genutils.testing.decorators import skipif, module_not_available
 from ipython_genutils.py3compat import string_types
 
 def _bad_packer(obj):
@@ -286,9 +287,8 @@ class TestSession(SessionTestCase):
         session = ss.Session(packer='pickle')
         self._datetime_test(session)
 
-    @skipif(module_not_available('msgpack'))
     def test_datetimes_msgpack(self):
-        import msgpack
+        msgpack = pytest.importorskip('msgpack')
 
         session = ss.Session(
             pack=msgpack.packb,
@@ -320,3 +320,15 @@ class TestSession(SessionTestCase):
         A.close()
         B.close()
         ctx.term()
+    
+    def test_clone(self):
+        s = self.session
+        s._add_digest('initial')
+        s2 = s.clone()
+        assert s2.session == s.session
+        assert s2.digest_history == s.digest_history
+        assert s2.digest_history is not s.digest_history
+        digest = 'abcdef'
+        s._add_digest(digest)
+        assert digest in s.digest_history
+        assert digest not in s2.digest_history
diff --git a/jupyter_client/tests/utils.py b/jupyter_client/tests/utils.py
index 0f68002..505084a 100644
--- a/jupyter_client/tests/utils.py
+++ b/jupyter_client/tests/utils.py
@@ -3,13 +3,20 @@
 """
 import os
 pjoin = os.path.join
+import sys
 try:
     from unittest.mock import patch
 except ImportError:
     from mock import patch
 
+import pytest
+
 from ipython_genutils.tempdir import TemporaryDirectory
 
+
+skip_win32 = pytest.mark.skipif(sys.platform.startswith('win'), reason="Windows")
+
+
 class test_env(object):
     """Set Jupyter path variables to a temporary directory
     
@@ -46,11 +53,11 @@ def execute(code='', kc=None, **kwargs):
     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')
+    assert 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)
+        assert execute_input['content']['code'] == code
 
     return msg_id, reply['content']
diff --git a/jupyter_client/threaded.py b/jupyter_client/threaded.py
index 4f1f77a..ace1d1d 100644
--- a/jupyter_client/threaded.py
+++ b/jupyter_client/threaded.py
@@ -141,14 +141,17 @@ class ThreadedZMQSocketChannel(object):
 class IOLoopThread(Thread):
     """Run a pyzmq ioloop in a thread to send and receive messages
     """
+    _exiting = False
+
     def __init__(self, loop):
         super(IOLoopThread, self).__init__()
         self.daemon = True
-        atexit.register(self._notice_exit)
         self.ioloop = loop or ioloop.IOLoop()
 
-    def _notice_exit(self):
-        self._exiting = True
+    @staticmethod
+    @atexit.register
+    def _notice_exit():
+        IOLoopThread._exiting = True
 
     def run(self):
         """Run my loop, ignoring EINTR events in the poller"""
diff --git a/setup.py b/setup.py
index ca7cb96..51bcf2f 100644
--- a/setup.py
+++ b/setup.py
@@ -82,7 +82,7 @@ install_requires = setuptools_args['install_requires'] = [
 ]
 
 extras_require = setuptools_args['extras_require'] = {
-    'test': ['ipykernel', 'ipython', 'nose_warnings_filters'],
+    'test': ['ipykernel', 'ipython', 'pytest'],
 }
 
 if 'setuptools' in sys.modules:

-- 
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