Hello community,

here is the log from the commit of package python-rpyc for openSUSE:Factory 
checked in at 2020-03-19 19:50:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-rpyc (Old)
 and      /work/SRC/openSUSE:Factory/.python-rpyc.new.3160 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-rpyc"

Thu Mar 19 19:50:48 2020 rev:7 rq:786356 version:4.1.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-rpyc/python-rpyc.changes  2019-09-11 
10:35:46.955289445 +0200
+++ /work/SRC/openSUSE:Factory/.python-rpyc.new.3160/python-rpyc.changes        
2020-03-19 19:54:13.904277536 +0100
@@ -1,0 +2,14 @@
+Thu Mar 19 07:57:20 UTC 2020 - pgaj...@suse.com
+
+- version update to 4.1.4
+  - Merged 3.7 and 3.8 teleportatio compat enhancement `#371`_
+  - Fixed connection hanging due to namepack cursor  `#369`_
+  - Fixed test dependencies and is_py_* for 3.9
+  - Performance improvements: `#366`_ and `#351`_
+  - Merged fix for propagate_KeyboardInterrupt_locally `#364`_
+  - Fixed handling of exceptions for request callbacks `#365`_
+  - Partially fixed return value for netref.__class__ `#355`_
+  - Fixed `CVE-2019-16328`_ which was caused by a missing protocol security 
check
+  - Fixed RPyC over RPyC for mutable parameters and extended unit testing for 
`#346`_
+
+-------------------------------------------------------------------

Old:
----
  4.1.1.tar.gz

New:
----
  4.1.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-rpyc.spec ++++++
--- /var/tmp/diff_new_pack.rePmNz/_old  2020-03-19 19:54:14.320277550 +0100
+++ /var/tmp/diff_new_pack.rePmNz/_new  2020-03-19 19:54:14.324277551 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-rpyc
 #
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -26,7 +26,7 @@
 %bcond_with test
 %endif
 Name:           python-rpyc%{psuffix}
-Version:        4.1.1
+Version:        4.1.4
 Release:        0
 Summary:        Remote Python Call (RPyC), a RPC library
 License:        MIT
@@ -36,14 +36,14 @@
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
-Requires:       python-plumbum
+Requires:       python-plumbum >= 1.2
 Requires(post): update-alternatives
 Requires(postun): update-alternatives
 BuildArch:      noarch
 %if %{with test}
 BuildRequires:  %{python_module gevent}
 BuildRequires:  %{python_module nose}
-BuildRequires:  %{python_module plumbum}
+BuildRequires:  %{python_module plumbum >= 1.2}
 BuildRequires:  %{python_module rpyc = %{version}}
 %endif
 %python_subpackages
@@ -76,7 +76,7 @@
 
 %if %{with test}
 %check
-%python_expand nosetests-%{$python_bin_suffix} -v -I test_deploy -I 
test_gevent_server -I test_ssh -I test_registry
+%python_expand nosetests-%{$python_bin_suffix} -v -I test_deploy -I 
test_gevent_server -I test_ssh -I test_registry -I test_win32pipes
 %endif
 
 %if !%{with test}

++++++ 4.1.1.tar.gz -> 4.1.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/.travis.yml new/rpyc-4.1.4/.travis.yml
--- old/rpyc-4.1.1/.travis.yml  2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/.travis.yml  2020-01-30 07:26:06.000000000 +0100
@@ -1,26 +1,23 @@
 language: python
 
-dist: trusty
+dist: bionic
 
 # See: https://docs.travis-ci.com/user/languages/python/
 # and: https://docs.travis-ci.com/user/customizing-the-build/
 matrix:
   include:
-    - {python: "2.6",      env: SKIP_GEVENT=true    SKIP_DEPLOY=true}
-    - {python: "2.7",      env: SKIP_GEVENT=true                    }
-    - {python: "3.3",      env: SKIP_GEVENT=true    SKIP_DEPLOY=true}
-    - {python: "3.4",      env:                                     }
-    - {python: "3.5",      env:                     SKIP_DEPLOY=true}
-    - {python: "3.6",      env:                     SKIP_DEPLOY=true}
-    - {python: "3.7",      env: SKIP_GEVENT=true    SKIP_DEPLOY=true,
-       dist: xenial,       sudo: true}
-    - {python: "nightly",  env: SKIP_GEVENT=true    SKIP_DEPLOY=true,
-       dist: xenial,       sudo: true}
+    - {python: "2.7"}
+    - {python: "3.6"}
+    - {python: "3.7"}
+    - {python: "3.8"}
+    - {python: "3.8-dev"}
+    - {python: "nightly"}
 
 install:
     - python setup.py install
-    - "pip install gevent   || [[ -n $SKIP_GEVENT ]]"
-    - "pip install paramiko || [[ -n $SKIP_DEPLOY ]]"
+    # Install fails and historically has been skipped anyway
+    # - pip install gevent
+    # - pip install paramiko
 
 before_script:
     - "echo NoHostAuthenticationForLocalhost yes >> ~/.ssh/config"
@@ -32,11 +29,7 @@
     - "cd tests"
 
 script:
-    - nosetests -vv -I test_deploy -I test_gevent_server
-    # need to run in its own python interpreter (`monkey.patch_all()`):
-    - "python test_gevent_server.py || [[ -n $SKIP_GEVENT ]]"
-    # it currently fails on all but the native python versions on travis:
-    - "python test_deploy.py        || [[ -n $SKIP_DEPLOY ]]"
+    - python -m unittest discover
 
 notifications:
     email:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/CHANGELOG.rst new/rpyc-4.1.4/CHANGELOG.rst
--- old/rpyc-4.1.1/CHANGELOG.rst        2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/CHANGELOG.rst        2020-01-30 07:26:06.000000000 +0100
@@ -1,3 +1,41 @@
+4.1.4
+-----
+Date: 1.30.2020
+
+- Merged 3.7 and 3.8 teleportatio compat enhancement `#371`_
+- Fixed connection hanging due to namepack cursor  `#369`_
+- Fixed test dependencies and is_py_* for 3.9
+
+.. _#371: https://github.com/tomerfiliba/rpyc/issues/371
+.. _#369: https://github.com/tomerfiliba/rpyc/issues/369
+
+4.1.3
+-----
+Date: 1.25.2020
+
+- Performance improvements: `#366`_ and `#351`_
+- Merged fix for propagate_KeyboardInterrupt_locally `#364`_
+- Fixed handling of exceptions for request callbacks `#365`_
+- Partially fixed return value for netref.__class__ `#355`_
+
+.. _#366: https://github.com/tomerfiliba/rpyc/issues/366
+.. _#351: https://github.com/tomerfiliba/rpyc/pull/351
+.. _#364: https://github.com/tomerfiliba/rpyc/pull/364
+.. _#365: https://github.com/tomerfiliba/rpyc/issues/365
+.. _#355: https://github.com/tomerfiliba/rpyc/issues/355
+
+
+4.1.2
+-----
+Date: 10.03.2019
+
+- Fixed `CVE-2019-16328`_ which was caused by a missing protocol security check
+- Fixed RPyC over RPyC for mutable parameters and extended unit testing for 
`#346`_
+
+.. _CVE-2019-16328: https://rpyc.readthedocs.io/en/latest/docs/security.html
+.. _#346: https://github.com/tomerfiliba/rpyc/issues/346
+
+
 4.1.1
 -----
 Date: 07.27.2019
Binary files 
old/rpyc-4.1.1/docs/docs/_static/advanced-debugging-chained-connection-w-wireshark.png
 and 
new/rpyc-4.1.4/docs/docs/_static/advanced-debugging-chained-connection-w-wireshark.png
 differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/docs/docs/advanced-debugging.rst 
new/rpyc-4.1.4/docs/docs/advanced-debugging.rst
--- old/rpyc-4.1.1/docs/docs/advanced-debugging.rst     1970-01-01 
01:00:00.000000000 +0100
+++ new/rpyc-4.1.4/docs/docs/advanced-debugging.rst     2020-01-30 
07:26:06.000000000 +0100
@@ -0,0 +1,37 @@
+.. _advdebugging:
+
+Advanced Debugging
+==================
+
+A guide to using Wireshark when debugging complex use such as 
chained-connections.
+
+Tips and Tricks
+---------------
+Setting up for a specific version ::
+
+    pkgver=4.0.2
+    myvenv=rpyc${pkgver}
+    virtualenv2 /tmp/$myvenv
+    source /tmp/$myvenv/bin/activate
+    pip install rpyc==$pkgver
+
+Display filtering for Wireshark ::
+
+    tcp.port == 18878 || tcp.port == 18879
+    (tcp.port == 18878 || tcp.port == 18879) && tcp.segment_data contains 
"rpyc.core.service.SlaveService"
+
+Running the chained-connection unit test ::
+
+    cd tests
+    python  -m unittest test_get_id_pack.Test_get_id_pack.test_chained_connect
+
+
+After stopping Wireshark, export specified packets, and open the PCAP. If not 
already configured, add a custom display column: ::
+
+    Title,        Type,   Fields,     Field Occurrence
+    Stream Index, Custom, tcp.stream, 0
+
+The stream index column makes it easier to decide which TCP stream to follow. 
Following a TCP provides a more human readable overview
+of requests and replies that can be printed as a PDF.
+
+.. figure:: _static/advanced-debugging-chained-connection-w-wireshark.png
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/docs/docs/security.rst 
new/rpyc-4.1.4/docs/docs/security.rst
--- old/rpyc-4.1.1/docs/docs/security.rst       2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/docs/docs/security.rst       2020-01-30 07:26:06.000000000 
+0100
@@ -3,26 +3,34 @@
 Security
 ========
 Operating over a network always involve a certain security risk, and requires 
some awareness.
-RPyC is believed to be a secure protocol -- no incidents have been reported 
since version 3 was
-released in 2008. Version 3 was a rewrite of the library, specifically 
targeting security and
-service-orientation. Unlike v2.6, RPyC no longer makes use of unsecure 
protocols like ``pickle``,
+Version 3 of RPyC was a rewrite of the library, specifically targeting 
security and
+service-orientation. Unlike version 2.6, RPyC no longer makes use of unsecure 
protocols like ``pickle``,
 supports :data:`security-related configuration parameters 
<rpyc.core.protocol.DEFAULT_CONFIG>`,
-comes with strict defaults, and encourages the use of a capability-based 
security model.
-I daresay RPyC itself is secure.
+comes with strict defaults, and encourages the use of a capability-based 
security model. Even so, it behooves you to
+take a layered to secure programming and not let RPyC be a single point of 
failure.
 
-However, if not used properly, RPyC is also the perfect back-door... The 
general recommendation
-is not to use RPyC openly exposed over the Internet. It's wiser to use it only 
over secure local
+`CVE-2019-16328`_ is the first vulnerability since 2008, which made it 
possible for a remote attacker to
+bypass standard protocol security checks and modify the behavior of a service. 
The latent flaw was committed
+to master from September 2018 to October 2019 and affected versions `4.1.0` 
and `4.1.1`. As of version
+`4.1.2`, the vulnerability has been fixed.
+
+RPyC is intuitive and secure when used properly. However, if not used 
properly, RPyC is also the perfect back-door...
+The general recommendation is not to use RPyC openly exposed over the 
Internet. It's wiser to use it only over secure local
 networks, where you trust your peers. This does not imply that there's 
anything wrong with the
-mechanism -- but the implementation details are sometimes too subtle to be 
sure of.
-Of course you can use RPyC over a :ref:`secure connection <ssl>`, to mitigate 
these risks
+mechanism--but the implementation details are sometimes too subtle to be sure 
of.
+Of course, you can use RPyC over a :ref:`secure connection <ssl>`, to mitigate 
these risks.
 
 RPyC works by exposing a root object, which in turn may expose other objects 
(and so on). For
 instance, if you expose a module or an object that has a reference to the 
``sys`` module,
 a user may be able to reach it. After reaching ``sys``, the user can traverse 
``sys.modules`` and
-gain access to all of the modules that the server imports. This of course 
depends on RPyC
-being configured to allow attribute access (by default, this parameter is set 
to false).
-But if you enable ``allow_public_attrs``, such things are likely to slip under 
the radar
-(though it's possible to prevent this -- see below).
+gain access to all of the modules that the server imports. More complex 
methodologies, similiar to those used in ``CVE-2019-16328``,
+could leverage access to ``builtins.str``, ``builtins.type``, 
``builtins.object``, and ``builtins.dict`` and gain access to
+``sys`` modules. The default configurations for RPyC are intended to mitigate 
access to dangerous objects. But if you enable
+``allow_public_attrs``, return uninitialized classes or override 
``_rpyc_getattr`` such things are likely to slip under the radar
+(it's possible to prevent this -- see below).
+
+.. _CVE-2019-16328: 
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-16328
+
 
 Wrapping
 --------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/docs/docs.rst new/rpyc-4.1.4/docs/docs.rst
--- old/rpyc-4.1.1/docs/docs.rst        2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/docs/docs.rst        2020-01-30 07:26:06.000000000 +0100
@@ -41,6 +41,7 @@
    docs/security
    docs/secure-connection
    docs/zerodeploy
+   docs/advanced-debugging
 
 
 * :ref:`Servers <servers>` - using the built-in servers and writing custom ones
@@ -62,3 +63,5 @@
 
 * :ref:`Zero-Deploy <zerodeploy>` - spawn temporary, short-lived RPyC server 
on remote
   machine with nothing more than SSH and a Python interpreter
+
+* :ref:`Advanced Debugging <advdebugging>` - debugging at the packet level
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/core/brine.py 
new/rpyc-4.1.4/rpyc/core/brine.py
--- old/rpyc-4.1.1/rpyc/core/brine.py   2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/rpyc/core/brine.py   2020-01-30 07:26:06.000000000 +0100
@@ -17,7 +17,7 @@
  >>> x == z
  True
 """
-from rpyc.lib.compat import Struct, BytesIO, is_py3k, BYTES_LITERAL
+from rpyc.lib.compat import Struct, BytesIO, is_py_3k, BYTES_LITERAL
 
 
 # singletons
@@ -49,7 +49,7 @@
 TAG_SLICE = b"\x19"
 TAG_FSET = b"\x1a"
 TAG_COMPLEX = b"\x1b"
-if is_py3k:
+if is_py_3k:
     IMM_INTS = dict((i, bytes([i + 0x50])) for i in range(-0x30, 0xa0))
 else:
     IMM_INTS = dict((i, chr(i + 0x50)) for i in range(-0x30, 0xa0))
@@ -156,7 +156,7 @@
     _dump_bytes(obj.encode("utf8"), stream)
 
 
-if not is_py3k:
+if not is_py_3k:
     @register(_dump_registry, long)  # noqa: F821
     def _dump_long(obj, stream):
         stream.append(TAG_LONG)
@@ -229,7 +229,7 @@
     return b""
 
 
-if is_py3k:
+if is_py_3k:
     @register(_load_registry, TAG_LONG)
     def _load_long(stream):
         obj = _load(stream)
@@ -316,7 +316,7 @@
     return tuple(_load(stream) for i in range(l))
 
 
-if is_py3k:
+if is_py_3k:
     @register(_load_registry, TAG_TUP_L4)
     def _load_tup_l4(stream):
         l, = I4.unpack(stream.read(4))
@@ -385,7 +385,7 @@
     return _load(stream)
 
 
-if is_py3k:
+if is_py_3k:
     simple_types = frozenset([type(None), int, bool, float, bytes, str, 
complex,
                               type(NotImplemented), type(Ellipsis)])
 else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/core/channel.py 
new/rpyc-4.1.4/rpyc/core/channel.py
--- old/rpyc-4.1.1/rpyc/core/channel.py 2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/rpyc/core/channel.py 2020-01-30 07:26:06.000000000 +0100
@@ -70,6 +70,6 @@
             data = zlib.compress(data, self.COMPRESSION_LEVEL)
         else:
             compressed = 0
-        header = self.FRAME_HEADER.pack(len(data), compressed)
-        buf = header + data + self.FLUSHER
-        self.stream.write(buf)
+        self.stream.write(self.FRAME_HEADER.pack(len(data), compressed))
+        self.stream.write(data)
+        self.stream.write(self.FLUSHER)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/core/netref.py 
new/rpyc-4.1.4/rpyc/core/netref.py
--- old/rpyc-4.1.1/rpyc/core/netref.py  2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/rpyc/core/netref.py  2020-01-30 07:26:06.000000000 +0100
@@ -4,7 +4,7 @@
 import sys
 import types
 from rpyc.lib import get_methods, get_id_pack
-from rpyc.lib.compat import pickle, is_py3k, maxint, with_metaclass
+from rpyc.lib.compat import pickle, is_py_3k, maxint, with_metaclass
 from rpyc.core import consts
 
 
@@ -47,7 +47,7 @@
 else:
     _builtin_types.append(BaseException)
 
-if is_py3k:
+if is_py_3k:
     _builtin_types.extend([
         bytes, bytearray, type(iter(range(10))), memoryview,
     ])
@@ -229,9 +229,15 @@
                 elif other.____id_pack__[2] != 0:
                     return True
             else:
+                # seems dubious if each netref proxies to a different address 
spaces
                 return syncreq(self, consts.HANDLE_INSTANCECHECK, 
other.____id_pack__)
         else:
-            return isinstance(other, self.__class__)
+            if self.____id_pack__[2] == 0:
+                # outside the context of `__instancecheck__`, `__class__` is 
expected to be type(self)
+                # within the context of `__instancecheck__`, `other` should be 
compared to the proxied class
+                return isinstance(other, 
type(self).__dict__['__class__'].instance)
+            else:
+                raise TypeError("isinstance() arg 2 must be a class, type, or 
tuple of classes and types")
 
 
 def _make_method(name, doc):
@@ -271,6 +277,33 @@
         return method
 
 
+class NetrefClass(object):
+    """a descriptor of the class being proxied
+
+    Future considerations:
+     + there may be a cleaner alternative but lib.compat.with_metaclass 
prevented using __new__
+     + consider using __slot__ for this class
+     + revisit the design choice to use properties here
+    """
+
+    def __init__(self, class_obj):
+        self._class_obj = class_obj
+
+    @property
+    def instance(self):
+        """accessor to class object for the instance being proxied"""
+        return self._class_obj
+
+    @property
+    def owner(self):
+        """accessor to the class object for the instance owner being proxied"""
+        return self._class_obj.__class__
+
+    def __get__(self, netref_instance, netref_owner):
+        """the value returned when accessing the netref class is dictated by 
whether or not an instance is proxied"""
+        return self.owner if netref_instance.____id_pack__[2] == 0 else 
self.instance
+
+
 def class_factory(id_pack, methods):
     """Creates a netref class proxying the given class
 
@@ -281,30 +314,30 @@
     """
     ns = {"__slots__": (), "__class__": None}
     name_pack = id_pack[0]
-    if name_pack is not None:  # attempt to resolve against builtins and 
sys.modules
-        ns["__class__"] = _normalized_builtin_types.get(name_pack)
-        if ns["__class__"] is None:
-            _module = None
-            didx = name_pack.rfind('.')
-            if didx != -1:
-                _module = sys.modules.get(name_pack[:didx])
-                if _module is not None:
-                    _module = getattr(_module, name_pack[didx + 1:], None)
-                else:
-                    _module = sys.modules.get(name_pack)
-            else:
-                _module = sys.modules.get(name_pack)
-            if _module:
-                if id_pack[2] == 0:
-                    ns["__class__"] = _module
-                else:
-                    ns["__class__"] = getattr(_module, "__class__", None)
-
+    class_descriptor = None
+    if name_pack is not None:
+        # attempt to resolve __class__ using sys.modules (i.e. builtins and 
imported modules)
+        _module = None
+        cursor = len(name_pack)
+        while cursor != -1:
+            _module = sys.modules.get(name_pack[:cursor])
+            if _module is None:
+                cursor = name_pack[:cursor].rfind('.')
+                continue
+            _class_name = name_pack[cursor + 1:]
+            _class = getattr(_module, _class_name, None)
+            if _class is not None and hasattr(_class, '__class__'):
+                class_descriptor = NetrefClass(_class)
+            break
+    ns['__class__'] = class_descriptor
+    netref_name = class_descriptor.owner.__name__ if class_descriptor is not 
None else name_pack
+    # create methods that must perform a syncreq
     for name, doc in methods:
         name = str(name)  # IronPython issue #10
+        # only create methods that wont shadow BaseNetref during merge for mro
         if name not in LOCAL_ATTRS:  # i.e. `name != __class__`
             ns[name] = _make_method(name, doc)
-    return type(name_pack, (BaseNetref,), ns)
+    return type(netref_name, (BaseNetref,), ns)
 
 
 for _builtin in _builtin_types:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/core/protocol.py 
new/rpyc-4.1.4/rpyc/core/protocol.py
--- old/rpyc-4.1.1/rpyc/core/protocol.py        2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/rpyc/core/protocol.py        2020-01-30 07:26:06.000000000 
+0100
@@ -8,7 +8,7 @@
 
 from threading import Lock, Condition
 from rpyc.lib import spawn, Timeout, get_methods, get_id_pack
-from rpyc.lib.compat import pickle, next, is_py3k, maxint, select_error, 
acquire_lock  # noqa: F401
+from rpyc.lib.compat import pickle, next, is_py_3k, maxint, select_error, 
acquire_lock  # noqa: F401
 from rpyc.lib.colls import WeakValueDict, RefCountingColl
 from rpyc.core import consts, brine, vinegar, netref
 from rpyc.core.async_ import AsyncResult
@@ -272,13 +272,6 @@
         else:
             id_pack = get_id_pack(obj)
             self._local_objects.add(id_pack, obj)
-            try:
-                cls = obj.__class__
-            except Exception:
-                # see issue #16
-                cls = type(obj)
-            if not isinstance(cls, type):
-                cls = type(obj)
             return consts.LABEL_REMOTE_REF, id_pack
 
     def _unbox(self, package):  # boxing
@@ -305,15 +298,19 @@
 
     def _netref_factory(self, id_pack):  # boxing
         """id_pack is for remote, so when class id fails to directly match """
-        if id_pack[0] in netref.builtin_classes_cache:
+        cls = None
+        if id_pack[2] == 0 and id_pack in self._netref_classes_cache:
+            cls = self._netref_classes_cache[id_pack]
+        elif id_pack[0] in netref.builtin_classes_cache:
             cls = netref.builtin_classes_cache[id_pack[0]]
-        elif id_pack[1] in self._netref_classes_cache:
-            cls = self._netref_classes_cache[id_pack[1]]
-        else:
+        if cls is None:
             # in the future, it could see if a sys.module cache/lookup hits 
first
             cls_methods = self.sync_request(consts.HANDLE_INSPECT, id_pack)
             cls = netref.class_factory(id_pack, cls_methods)
-            self._netref_classes_cache[id_pack[1]] = cls
+            if id_pack[2] == 0:
+                # only use cached netrefs for classes
+                # ... instance caching after gc of a proxy will take some 
mental gymnastics
+                self._netref_classes_cache[id_pack] = cls
         return cls(self, id_pack)
 
     def _dispatch_request(self, seq, raw_args):  # dispatch
@@ -321,7 +318,7 @@
             handler, args = raw_args
             args = self._unbox(args)
             res = self._HANDLERS[handler](self, *args)
-        except Exception:
+        except:  # TODO: revist how to catch handle locally, this should 
simplify when py2 is dropped
             # need to catch old style exceptions too
             t, v, tb = sys.exc_info()
             self._last_traceback = tb
@@ -347,16 +344,24 @@
                             
instantiate_custom_exceptions=self._config["instantiate_custom_exceptions"],
                             
instantiate_oldstyle_exceptions=self._config["instantiate_oldstyle_exceptions"])
 
+    def _seq_request_callback(self, msg, seq, is_exc, obj):
+        _callback = self._request_callbacks.pop(seq, None)
+        if _callback is not None:
+            _callback(is_exc, obj)
+        elif self._config["logger"] is not None:
+            debug_msg = 'Recieved {} seq {} and a related request callback did 
not exist'
+            self._config["logger"].debug(debug_msg.format(msg, seq))
+
     def _dispatch(self, data):  # serving---dispatch?
         msg, seq, args = brine.load(data)
         if msg == consts.MSG_REQUEST:
             self._dispatch_request(seq, args)
         elif msg == consts.MSG_REPLY:
             obj = self._unbox(args)
-            self._request_callbacks.pop(seq)(False, obj)
+            self._seq_request_callback(msg, seq, False, obj)
         elif msg == consts.MSG_EXCEPTION:
             obj = self._unbox_exc(args)
-            self._request_callbacks.pop(seq)(True, obj)
+            self._seq_request_callback(msg, seq, True, obj)
         else:
             raise ValueError("invalid message type: %r" % (msg,))
 
@@ -410,8 +415,14 @@
             self.close()
 
     def serve_threaded(self, thread_count=10):  # serving
-        """Serves all requests and replies for as long as the connection is
-        alive."""
+        """Serves all requests and replies for as long as the connection is 
alive.
+
+        CAVEAT: using non-immutable types that require a netref to be 
constructed to serve a request,
+        or invoking anything else that performs a sync_request, may timeout 
due to the sync_request reply being
+        received by another thread serving the connection. A more conventional 
approach where each client thread
+        opens a new connection would allow `ThreadedServer` to naturally avoid 
such multiplexing issues and
+        is the preferred approach for threading procedures that invoke 
sync_request. See issue #345
+        """
         def _thread_target():
             try:
                 while True:
@@ -463,6 +474,9 @@
         try:
             self._send(consts.MSG_REQUEST, seq, (handler, self._box(args)))
         except Exception:
+            # TODO: review test_remote_exception, logging exceptions show 
attempt to write on closed stream
+            # depending on the case, the MSG_REQUEST may or may not have been 
sent completely
+            # so, pop the callback and raise to keep response integrity is 
consistent
             self._request_callbacks.pop(seq, None)
             raise
 
@@ -507,7 +521,7 @@
         raise AttributeError("cannot access %r" % (name,))
 
     def _access_attr(self, obj, name, args, overrider, param, default):  # 
attribute access
-        if is_py3k:
+        if is_py_3k:
             if type(name) is bytes:
                 name = str(name, "utf8")
             elif type(name) is not str:
@@ -566,12 +580,11 @@
         return str(obj)
 
     def _handle_cmp(self, obj, other, op='__cmp__'):  # request handler
-        # cmp() might enter recursive resonance... yet another workaround
-        # return cmp(obj, other)
+        # cmp() might enter recursive resonance... so use the underlying type 
and return cmp(obj, other)
         try:
-            return getattr(type(obj), op)(obj, other)
-        except (AttributeError, TypeError):
-            return NotImplemented
+            return self._access_attr(type(obj), op, (), "_rpyc_getattr", 
"allow_getattr", getattr)(obj, other)
+        except Exception:
+            raise
 
     def _handle_hash(self, obj):  # request handler
         return hash(obj)
@@ -583,7 +596,14 @@
         return tuple(dir(obj))
 
     def _handle_inspect(self, id_pack):  # request handler
-        return tuple(get_methods(netref.LOCAL_ATTRS, 
self._local_objects[id_pack]))
+        if hasattr(self._local_objects[id_pack], '____conn__'):
+            # When RPyC is chained (RPyC over RPyC), id_pack is cached in 
local objects as a netref
+            # since __mro__ is not a safe attribute the request is forwarded 
using the proxy connection
+            # see issue #346 or tests.test_rpyc_over_rpyc.Test_rpyc_over_rpyc
+            conn = self._local_objects[id_pack].____conn__
+            return conn.sync_request(consts.HANDLE_INSPECT, id_pack)
+        else:
+            return tuple(get_methods(netref.LOCAL_ATTRS, 
self._local_objects[id_pack]))
 
     def _handle_getattr(self, obj, name):  # request handler
         return self._access_attr(obj, name, (), "_rpyc_getattr", 
"allow_getattr", getattr)
@@ -609,14 +629,27 @@
         return self._handle_getattr(obj, "__exit__")(exc, typ, tb)
 
     def _handle_instancecheck(self, obj, other_id_pack):
+        # TODOs:
+        #  + refactor cache instancecheck/inspect/class_factory
+        #  + improve cache docs
+
+        if hasattr(obj, '____conn__'):  # keep unwrapping!
+            # When RPyC is chained (RPyC over RPyC), id_pack is cached in 
local objects as a netref
+            # since __mro__ is not a safe attribute the request is forwarded 
using the proxy connection
+            # relates to issue #346 or 
tests.test_netref_hierachy.Test_Netref_Hierarchy.test_StandardError
+            conn = obj.____conn__
+            return conn.sync_request(consts.HANDLE_INSPECT, id_pack)
         # Create a name pack which would be familiar here and see if there is 
a hit
         other_id_pack2 = (other_id_pack[0], other_id_pack[1], 0)
-        if other_id_pack2 in self._netref_classes_cache:
+        if other_id_pack[0] in netref.builtin_classes_cache:
+            cls = netref.builtin_classes_cache[other_id_pack[0]]
+            other = cls(self, other_id_pack)
+        elif other_id_pack2 in self._netref_classes_cache:
             cls = self._netref_classes_cache[other_id_pack2]
             other = cls(self, other_id_pack2)
-            return isinstance(other, obj)
         else:  # might just have missed cache, FIX ME
             return False
+        return isinstance(other, obj)
 
     def _handle_pickle(self, obj, proto):  # request handler
         if not self._config["allow_pickle"]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/core/service.py 
new/rpyc-4.1.4/rpyc/core/service.py
--- old/rpyc-4.1.1/rpyc/core/service.py 2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/rpyc/core/service.py 2020-01-30 07:26:06.000000000 +0100
@@ -9,7 +9,7 @@
 from functools import partial
 
 from rpyc.lib import hybridmethod
-from rpyc.lib.compat import execute, is_py3k
+from rpyc.lib.compat import execute, is_py_3k
 from rpyc.core.protocol import Connection
 
 
@@ -215,7 +215,7 @@
     @staticmethod
     def _install(conn, slave):
         modules = ModuleNamespace(slave.getmodule)
-        builtin = modules.builtins if is_py3k else modules.__builtin__
+        builtin = modules.builtins if is_py_3k else modules.__builtin__
         conn.modules = modules
         conn.eval = slave.eval
         conn.execute = slave.execute
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/core/vinegar.py 
new/rpyc-4.1.4/rpyc/core/vinegar.py
--- old/rpyc-4.1.1/rpyc/core/vinegar.py 2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/rpyc/core/vinegar.py 2020-01-30 07:26:06.000000000 +0100
@@ -22,7 +22,7 @@
 from rpyc.core import brine
 from rpyc.core import consts
 from rpyc import version
-from rpyc.lib.compat import is_py3k
+from rpyc.lib.compat import is_py_3k
 
 
 REMOTE_LINE_START = "\n\n========= Remote Traceback "
@@ -135,7 +135,7 @@
     else:
         cls = None
 
-    if is_py3k:
+    if is_py_3k:
         if not isinstance(cls, type) or not issubclass(cls, BaseException):
             cls = None
     else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/lib/__init__.py 
new/rpyc-4.1.4/rpyc/lib/__init__.py
--- old/rpyc-4.1.1/rpyc/lib/__init__.py 2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/rpyc/lib/__init__.py 2020-01-30 07:26:06.000000000 +0100
@@ -151,8 +151,39 @@
 
 
 def get_id_pack(obj):
-    """introspects the given (local) object, returns id_pack as expected by 
BaseNetref"""
-    if not inspect.isclass(obj):
+    """introspects the given "local" object, returns id_pack as expected by 
BaseNetref
+
+    The given object is "local" in the sense that it is from the local cache. 
Any object in the local cache exists
+    in the current address space or is a netref. A netref in the local cache 
could be from a chained-connection.
+    To handle type related behavior properly, the attribute `__class__` is a 
descriptor for netrefs.
+
+    So, check thy assumptions regarding the given object when creating 
`id_pack`.
+    """
+    if hasattr(obj, '____id_pack__'):
+        # netrefs are handled first since __class__ is a descriptor
+        return obj.____id_pack__
+    elif inspect.ismodule(obj) or getattr(obj, '__name__', None) == 'module':
+        # TODO: not sure about this, need to enumerate cases in units
+        if isinstance(obj, type):  # module
+            obj_cls = type(obj)
+            name_pack = '{0}.{1}'.format(obj_cls.__module__, obj_cls.__name__)
+            return (name_pack, id(type(obj)), id(obj))
+        else:
+            if inspect.ismodule(obj) and obj.__name__ != 'module':
+                if obj.__name__ in sys.modules:
+                    name_pack = obj.__name__
+                else:
+                    name_pack = '{0}.{1}'.format(obj.__class__.__module__, 
obj.__name__)
+            elif inspect.ismodule(obj):
+                name_pack = '{0}.{1}'.format(obj__module__, obj.__name__)
+                print(name_pack)
+            elif hasattr(obj, '__module__'):
+                name_pack = '{0}.{1}'.format(obj.__module__, obj.__name__)
+            else:
+                obj_cls = type(obj)
+                name_pack = '{0}'.format(obj.__name__)
+            return (name_pack, id(type(obj)), id(obj))
+    elif not inspect.isclass(obj):
         name_pack = '{0}.{1}'.format(obj.__class__.__module__, 
obj.__class__.__name__)
         return (name_pack, id(type(obj)), id(obj))
     else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/lib/compat.py 
new/rpyc-4.1.4/rpyc/lib/compat.py
--- old/rpyc-4.1.1/rpyc/lib/compat.py   2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/rpyc/lib/compat.py   2020-01-30 07:26:06.000000000 +0100
@@ -5,9 +5,11 @@
 import sys
 import time
 
-is_py3k = (sys.version_info[0] >= 3)
+is_py_3k = (sys.version_info[0] >= 3)
+is_py_gte38 = is_py_3k and (sys.version_info[1] >= 8)
 
-if is_py3k:
+
+if is_py_3k:
     exec("execute = exec")
 
     def BYTES_LITERAL(text):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/utils/classic.py 
new/rpyc-4.1.4/rpyc/utils/classic.py
--- old/rpyc-4.1.1/rpyc/utils/classic.py        2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/rpyc/utils/classic.py        2020-01-30 07:26:06.000000000 
+0100
@@ -2,7 +2,7 @@
 import sys
 import os
 import inspect
-from rpyc.lib.compat import pickle, execute, is_py3k  # noqa: F401
+from rpyc.lib.compat import pickle, execute, is_py_3k  # noqa: F401
 from rpyc.core.service import ClassicService, Slave
 from rpyc.utils import factory
 from rpyc.core.service import ModuleNamespace  # noqa: F401
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/utils/teleportation.py 
new/rpyc-4.1.4/rpyc/utils/teleportation.py
--- old/rpyc-4.1.1/rpyc/utils/teleportation.py  2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/rpyc/utils/teleportation.py  2020-01-30 07:26:06.000000000 
+0100
@@ -4,7 +4,7 @@
     import __builtin__
 except ImportError:
     import builtins as __builtin__  # noqa: F401
-from rpyc.lib.compat import is_py3k
+from rpyc.lib.compat import is_py_3k, is_py_gte38
 from types import CodeType, FunctionType
 from rpyc.core import brine
 from rpyc.core import netref
@@ -39,7 +39,7 @@
 
 def decode_codeobj(codeobj):
     # adapted from dis.dis
-    if is_py3k:
+    if is_py_3k:
         codestr = codeobj.co_code
     else:
         codestr = [ord(ch) for ch in codeobj.co_code]
@@ -75,7 +75,13 @@
         else:
             raise TypeError("Cannot export a function with non-brinable 
constants: %r" % (const,))
 
-    if is_py3k:
+    if is_py_gte38:
+        # Constructor was changed in 3.8 to support "advanced" programming 
styles
+        exported = (cobj.co_argcount, cobj.co_posonlyargcount, 
cobj.co_kwonlyargcount, cobj.co_nlocals,
+                    cobj.co_stacksize, cobj.co_flags, cobj.co_code, 
tuple(consts2), cobj.co_names, cobj.co_varnames,
+                    cobj.co_filename, cobj.co_name, cobj.co_firstlineno, 
cobj.co_lnotab, cobj.co_freevars,
+                    cobj.co_cellvars)
+    elif is_py_3k:
         exported = (cobj.co_argcount, cobj.co_kwonlyargcount, cobj.co_nlocals, 
cobj.co_stacksize, cobj.co_flags,
                     cobj.co_code, tuple(consts2), cobj.co_names, 
cobj.co_varnames, cobj.co_filename,
                     cobj.co_name, cobj.co_firstlineno, cobj.co_lnotab, 
cobj.co_freevars, cobj.co_cellvars)
@@ -89,7 +95,7 @@
 
 
 def export_function(func):
-    if is_py3k:
+    if is_py_3k:
         func_closure = func.__closure__
         func_code = func.__code__
         func_defaults = func.__defaults__
@@ -107,12 +113,18 @@
 
 
 def _import_codetup(codetup):
-    if is_py3k:
-        (argcnt, kwargcnt, nloc, stk, flg, codestr, consts, names, varnames, 
filename, name,
-            firstlineno, lnotab, freevars, cellvars) = codetup
+    if is_py_3k:
+        # Handle tuples sent from 3.8 as well as 3 < version < 3.8.
+        if len(codetup) == 16:
+            (argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize, 
flags, code, consts, names, varnames,
+             filename, name, firstlineno, lnotab, freevars, cellvars) = codetup
+        else:
+            (argcount, kwonlyargcount, nlocals, stacksize, flags, code, 
consts, names, varnames,
+             filename, name, firstlineno, lnotab, freevars, cellvars) = codetup
+            posonlyargcount = 0
     else:
-        (argcnt, nloc, stk, flg, codestr, consts, names, varnames, filename, 
name,
-            firstlineno, lnotab, freevars, cellvars) = codetup
+        (argcount, nlocals, stacksize, flags, code, consts, names, varnames,
+         filename, name, firstlineno, lnotab, freevars, cellvars) = codetup
 
     consts2 = []
     for const in consts:
@@ -120,13 +132,17 @@
             consts2.append(_import_codetup(const[1]))
         else:
             consts2.append(const)
-
-    if is_py3k:
-        return CodeType(argcnt, kwargcnt, nloc, stk, flg, codestr, 
tuple(consts2), names, varnames, filename, name,
-                        firstlineno, lnotab, freevars, cellvars)
+    consts = tuple(consts2)
+    if is_py_gte38:
+        codetup = (argcount, posonlyargcount, kwonlyargcount, nlocals, 
stacksize, flags, code, consts, names, varnames,
+                   filename, name, firstlineno, lnotab, freevars, cellvars)
+    elif is_py_3k:
+        codetup = (argcount, kwonlyargcount, nlocals, stacksize, flags, code, 
consts, names, varnames, filename, name,
+                   firstlineno, lnotab, freevars, cellvars)
     else:
-        return CodeType(argcnt, nloc, stk, flg, codestr, tuple(consts2), 
names, varnames, filename, name,
-                        firstlineno, lnotab, freevars, cellvars)
+        codetup = (argcount, nlocals, stacksize, flags, code, consts, names, 
varnames, filename, name, firstlineno,
+                   lnotab, freevars, cellvars)
+    return CodeType(*codetup)
 
 
 def import_function(functup, globals=None, def_=True):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/utils/zerodeploy.py 
new/rpyc-4.1.4/rpyc/utils/zerodeploy.py
--- old/rpyc-4.1.1/rpyc/utils/zerodeploy.py     2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/rpyc/utils/zerodeploy.py     2020-01-30 07:26:06.000000000 
+0100
@@ -13,6 +13,7 @@
 import rpyc.utils.classic
 try:
     from plumbum import local, ProcessExecutionError, CommandNotFound
+    from plumbum.commands.base import BoundCommand
     from plumbum.path import copy
 except ImportError:
     import inspect
@@ -102,7 +103,9 @@
         modname, clsname = server_class.rsplit(".", 1)
         script.write(SERVER_SCRIPT.replace("$MODULE$", modname).replace(
             "$SERVER$", clsname).replace("$EXTRA_SETUP$", extra_setup))
-        if python_executable:
+        if isinstance(python_executable, BoundCommand):
+            cmd = python_executable
+        elif python_executable:
             cmd = remote_machine[python_executable]
         else:
             major = sys.version_info[0]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/rpyc/version.py 
new/rpyc-4.1.4/rpyc/version.py
--- old/rpyc-4.1.1/rpyc/version.py      2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/rpyc/version.py      2020-01-30 07:26:06.000000000 +0100
@@ -1,3 +1,3 @@
-version = (4, 1, 1)
+version = (4, 1, 4)
 version_string = ".".join(map(str, version))
-release_date = "2019.05.25"
+release_date = "2020.1.30"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/setup.py new/rpyc-4.1.4/setup.py
--- old/rpyc-4.1.1/setup.py     2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/setup.py     2020-01-30 07:26:06.000000000 +0100
@@ -33,7 +33,7 @@
           os.path.join("bin", "rpyc_classic.py"),
           os.path.join("bin", "rpyc_registry.py"),
       ],
-      tests_require=['nose'],
+      tests_require=[],
       test_suite='nose.collector',
       install_requires=["plumbum"],
       #    entry_points = dict(
@@ -52,14 +52,12 @@
           "Intended Audience :: System Administrators",
           "License :: OSI Approved :: MIT License",
           "Operating System :: OS Independent",
-          "Programming Language :: Python :: 2.6",
           "Programming Language :: Python :: 2.7",
           "Programming Language :: Python :: 3",
-          "Programming Language :: Python :: 3.3",
-          "Programming Language :: Python :: 3.4",
-          "Programming Language :: Python :: 3.5",
           "Programming Language :: Python :: 3.6",
           "Programming Language :: Python :: 3.7",
+          "Programming Language :: Python :: 3.8",
+          "Programming Language :: Python :: 3.9",
           "Topic :: Internet",
           "Topic :: Software Development :: Libraries :: Python Modules",
           "Topic :: Software Development :: Object Brokering",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_attr_access.py 
new/rpyc-4.1.4/tests/test_attr_access.py
--- old/rpyc-4.1.1/tests/test_attr_access.py    2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/tests/test_attr_access.py    2020-01-30 07:26:06.000000000 
+0100
@@ -89,7 +89,7 @@
 
 class TestRestricted(unittest.TestCase):
     def setUp(self):
-        self.server = ThreadedServer(MyService, port=0)
+        self.server = ThreadedServer(MyService)
         self.thd = self.server._start_in_thread()
         self.conn = rpyc.connect("localhost", self.server.port)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_brine.py 
new/rpyc-4.1.4/tests/test_brine.py
--- old/rpyc-4.1.1/tests/test_brine.py  2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/tests/test_brine.py  2020-01-30 07:26:06.000000000 +0100
@@ -1,11 +1,11 @@
 from rpyc.core import brine
-from rpyc.lib.compat import is_py3k
+from rpyc.lib.compat import is_py_3k
 import unittest
 
 
 class BrineTest(unittest.TestCase):
     def test_brine_2(self):
-        if is_py3k:
+        if is_py_3k:
             exec('''x = (b"he", 7, "llo", 8, (), 900, None, True, Ellipsis, 
18.2, 18.2j + 13,
                  slice(1, 2, 3), frozenset([5, 6, 7]), NotImplemented, 
(1,2))''', globals())
         else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_classic.py 
new/rpyc-4.1.4/tests/test_classic.py
--- old/rpyc-4.1.1/tests/test_classic.py        2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/tests/test_classic.py        2020-01-30 07:26:06.000000000 
+0100
@@ -37,20 +37,10 @@
         self.assertEqual(list(bi), list(range(10000)))
 
     def test_classic(self):
-        print(self.conn.modules.sys)
-        print(self.conn.modules["xml.dom.minidom"].parseString("<a/>"))
         self.conn.execute("x = 5")
         self.assertEqual(self.conn.namespace["x"], 5)
         self.assertEqual(self.conn.eval("1+x"), 6)
 
-    def test_isinstance(self):
-        x = self.conn.builtin.list((1, 2, 3, 4))
-        print(self.conn.builtin.list, type(self.conn.builtin.list))  # <class 
'list'> <netref class 'builtins.type'>
-        print(x, type(x))  # [1, 2, 3, 4] <netref class 'builtins.list'>
-        print(x.__class__, type(x.__class__))  # <class 'list'> <class 'type'>
-        self.assertTrue(isinstance(x, list))
-        self.assertTrue(isinstance(x, rpyc.BaseNetref))
-
     def test_mock_connection(self):
         from rpyc.utils.classic import MockClassicConnection
         import sys
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_deploy.py 
new/rpyc-4.1.4/tests/test_deploy.py
--- old/rpyc-4.1.1/tests/test_deploy.py 2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/tests/test_deploy.py 2020-01-30 07:26:06.000000000 +0100
@@ -2,9 +2,16 @@
 import unittest
 import sys
 from plumbum import SshMachine
+from plumbum.machines.paramiko_machine import ParamikoMachine
 from rpyc.utils.zerodeploy import DeployedServer
+try:
+    import paramiko  # noqa
+    _paramiko_import_failed = False
+except Exception:
+    _paramiko_import_failed = True
 
 
+@unittest.skipIf(_paramiko_import_failed, "Paramiko is not available")
 class TestDeploy(unittest.TestCase):
     def test_deploy(self):
         rem = SshMachine("localhost")
@@ -23,12 +30,6 @@
             self.fail("expected an EOFError")
 
     def test_deploy_paramiko(self):
-        try:
-            import paramiko     # @UnusedImport
-        except Exception:
-            self.skipTest("Paramiko is not available")
-        from plumbum.machines.paramiko_machine import ParamikoMachine
-
         rem = ParamikoMachine("localhost", 
missing_host_policy=paramiko.AutoAddPolicy())
         with DeployedServer(rem) as dep:
             conn = dep.classic_connect()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_get_id_pack.py 
new/rpyc-4.1.4/tests/test_get_id_pack.py
--- old/rpyc-4.1.1/tests/test_get_id_pack.py    1970-01-01 01:00:00.000000000 
+0100
+++ new/rpyc-4.1.4/tests/test_get_id_pack.py    2020-01-30 07:26:06.000000000 
+0100
@@ -0,0 +1,44 @@
+import rpyc
+from rpyc.utils.server import ThreadedServer
+from rpyc import SlaveService
+import unittest
+
+
+class Test_get_id_pack(unittest.TestCase):
+
+    def setUp(self):
+        self.port = 18878
+        self.port2 = 18879
+        self.server = ThreadedServer(SlaveService, port=self.port, 
auto_register=False)
+        self.server2 = ThreadedServer(SlaveService, port=self.port2, 
auto_register=False)
+        self.server._start_in_thread()
+        self.server2._start_in_thread()
+        self.conn = rpyc.classic.connect("localhost", port=self.port)
+        self.conn_rpyc = self.conn.root.getmodule('rpyc')
+        self.chained_conn = self.conn_rpyc.connect('localhost', self.port2)
+
+    def tearDown(self):
+        self.chained_conn.close()
+        self.conn.close()
+        self.server.close()
+        self.server2.close()
+
+    def test_netref(self):
+        self.assertEquals(self.conn.root.____id_pack__, 
rpyc.lib.get_id_pack(self.conn.root))
+
+    def test_chained_connect(self):
+        self.chained_conn.root.getmodule('os')
+
+    def test_class_instance_wo_name(self):
+        ss = rpyc.SlaveService()
+        id_pack = rpyc.lib.get_id_pack(ss)
+        self.assertEqual('rpyc.core.service.SlaveService', id_pack[0])
+
+    def test_class_wo_name(self):
+        ss = rpyc.SlaveService
+        id_pack = rpyc.lib.get_id_pack(ss)
+        self.assertEqual('rpyc.core.service.SlaveService', id_pack[0])
+
+
+if __name__ == "__main__":
+    unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_gevent_server.py 
new/rpyc-4.1.4/tests/test_gevent_server.py
--- old/rpyc-4.1.1/tests/test_gevent_server.py  2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/tests/test_gevent_server.py  2020-01-30 07:26:06.000000000 
+0100
@@ -3,14 +3,19 @@
 from rpyc.utils.server import GeventServer
 import time
 import rpyc
-import gevent
-from gevent import monkey
-monkey.patch_all()
+try:
+    import gevent
+    _gevent_import_failed = False
+except Exception:
+    _gevent_import_failed = True
 
 
+@unittest.skipIf(_gevent_import_failed, "Gevent is not available")
 class Test_GeventServer(unittest.TestCase):
 
     def setUp(self):
+        from gevent import monkey
+        monkey.patch_all()
         self.server = GeventServer(SlaveService, port=18878, 
auto_register=False)
         self.server.logger.quiet = False
         self.server._listen()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_ipv6.py 
new/rpyc-4.1.4/tests/test_ipv6.py
--- old/rpyc-4.1.1/tests/test_ipv6.py   2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/tests/test_ipv6.py   2020-01-30 07:26:06.000000000 +0100
@@ -2,12 +2,10 @@
 import unittest
 from rpyc.utils.server import ThreadedServer
 from rpyc import SlaveService
-from nose import SkipTest
-
-# travis: "Network is unreachable", 
https://travis-ci.org/tomerfiliba/rpyc/jobs/108231239#L450
-raise SkipTest("requires IPv6")
 
 
+# travis: "Network is unreachable", 
https://travis-ci.org/tomerfiliba/rpyc/jobs/108231239#L450
+@unittest.skip("requires IPv6")
 class Test_IPv6(unittest.TestCase):
     def setUp(self):
         self.server = ThreadedServer(SlaveService, port=0, ipv6=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_netref_hierachy.py 
new/rpyc-4.1.4/tests/test_netref_hierachy.py
--- old/rpyc-4.1.1/tests/test_netref_hierachy.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/rpyc-4.1.4/tests/test_netref_hierachy.py        2020-01-30 
07:26:06.000000000 +0100
@@ -0,0 +1,126 @@
+import os
+import rpyc
+import tempfile
+from rpyc.utils.server import ThreadedServer, ThreadPoolServer
+from rpyc import SlaveService
+import unittest
+
+
+class MyMeta(type):
+
+    def spam(self):
+        return self.__name__ * 5
+
+
+class MyClass(object):
+    __metaclass__ = MyMeta
+
+
+class MyService(rpyc.Service):
+    on_connect_called = False
+    on_disconnect_called = False
+
+    def on_connect(self, conn):
+        self.on_connect_called = True
+
+    def on_disconnect(self, conn):
+        self.on_disconnect_called = True
+
+    def exposed_distance(self, p1, p2):
+        x1, y1 = p1
+        x2, y2 = p2
+        return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
+
+    def exposed_getlist(self):
+        return [
+            1, 2, 3]
+
+    def foobar(self):
+        assert False
+
+    def exposed_getmeta(self):
+        return MyClass()
+
+    def exposed_instance(self, inst, cls):
+        return isinstance(inst, cls)
+
+
+class Test_Netref_Hierarchy(unittest.TestCase):
+
+    def setUp(self):
+        self.server = ThreadedServer(SlaveService, port=18878, 
auto_register=False)
+        self.server.logger.quiet = False
+        self.server._start_in_thread()
+
+    def tearDown(self):
+        self.server.close()
+
+    def test_instancecheck_across_connections(self):
+        conn = rpyc.classic.connect('localhost', port=18878)
+        conn2 = rpyc.classic.connect('localhost', port=18878)
+        conn.execute('import test_magic')
+        conn2.execute('import test_magic')
+        foo = conn.modules.test_magic.Foo()
+        bar = conn.modules.test_magic.Bar()
+        self.assertTrue(isinstance(foo, conn.modules.test_magic.Foo))
+        self.assertTrue(isinstance(bar, conn2.modules.test_magic.Bar))
+        self.assertFalse(isinstance(bar, conn.modules.test_magic.Foo))
+        with self.assertRaises(TypeError):
+            isinstance(conn.modules.test_magic.Foo, bar)
+        conn.close()
+        conn2.close()
+
+    def test_classic(self):
+        conn = rpyc.classic.connect_thread()
+        x = conn.builtin.list((1, 2, 3, 4))
+        self.assertTrue(isinstance(x, list))
+        self.assertTrue(isinstance(x, rpyc.BaseNetref))
+        with self.assertRaises(TypeError):
+            isinstance([], x)
+        i = 0
+        self.assertTrue(type(x).__getitem__(x, i) == x.__getitem__(i))
+        _builtins = conn.modules.builtins if rpyc.lib.compat.is_py_3k else 
conn.modules.__builtin__
+        self.assertEqual(repr(_builtins.float.__class__), repr(type))
+        self.assertEqual(repr(type(_builtins.float)), 
repr(type(_builtins.type)))
+
+    def test_instancecheck_list(self):
+        service = MyService()
+        conn = rpyc.connect_thread(remote_service=service)
+        conn.root
+        remote_list = conn.root.getlist()
+        self.assertTrue(conn.root.instance(remote_list, list))
+        conn.close()
+
+    def test_StandardError(self):
+        conn = rpyc.classic.connect_thread()
+        _builtins = conn.modules.builtins if rpyc.lib.compat.is_py_3k else 
conn.modules.__builtin__
+        self.assertTrue(isinstance(_builtins.Exception(), 
_builtins.BaseException))
+        self.assertTrue(isinstance(_builtins.Exception(), _builtins.Exception))
+        self.assertTrue(isinstance(_builtins.Exception(), BaseException))
+        self.assertTrue(isinstance(_builtins.Exception(), Exception))
+
+    def test_modules(self):
+        """
+        >>> type(sys)
+        <type 'module'>  # base case
+        >>> type(conn.modules.sys)
+        <netref class 'rpyc.core.netref.__builtin__.module'>  # matches base 
case
+        >>> sys.__class__
+        <type 'module'>  # base case
+        >>> conn.modules.sys.__class__
+        <type 'module'>  # matches base case
+        >>> type(sys.__class__)
+        <type 'type'>  # base case
+        >>> type(conn.modules.sys.__class__)
+        <netref class 'rpyc.core.netref.__builtin__.module'>  # doesn't match. 
 Should be a netref class of "type" (or maybe just <type 'type'> itself?)
+        """
+        import sys
+        conn = rpyc.classic.connect_thread()
+        self.assertEqual(repr(sys.__class__), repr(conn.modules.sys.__class__))
+        # _builtin = sys.modules['builtins' if rpyc.lib.compat.is_py_3k else 
'__builtins__'].__name__
+        # self.assertEqual(repr(type(conn.modules.sys)), "<netref class 
'rpyc.core.netref.{}.module'>".format(_builtin))
+        # self.assertEqual(repr(type(conn.modules.sys.__class__)), "<netref 
class 'rpyc.core.netref.{}.type'>".format(_builtin))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_remoting.py 
new/rpyc-4.1.4/tests/test_remoting.py
--- old/rpyc-4.1.1/tests/test_remoting.py       2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/tests/test_remoting.py       2020-01-30 07:26:06.000000000 
+0100
@@ -2,7 +2,6 @@
 import tempfile
 import shutil
 import unittest
-from nose import SkipTest
 import rpyc
 
 
@@ -32,16 +31,17 @@
 
         shutil.rmtree(base)
 
+    @unittest.skip("TODO: upload a package and a module")
     def test_distribution(self):
-        raise SkipTest("TODO: upload a package and a module")
+        pass
 
+    @unittest.skip("Requires manual testing atm")
     def test_interactive(self):
-        raise SkipTest("Need to be manually")
         print("type Ctrl+D to exit (Ctrl+Z on Windows)")
         rpyc.classic.interact(self.conn)
 
+    @unittest.skip("Requires manual testing atm")
     def test_post_mortem(self):
-        raise SkipTest("Need to be manually")
         try:
             self.conn.modules.sys.path[100000]
         except IndexError:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_rpyc_over_rpyc.py 
new/rpyc-4.1.4/tests/test_rpyc_over_rpyc.py
--- old/rpyc-4.1.1/tests/test_rpyc_over_rpyc.py 1970-01-01 01:00:00.000000000 
+0100
+++ new/rpyc-4.1.4/tests/test_rpyc_over_rpyc.py 2020-01-30 07:26:06.000000000 
+0100
@@ -0,0 +1,102 @@
+import rpyc
+from rpyc.utils.server import ThreadedServer
+import unittest
+
+CONNECT_CONFIG = {"allow_setattr": True}
+
+
+class Fee(object):
+
+    def __init__(self, msg="Fee"):
+        self._msg = msg
+
+    @property
+    def exposed_msg(self):
+        return self._msg
+
+    @exposed_msg.setter
+    def exposed_msg(self, value):
+        self._msg = value
+
+    def __str__(self):
+        return str(self._msg)
+
+    def __add__(self, rhs):
+        return self.__str__() + str(rhs)
+
+
+class Service(rpyc.Service):
+
+    PORT = 18878
+
+    def exposed_fee(self, arg):
+        return arg
+
+    def exposed_fee_str(self, arg):
+        return str(arg)
+
+    def exposed_foe_update(self, arg, msg):
+        arg.msg = arg.msg + " foe" + msg
+        return arg
+
+
+class Intermediate(rpyc.Service):
+
+    PORT = 18879
+
+    def exposed_fee(self, arg):
+        with rpyc.connect("localhost", port=Service.PORT, 
config=CONNECT_CONFIG) as conn:
+            return conn.root.fee(arg)
+
+    def exposed_fee_str(self, arg):
+        with rpyc.connect("localhost", port=Service.PORT) as conn:
+            return conn.root.fee_str(arg)
+
+    def exposed_fie_update(self, arg):
+        arg.msg = arg.msg + " fie"
+        with rpyc.connect("localhost", port=Service.PORT, 
config=CONNECT_CONFIG) as conn:
+            return conn.root.foe_update(arg, " foo bar")
+
+
+class Test_rpyc_over_rpyc(unittest.TestCase):
+    """Issue #346 shows that exceptions are being raised when an RPyC service 
method
+    calls another RPyC service, forwarding a non-trivial (and thus given as a 
proxy) argument.
+    """
+
+    def setUp(self):
+        self.server = ThreadedServer(Service, port=Service.PORT, 
auto_register=False)
+        self.i_server = ThreadedServer(Intermediate, port=Intermediate.PORT,
+                                       auto_register=False, 
protocol_config=CONNECT_CONFIG)
+        self.server._start_in_thread()
+        self.i_server._start_in_thread()
+        self.conn = rpyc.connect("localhost", port=Intermediate.PORT, 
config=CONNECT_CONFIG)
+
+    def tearDown(self):
+        self.conn.close()
+        self.server.close()
+        self.i_server.close()
+
+    def test_immutable_object_return(self):
+        """Tests using rpyc over rpyc---issue #346 reported traceback for this 
use case"""
+        obj = Fee()
+        result = self.conn.root.fee_str(obj)
+        self.assertEqual(str(obj), "Fee", "String representation of obj should 
not have changed")
+        self.assertEqual(str(result), "Fee", "String representation of result 
should be the same as obj")
+
+    def test_return_of_unmodified_parameter(self):
+        obj = Fee()
+        original_obj_id = id(obj)
+        result = self.conn.root.fee(obj)
+        self.assertEqual(str(obj), "Fee", "String representation of obj should 
not have changed")
+        self.assertEqual(id(result), original_obj_id, "Unboxing of result 
should be bound to the same object as obj")
+
+    def test_return_of_modified_parameter(self):
+        obj = Fee()
+        original_obj_id = id(obj)
+        result = self.conn.root.fie_update(obj)
+        self.assertEqual(str(obj), "Fee fie foe foo bar", "String 
representation of obj should have changed")
+        self.assertEqual(id(result), original_obj_id, "Unboxing of result 
should be bound to the same object as obj")
+
+
+if __name__ == "__main__":
+    unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_service_pickle.py 
new/rpyc-4.1.4/tests/test_service_pickle.py
--- old/rpyc-4.1.1/tests/test_service_pickle.py 2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/tests/test_service_pickle.py 2020-01-30 07:26:06.000000000 
+0100
@@ -4,13 +4,15 @@
 import timeit
 import rpyc
 import unittest
-from nose import SkipTest
 import cfg_tests
 try:
     import pandas as pd
     import numpy as np
+    _pampy_import_failed = False
 except Exception:
-    raise SkipTest("Requires pandas, numpy, and tables")
+    pd = None
+    np = None
+    _pampy_import_failed = True
 
 
 DF_ROWS = 2000
@@ -30,10 +32,14 @@
     def exposed_write_data(self, dataframe):
         rpyc.classic.obtain(dataframe)
 
+    def exposed_get(self):
+        return np.random.rand(3, 3)
+
     def exposed_ping(self):
         return "pong"
 
 
+@unittest.skipIf(_pampy_import_failed, "Pandas & numpy are not available")
 class TestServicePickle(unittest.TestCase):
     """Issues #323 and #329 showed for large objects there is an excessive 
number of round trips.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_ssl.py 
new/rpyc-4.1.4/tests/test_ssl.py
--- old/rpyc-4.1.1/tests/test_ssl.py    2019-07-27 08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/tests/test_ssl.py    2020-01-30 07:26:06.000000000 +0100
@@ -1,17 +1,18 @@
 import rpyc
 import os
 import unittest
-import nose
 from rpyc.utils.authenticators import SSLAuthenticator
 from rpyc.utils.server import ThreadedServer
 from rpyc import SlaveService
 
 try:
     import ssl  # noqa
+    _ssl_import_failed = False
 except ImportError:
-    raise nose.SkipTest("requires ssl")
+    _ssl_import_failed = True
 
 
+@unittest.skipIf(_ssl_import_failed, "Ssl not available")
 class Test_SSL(unittest.TestCase):
     '''
     created key like that
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_teleportation.py 
new/rpyc-4.1.4/tests/test_teleportation.py
--- old/rpyc-4.1.1/tests/test_teleportation.py  2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/tests/test_teleportation.py  2020-01-30 07:26:06.000000000 
+0100
@@ -3,8 +3,10 @@
 import sys
 import os
 import rpyc
+import types
 import unittest
 from rpyc.utils.teleportation import export_function, import_function
+from rpyc.lib.compat import is_py_3k, is_py_gte38
 from rpyc.utils.classic import teleport_function
 
 
@@ -76,6 +78,40 @@
         self.assertEqual(foo_(), 43)
         self.assertEqual(bar_(), 42)
 
+    def test_compat(self):  # assumes func has only brineable types
+
+        def get37_schema(cobj):
+            return (cobj.co_argcount, 0, cobj.co_nlocals, cobj.co_stacksize,
+                    cobj.co_flags, cobj.co_code, cobj.co_consts, 
cobj.co_names, cobj.co_varnames,
+                    cobj.co_filename, cobj.co_name, cobj.co_firstlineno, 
cobj.co_lnotab,
+                    cobj.co_freevars, cobj.co_cellvars)
+
+        def get38_schema(cobj):
+            return (cobj.co_argcount, 2, cobj.co_kwonlyargcount, 
cobj.co_nlocals,
+                    cobj.co_stacksize, cobj.co_flags, cobj.co_code, 
cobj.co_consts, cobj.co_names,
+                    cobj.co_varnames, cobj.co_filename, cobj.co_name, 
cobj.co_firstlineno, cobj.co_lnotab,
+                    cobj.co_freevars, cobj.co_cellvars)
+
+        if is_py_3k:
+            pow37 = lambda x, y : x ** y  # noqa
+            pow38 = lambda x, y : x ** y  # noqa
+            export37 = get37_schema(pow37.__code__)
+            export38 = get38_schema(pow38.__code__)
+            schema37 = (pow37.__name__, pow37.__module__, pow37.__defaults__, 
export37)
+            schema38 = (pow38.__name__, pow38.__module__, pow38.__defaults__, 
export38)
+            pow37_netref = 
self.conn.modules["rpyc.utils.teleportation"].import_function(schema37)
+            pow38_netref = 
self.conn.modules["rpyc.utils.teleportation"].import_function(schema38)
+            self.assertEquals(pow37_netref(2, 3), pow37(2, 3))
+            self.assertEquals(pow38_netref(2, 3), pow38(2, 3))
+            self.assertEquals(pow37_netref(x=2, y=3), pow37(x=2, y=3))
+            if not is_py_gte38:
+                return  # skip remained of tests for 3.7
+            pow38.__code__ = types.CodeType(*export38)  # pow38 = lambda x, y, 
/: x ** y
+            with self.assertRaises(TypeError):  # show local behavior
+                pow38(x=2, y=3)
+            with self.assertRaises(TypeError):
+                pow38_netref(x=2, y=3)
+
 
 if __name__ == "__main__":
     unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_threaded_server.py 
new/rpyc-4.1.4/tests/test_threaded_server.py
--- old/rpyc-4.1.1/tests/test_threaded_server.py        2019-07-27 
08:52:40.000000000 +0200
+++ new/rpyc-4.1.4/tests/test_threaded_server.py        2020-01-30 
07:26:06.000000000 +0100
@@ -25,21 +25,6 @@
         self.assertEqual(conn.eval("1+x"), 6)
         conn.close()
 
-    def test_instancecheck_across_connections(self):
-        conn = rpyc.classic.connect("localhost", port=18878)
-        conn2 = rpyc.classic.connect("localhost", port=18878)
-        conn.execute("import test_magic")
-        conn2.execute("import test_magic")
-        foo = conn.modules.test_magic.Foo()
-        bar = conn.modules.test_magic.Bar()
-        self.assertTrue(isinstance(foo, conn.modules.test_magic.Foo))
-        self.assertTrue(isinstance(bar, conn2.modules.test_magic.Bar))
-        self.assertFalse(isinstance(bar, conn.modules.test_magic.Foo))
-        with self.assertRaises(TypeError):
-            isinstance(conn.modules.test_magic.Foo, bar)
-        conn.close()
-        conn2.close()
-
 
 class Test_ThreadedServerOverUnixSocket(unittest.TestCase):
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/rpyc-4.1.1/tests/test_win32pipes.py 
new/rpyc-4.1.4/tests/test_win32pipes.py
--- old/rpyc-4.1.1/tests/test_win32pipes.py     2019-07-27 08:52:40.000000000 
+0200
+++ new/rpyc-4.1.4/tests/test_win32pipes.py     2020-01-30 07:26:06.000000000 
+0100
@@ -5,11 +5,8 @@
 import time
 import unittest
 
-from nose import SkipTest
-if sys.platform != "win32":
-    raise SkipTest("Requires windows")
-
 
+@unittest.skipIf(sys.platform != "win32", "Requires windows")
 class Test_Pipes(unittest.TestCase):
     def test_basic_io(self):
         p1, p2 = PipeStream.create_pair()
@@ -38,6 +35,7 @@
         server_thread.join()
 
 
+@unittest.skipIf(sys.platform != "win32", "Requires windows")
 class Test_NamedPipe(object):
     def setUp(self):
         self.pipe_server_thread = rpyc.spawn(self.pipe_server)


Reply via email to