Repository: mosecorg/mosec

Issue:
[FEATURE] Worker with msgpack serialization

Comments:

Patch:
diff --git a/mosec/errors.py b/mosec/errors.py
index d189be1..143bd94 100644
--- a/mosec/errors.py
+++ b/mosec/errors.py
@@ -25,6 +25,17 @@ implemented by users), the `ValidationError` should be raised.
 """
 
 
+class EncodingError(Exception):
+    """Serialization error.
+
+    The `EncodingError` should be raised in user-implemented codes when
+    the serialization for the response bytes fails. This error will set
+    to status code to
+    [HTTP 500](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500)
+    in the response.
+    """
+
+
 class DecodingError(Exception):
     """De-serialization error.
 
diff --git a/mosec/mixin/__init__.py b/mosec/mixin/__init__.py
new file mode 100644
index 0000000..fc25af2
--- /dev/null
+++ b/mosec/mixin/__init__.py
@@ -0,0 +1,19 @@
+# Copyright 2022 MOSEC Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Provide useful mixin to extend MOSEC."""
+
+from .msgpack_worker import MsgpackMixin
+
+__all__ = ["MsgpackMixin"]
diff --git a/mosec/mixin/msgpack_worker.py b/mosec/mixin/msgpack_worker.py
new file mode 100644
index 0000000..d0a4799
--- /dev/null
+++ b/mosec/mixin/msgpack_worker.py
@@ -0,0 +1,74 @@
+# Copyright 2022 MOSEC Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""MOSEC msgpack worker mixin.
+
+Features:
+
+    * deserialize request body with msgpack
+    * serialize response body with msgpack
+"""
+
+
+import warnings
+from typing import Any
+
+from ..errors import DecodingError, EncodingError
+
+try:
+    import msgpack  # type: ignore
+except ImportError:
+    warnings.warn("msgpack is required for MsgpackWorker", ImportWarning)
+
+
+class MsgpackMixin:
+    """Msgpack worker mixin interface."""
+
+    # pylint: disable=no-self-use
+
+    def serialize(self, data: Any) -> bytes:
+        """Serialize with msgpack for the last stage (egress).
+
+        Arguments:
+            data: the [_*same type_][mosec.worker.Worker--note]
+
+        Returns:
+            the bytes you want to put into the response body
+
+        Raises:
+            EncodingError: if the data cannot be serialized with msgpack
+        """
+        try:
+            data_bytes = msgpack.packb(data)
+        except Exception as err:
+            raise EncodingError from err
+        return data_bytes
+
+    def deserialize(self, data: bytes) -> Any:
+        """Deserialize method for the first stage (ingress).
+
+        Arguments:
+            data: the raw bytes extracted from the request body
+
+        Returns:
+            [_*same type_][mosec.worker.Worker--note]
+
+        Raises:
+            DecodingError: if the data cannot be deserialized with msgpack
+        """
+        try:
+            data_msg = msgpack.unpackb(data, use_list=False)
+        except Exception as err:
+            raise DecodingError from err
+        return data_msg
diff --git a/mosec/worker.py b/mosec/worker.py
index 9ea69f6..c3314d0 100644
--- a/mosec/worker.py
+++ b/mosec/worker.py
@@ -28,7 +28,7 @@ import logging
 import pickle
 from typing import Any
 
-from .errors import DecodingError
+from .errors import DecodingError, EncodingError
 
 logger = logging.getLogger(__name__)
 
@@ -124,12 +124,12 @@ class Worker(abc.ABC):
             the bytes you want to put into the response body
 
         Raises:
-            ValueError: if the data cannot be serialized with JSON
+            EncodingError: if the data cannot be serialized with JSON
         """
         try:
             data_bytes = json.dumps(data, indent=2).encode()
         except Exception as err:
-            raise ValueError from err
+            raise EncodingError from err
         return data_bytes
 
     def deserialize(self, data: bytes) -> Any:
diff --git a/requirements/plugin.txt b/requirements/plugin.txt
index 1e60e1c..facba2e 100644
--- a/requirements/plugin.txt
+++ b/requirements/plugin.txt
@@ -1,1 +1,2 @@
 pyarrow>=0.6.1
+msgpack>=1.0.2


Test patch:
diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py
index 80ab1b2..49b8174 100644
--- a/tests/test_coordinator.py
+++ b/tests/test_coordinator.py
@@ -29,6 +29,7 @@ import msgpack  # type: ignore
 import pytest
 
 from mosec.coordinator import PROTOCOL_TIMEOUT, STAGE_EGRESS, STAGE_INGRESS, Coordinator
+from mosec.mixin import MsgpackMixin
 from mosec.protocol import Protocol, _recv_all
 from mosec.worker import Worker
 
@@ -65,14 +66,8 @@ class EchoWorkerJSON(Worker):
         return data
 
 
-class EchoWorkerMSGPACK(EchoWorkerJSON):
-    @staticmethod
-    def deserialize(data):
-        return msgpack.unpackb(data)
-
-    @staticmethod
-    def serialize(data):
-        return msgpack.packb(data)
+class EchoWorkerMSGPACK(MsgpackMixin, EchoWorkerJSON):
+    """"""
 
 
 @pytest.fixture