diff --git a/rpyc/utils/teleportation.py b/rpyc/utils/teleportation.py index 5fa5f2b0..6ed8844a 100644 --- a/rpyc/utils/teleportation.py +++ b/rpyc/utils/teleportation.py @@ -1,9 +1,5 @@ import opcode -import sys -try: - import __builtin__ -except ImportError: - import builtins as __builtin__ # noqa: F401 + from rpyc.lib.compat import is_py_gte38 from types import CodeType, FunctionType from rpyc.core import brine, netref @@ -67,16 +63,21 @@ def _export_codeobj(cobj): def export_function(func): - func_closure = func.__closure__ - func_code = func.__code__ - func_defaults = func.__defaults__ - - if func_closure: + closure = func.__closure__ + code = func.__code__ + defaults = func.__defaults__ + kwdefaults = func.__kwdefaults__ + if kwdefaults is not None: + kwdefaults = tuple(kwdefaults.items()) + + if closure: raise TypeError("Cannot export a function closure") - if not brine.dumpable(func_defaults): - raise TypeError("Cannot export a function with non-brinable defaults (func_defaults)") + if not brine.dumpable(defaults): + raise TypeError("Cannot export a function with non-brinable defaults (__defaults__)") + if not brine.dumpable(kwdefaults): + raise TypeError("Cannot export a function with non-brinable defaults (__kwdefaults__)") - return func.__name__, func.__module__, func_defaults, _export_codeobj(func_code)[1] + return func.__name__, func.__module__, defaults, kwdefaults, _export_codeobj(code)[1] def _import_codetup(codetup): @@ -106,7 +107,7 @@ def _import_codetup(codetup): def import_function(functup, globals=None, def_=True): - name, modname, defaults, codetup = functup + name, modname, defaults, kwdefaults, codetup = functup if globals is None: try: mod = __import__(modname, None, None, "*") @@ -120,6 +121,8 @@ def import_function(functup, globals=None, def_=True): globals.setdefault('__builtins__', __builtins__) codeobj = _import_codetup(codetup) funcobj = FunctionType(codeobj, globals, name, defaults) + if kwdefaults is not None: + funcobj.__kwdefaults__ = {t[0]: t[1] for t in kwdefaults} if def_: globals[name] = funcobj return funcobj diff --git a/tests/test_teleportation.py b/tests/test_teleportation.py index c49aead0..4a15b52b 100644 --- a/tests/test_teleportation.py +++ b/tests/test_teleportation.py @@ -26,6 +26,14 @@ def g(b): return g +def defaults(a=5, b="hi", c=(5.5, )): + return a, b, c + + +def kwdefaults(pos=5, *, a=42, b="bye", c=(12.4, )): + return pos, a, b, c + + def h(a): import os return a * os.getpid() @@ -82,6 +90,14 @@ def test_def(self): self.assertEqual(foo_(), 43) self.assertEqual(bar_(), 42) + def test_defaults(self): + defaults_ = teleport_function(self.conn, defaults) + self.assertEqual(defaults_(), defaults()) + + def test_kwdefaults(self): + kwdefaults_ = teleport_function(self.conn, kwdefaults) + self.assertEqual(kwdefaults_(), kwdefaults()) + def test_compat(self): # assumes func has only brineable types def get37_schema(cobj): @@ -101,8 +117,8 @@ def get38_schema(cobj): 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) + schema37 = (pow37.__name__, pow37.__module__, pow37.__defaults__, pow37.__kwdefaults__, export37) + schema38 = (pow38.__name__, pow38.__module__, pow38.__defaults__, pow38.__kwdefaults__, export38) pow37_netref = self.conn.modules["rpyc.utils.teleportation"].import_function(schema37) pow38_netref = self.conn.modules["rpyc.utils.teleportation"].import_function(schema38) self.assertEqual(pow37_netref(2, 3), pow37(2, 3))