Repository: OpenMined/PySyft
Pull request #1682 (2018-11-15 09:30:26)

Issue #1668
This solves the problem mentioned in #1668. Added tests and changed documentation accordingly. 

It could be discussed whether the method `_detail_collection_tuple` should be renamed to `_detail_tuple`. 


Patch:
diff --git a/syft/__init__.py b/syft/__init__.py
index 617c31c0d6..c351b2d083 100644
--- a/syft/__init__.py
+++ b/syft/__init__.py
@@ -3,4 +3,4 @@ from . import frameworks
 from . import workers
 from . import serde
 
-__all__ = ['frameworks', 'workers', 'serde']
\ No newline at end of file
+__all__ = ["frameworks", "workers", "serde"]
diff --git a/syft/frameworks/__init__.py b/syft/frameworks/__init__.py
index 525a0dfdd4..b746581235 100644
--- a/syft/frameworks/__init__.py
+++ b/syft/frameworks/__init__.py
@@ -1,3 +1,3 @@
 from . import torch
 
-__all__ = ['torch']
\ No newline at end of file
+__all__ = ["torch"]
diff --git a/syft/frameworks/torch/__init__.py b/syft/frameworks/torch/__init__.py
index 668c94418b..878586dd53 100644
--- a/syft/frameworks/torch/__init__.py
+++ b/syft/frameworks/torch/__init__.py
@@ -1,2 +1,2 @@
 from . import tensors
-from . import mpc
\ No newline at end of file
+from . import mpc
diff --git a/syft/frameworks/torch/mpc/__init__.py b/syft/frameworks/torch/mpc/__init__.py
index 76a2273cc1..a4f83637be 100644
--- a/syft/frameworks/torch/mpc/__init__.py
+++ b/syft/frameworks/torch/mpc/__init__.py
@@ -1,2 +1,2 @@
 from . import snn
-from . import spdz
\ No newline at end of file
+from . import spdz
diff --git a/syft/objects.py b/syft/objects.py
index fdb3af4eab..37bd399245 100644
--- a/syft/objects.py
+++ b/syft/objects.py
@@ -1,5 +1,6 @@
 import enum
 
+
 class ObjType(enum.Enum):
 
     Tensor = 0
diff --git a/syft/serde.py b/syft/serde.py
index 09a5878efb..aab03a10aa 100644
--- a/syft/serde.py
+++ b/syft/serde.py
@@ -76,7 +76,6 @@ def deserialize(binary: bin, compressed=True) -> object:
     # object (or nested dict/collection of python objects)
     simple_objects = msgpack.loads(binary)
 
-
     # 3) Detail
     # This function converts typed, simple objects into their more
     # complex (and difficult to serialize) counterparts which the
@@ -213,6 +212,34 @@ def _detail_collection(my_collection: Collection) -> Collection:
     return my_type(pieces)
 
 
+def _detail_collection_tuple(my_tuple: tuple) -> tuple:
+
+    """
+    This function is designed to operate in the opposite direction of
+    _simplify_collection. It takes a tuple of simple python objects
+    and iterates through it to determine whether objects in the collection
+    need to be converted into more advanced types. In particular, it
+    converts binary objects into torch Tensors where appropriate.
+    This is only applicable to tuples. They need special handling because
+    `msgpack` is encoding a tuple as a list.
+
+    Args:
+        tuple: a collection of simple python objects (including binary).
+
+    Returns:
+        tuple: a collection of the same type as the input where the objects
+            in the collection have been detailed.
+    """
+
+    pieces = list()
+
+    # Step 1: deserialize each part of the collection
+    for part in my_tuple:
+        pieces.append(_detail(part))
+
+    return tuple(pieces)
+
+
 # Dictionaries
 
 
@@ -307,7 +334,7 @@ def _detail(obj: object) -> object:
 
 detailers = [
     _detail_torch_tensor,
-    _detail_collection,
+    _detail_collection_tuple,
     _detail_collection,
     _detail_collection,
     _detail_dictionary,
diff --git a/syft/workers/__init__.py b/syft/workers/__init__.py
index be06db3963..79ece3e55f 100644
--- a/syft/workers/__init__.py
+++ b/syft/workers/__init__.py
@@ -1,4 +1,3 @@
-
 # Section 1: General Imports
 import enum
 
@@ -6,4 +5,4 @@ import enum
 from . import base
 from . import virtual
 
-__all__ = ['base', 'virtual']
\ No newline at end of file
+__all__ = ["base", "virtual"]
diff --git a/syft/workers/base.py b/syft/workers/base.py
index 6e2e9598b1..6abd121e86 100644
--- a/syft/workers/base.py
+++ b/syft/workers/base.py
@@ -9,6 +9,7 @@ MSGTYPE_OBJ = 2
 MSGTYPE_OBJ_REQ = 3
 MSGTYPE_EXCEPTION = 4
 
+
 class BaseWorker(ABC):
     """This is the class which contains functionality generic to all workers. Other workers will extend this
     class to inherit all functionality necessary for PySyft's protocol. Extensions of this class will
@@ -20,7 +21,6 @@ class BaseWorker(ABC):
     other workers interact with objects owned by itself. Objects are most frequently tensors but they
     can be of any type supported by the PySyft protocol."""
 
-
     def __init__(self):
 
         # This is the core object in every BaseWorker instantiation, a collection of
@@ -69,7 +69,7 @@ class BaseWorker(ABC):
         response = self._message_router[msg_type](contents)
 
         # Step 2: If response is none, set default
-        if(response is None):
+        if response is None:
             response = 0
 
         # Step 3: Serialize the message to simple python objects
@@ -77,7 +77,6 @@ class BaseWorker(ABC):
 
         return bin_response
 
-
     ## SECTION: recv_msg() uses self._message_router to route to these methods
     # Each method corresponds to a MsgType enum.
 
@@ -93,4 +92,4 @@ class BaseWorker(ABC):
         return self.send_msg(MSGTYPE_OBJ, obj, location)
 
     def request_obj(self, obj_id, location):
-        return self.send_msg(MSGTYPE_OBJ_REQ, obj_id, location)
\ No newline at end of file
+        return self.send_msg(MSGTYPE_OBJ_REQ, obj_id, location)
diff --git a/syft/workers/virtual.py b/syft/workers/virtual.py
index 073e774aa8..07bfd3e123 100644
--- a/syft/workers/virtual.py
+++ b/syft/workers/virtual.py
@@ -1,9 +1,9 @@
 from .base import BaseWorker
 
-class VirtualWorker(BaseWorker):
 
+class VirtualWorker(BaseWorker):
     def _send_msg(self, message, location):
         return location._recv_msg(message)
 
     def _recv_msg(self, message):
-        return self.recv_msg(message)
\ No newline at end of file
+        return self.recv_msg(message)


Test patch:
diff --git a/test/test_serde.py b/test/test_serde.py
index a1b2c33676..3b0441cdce 100644
--- a/test/test_serde.py
+++ b/test/test_serde.py
@@ -55,3 +55,19 @@ class TestSerde(TestCase):
         t_serialized = serialize(t, compress=False)
         t_serialized_deserialized = deserialize(t_serialized, compressed=False)
         assert (t == t_serialized_deserialized).all()
+
+    def test_tuple_serde(self):
+        tuple = (1, 2)
+        tuple_serialized = serialize(tuple, compress=False)
+        tuple_serialized_deserialized = deserialize(tuple_serialized, compressed=False)
+        assert tuple == tuple_serialized_deserialized
+
+        tensor_one = tensor(numpy.random.random((100, 100)))
+        tensor_two = tensor(numpy.random.random((100, 100)))
+        tuple = (tensor_one, tensor_two)
+        tuple_serialized = serialize(tuple, compress=False)
+        tuple_serialized_deserialized = deserialize(tuple_serialized, compressed=False)
+        # `assert tuple_serialized_deserialized == tuple` does not work, therefore it's split into 3 assertions
+        assert type(tuple_serialized_deserialized) == type(tuple)
+        assert (tuple_serialized_deserialized[0] == tensor_one).all()
+        assert (tuple_serialized_deserialized[1] == tensor_two).all()