Repository: SeldonIO/MLServer

Issue:
convertor from grpc response not implemented
As mentioned https://github.com/SeldonIO/MLServer/pull/727#discussion_r972003311 the convertor from grpc output is not implemented.

This isn't working as the following line:
```python
from mlserver.grpc.converters import ModelInferResponseConverter
from mlserver.codecs.string import StringRequestCodec

inference_response = ModelInferResponseConverter.to_types(response)
```
is calling the following, which the `inference_response` output for the subsequent line of the scripts will be `None`
https://github.com/SeldonIO/MLServer/blob/fac90c83a3536dbbea9e7075c590e33ace424537/mlserver/grpc/converters.py#L301


Discussion:

Resolving patch:
diff --git a/mlserver/grpc/converters.py b/mlserver/grpc/converters.py
--- a/mlserver/grpc/converters.py
+++ b/mlserver/grpc/converters.py
@@ -44,7 +44,11 @@ class ServerMetadataResponseConverter:
     def to_types(
         cls, pb_object: pb.ServerMetadataResponse
     ) -> types.MetadataServerResponse:
-        pass
+        return types.MetadataServerResponse(
+            name=pb_object.name,
+            version=pb_object.version,
+            extensions=list(pb_object.extensions),
+        )
 
     @classmethod
     def from_types(
@@ -62,7 +66,24 @@ class ModelMetadataResponseConverter:
     def to_types(
         cls, pb_object: pb.ModelMetadataResponse
     ) -> types.MetadataModelResponse:
-        pass
+        metadata = types.MetadataModelResponse(
+            name=pb_object.name,
+            platform=pb_object.platform,
+            versions=list(pb_object.versions),
+            parameters=ParametersConverter.to_types(pb_object.parameters),
+        )
+
+        if pb_object.inputs:
+            metadata.inputs = [
+                TensorMetadataConverter.to_types(inp) for inp in pb_object.inputs
+            ]
+
+        if pb_object.outputs:
+            metadata.outputs = [
+                TensorMetadataConverter.to_types(out) for out in pb_object.outputs
+            ]
+
+        return metadata
 
     @classmethod
     def from_types(
@@ -98,7 +119,12 @@ class TensorMetadataConverter:
     def to_types(
         cls, pb_object: pb.ModelMetadataResponse.TensorMetadata
     ) -> types.MetadataTensor:
-        pass
+        return types.MetadataTensor(
+            name=pb_object.name,
+            datatype=pb_object.datatype,
+            shape=list(pb_object.shape),
+            parameters=ParametersConverter.to_types(pb_object.parameters),
+        )
 
     @classmethod
     def from_types(
@@ -146,8 +172,17 @@ def to_types(cls, pb_object: pb.ModelInferRequest) -> types.InferenceRequest:
 
     @classmethod
     def from_types(
-        cls, type_object: types.InferenceRequest, model_name: str, model_version: str
+        cls,
+        type_object: types.InferenceRequest,
+        model_name: str,
+        model_version: str = "",
+        use_raw: bool = False,
     ) -> pb.ModelInferRequest:
+        if use_raw:
+            # Extract the raw data in advance, to ensure the `data` field of
+            # the input objects is empty
+            type_object.inputs, raw = extract_raw(type_object.inputs)  # type: ignore
+
         model_infer_request = pb.ModelInferRequest(
             model_name=model_name,
             model_version=model_version,
@@ -156,6 +191,9 @@ def from_types(
             ],
         )
 
+        if use_raw:
+            model_infer_request.raw_input_contents.extend(raw)
+
         if type_object.id is not None:
             model_infer_request.id = type_object.id
 
@@ -305,7 +343,27 @@ def _get_contents(cls, type_object: types.TensorData, datatype: str) -> dict:
 class ModelInferResponseConverter:
     @classmethod
     def to_types(cls, pb_object: pb.ModelInferResponse) -> types.InferenceResponse:
-        pass
+        inference_response = types.InferenceResponse.construct(
+            id=pb_object.id,
+            model_name=pb_object.model_name,
+            parameters=ParametersConverter.to_types(pb_object.parameters),
+            outputs=[
+                InferOutputTensorConverter.to_types(output)
+                for output in pb_object.outputs
+            ],
+        )
+
+        if pb_object.model_version:
+            inference_response.model_version = pb_object.model_version
+
+        if pb_object.raw_output_contents:
+            # Unpack and inject raw contents into `data` fields if present
+            inject_raw(
+                inference_response.outputs,  # type: ignore
+                pb_object.raw_output_contents,  # type: ignore
+            )
+
+        return inference_response
 
     @classmethod
     def from_types(


Test patch:
diff --git a/tests/grpc/test_converters.py b/tests/grpc/test_converters.py
--- a/tests/grpc/test_converters.py
+++ b/tests/grpc/test_converters.py
@@ -24,6 +24,15 @@ def test_servermetadataresponse_from_types(metadata_server_response):
     assert metadata.extensions == metadata_server_response.extensions
 
 
+def test_servermetadataresponse_to_types(metadata_server_response):
+    server_metadata_response = ServerMetadataResponseConverter.from_types(
+        metadata_server_response
+    )
+    metadata = ServerMetadataResponseConverter.to_types(server_metadata_response)
+
+    assert metadata == metadata_server_response
+
+
 def test_modelmetadataresponse_from_types(metadata_model_response):
     metadata = ModelMetadataResponseConverter.from_types(metadata_model_response)
 
@@ -42,6 +51,15 @@ def test_modelmetadataresponse_from_types(metadata_model_response):
         assert pb_obj.shape == ty_obj.shape
 
 
+def test_modelmetadataresponse_to_types(metadata_model_response):
+    model_metadata_response = ModelMetadataResponseConverter.from_types(
+        metadata_model_response
+    )
+    model_metadata = ModelMetadataResponseConverter.to_types(model_metadata_response)
+
+    assert model_metadata == metadata_model_response
+
+
 @pytest.mark.parametrize(
     "model_infer_request",
     [
@@ -92,6 +110,65 @@ def test_modelinferrequest_to_types(model_infer_request):
     assert dict(inference_request) == dict(expected)
 
 
+@pytest.mark.parametrize(
+    "use_raw, expected",
+    [
+        (
+            False,
+            pb.ModelInferRequest(
+                id="123",
+                model_name="sum-model",
+                inputs=[
+                    pb.ModelInferRequest.InferInputTensor(
+                        name="input-0",
+                        datatype="FP32",
+                        shape=[1],
+                        contents=pb.InferTensorContents(fp32_contents=[21.0]),
+                    )
+                ],
+            ),
+        ),
+        (
+            True,
+            pb.ModelInferRequest(
+                id="123",
+                model_name="sum-model",
+                inputs=[
+                    pb.ModelInferRequest.InferInputTensor(
+                        name="input-0",
+                        datatype="FP32",
+                        shape=[1],
+                        contents=pb.InferTensorContents(),
+                    )
+                ],
+                raw_input_contents=[b"\x00\x00\xa8A"],
+            ),
+        ),
+    ],
+)
+def test_modelinferrequest_from_types(use_raw, expected):
+    inference_request = types.InferenceRequest(
+        id="123",
+        inputs=[
+            types.RequestInput(
+                name="input-0",
+                datatype="FP32",
+                shape=[1],
+                data=types.TensorData.parse_obj([21.0]),
+            )
+        ],
+    )
+
+    model_infer_request = ModelInferRequestConverter.from_types(
+        inference_request, model_name="sum-model", use_raw=use_raw
+    )
+
+    assert type(model_infer_request) is pb.ModelInferRequest
+    assert json_format.MessageToDict(model_infer_request) == json_format.MessageToDict(
+        expected
+    )
+
+
 @pytest.mark.parametrize(
     "use_raw, expected",
     [
@@ -139,6 +216,58 @@ def test_modelinferresponse_from_types(inference_response, use_raw, expected):
     )
 
 
+@pytest.mark.parametrize(
+    "model_infer_response",
+    [
+        pb.ModelInferResponse(
+            id="",
+            model_name="foo",
+            outputs=[
+                pb.ModelInferResponse.InferOutputTensor(
+                    name="input-0",
+                    datatype="INT32",
+                    shape=[1, 3],
+                    parameters={"content_type": pb.InferParameter(string_param="np")},
+                    contents=pb.InferTensorContents(int_contents=[1, 2, 3]),
+                )
+            ],
+        ),
+        pb.ModelInferResponse(
+            id="",
+            model_name="foo",
+            outputs=[
+                pb.ModelInferResponse.InferOutputTensor(
+                    name="input-0",
+                    datatype="INT32",
+                    shape=[1, 3],
+                    parameters={"content_type": pb.InferParameter(string_param="np")},
+                    contents=pb.InferTensorContents(),
+                )
+            ],
+            raw_output_contents=[b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00"],
+        ),
+    ],
+)
+def test_modelinferresponse_to_types(model_infer_response):
+    expected = types.InferenceResponse(
+        id="",
+        model_name="foo",
+        outputs=[
+            types.ResponseOutput(
+                name="input-0",
+                datatype="INT32",
+                shape=[1, 3],
+                parameters=types.Parameters(content_type="np"),
+                data=[1, 2, 3],
+            )
+        ],
+    )
+
+    inference_response = ModelInferResponseConverter.to_types(model_infer_response)
+
+    assert inference_response == expected
+
+
 def test_parameters_to_types(grpc_parameters):
     parameters = ParametersConverter.to_types(grpc_parameters)