We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Support for NaN in np.ndarray codec?
Sending/receiving a `numpy` array containing NaN values to/from an mlserver with the `NumpyCodec` results in a `ValueError: Out of range float values are not JSON compliant` error. Is this a known limitation, and are there any good workarounds that would still allow me to use a pre-packaged server? I understand that it would probably be bad to assume than `NaN` can be mapped to `null` in the JSON, but maybe that could be an option?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mlserver/codecs/numpy.py`
Content:
```
1 import numpy as np
2 
3 from typing import Any
4 
5 from ..types import RequestInput, ResponseOutput, Parameters
6 
7 from .base import InputCodec, register_input_codec, register_request_codec
8 from .utils import SingleInputRequestCodec, InputOrOutput, inject_batch_dimension
9 from .lists import is_list_of
10 from .string import encode_str
11 
12 _DatatypeToNumpy = {
13     "BOOL": "bool",
14     "UINT8": "uint8",
15     "UINT16": "uint16",
16     "UINT32": "uint32",
17     "UINT64": "uint64",
18     "INT8": "int8",
19     "INT16": "int16",
20     "INT32": "int32",
21     "INT64": "int64",
22     "FP16": "float16",
23     "FP32": "float32",
24     "FP64": "float64",
25     "BYTES": "bytes",
26 }
27 
28 _NumpyToDatatype = {value: key for key, value in _DatatypeToNumpy.items()}
29 
30 # NOTE: numpy has more types than v2 protocol
31 _NumpyToDatatype["object"] = "BYTES"
32 _NumpyToDatatype["S"] = "BYTES"
33 _NumpyToDatatype["U"] = "BYTES"
34 
35 
36 def to_dtype(input_or_output: InputOrOutput) -> "np.dtype":
37     dtype = _DatatypeToNumpy[input_or_output.datatype]
38 
39     if input_or_output.datatype == "BYTES":
40         data = getattr(input_or_output.data, "__root__", input_or_output.data)
41         if is_list_of(data, str):
42             # Handle special case of strings being treated as Numpy arrays
43             return np.dtype(str)
44 
45         # bytes have variable size, so need to specify as part of type
46         # TODO: Make elem size variable (and not just the last dimension)
47         elem_size = input_or_output.shape[-1]
48         return np.dtype((dtype, elem_size))
49 
50     return np.dtype(dtype)
51 
52 
53 def to_datatype(dtype: np.dtype) -> str:
54     as_str = str(dtype)
55 
56     if as_str not in _NumpyToDatatype:
57         # If not present, try with kind
58         as_str = getattr(dtype, "kind")
59 
60     datatype = _NumpyToDatatype[as_str]
61 
62     return datatype
63 
64 
65 def _to_ndarray(input_or_output: InputOrOutput) -> np.ndarray:
66     data = getattr(input_or_output.data, "__root__", input_or_output.data)
67     dtype = to_dtype(input_or_output)
68 
69     if input_or_output.datatype == "BYTES":
70         if is_list_of(data, bytes):
71             # If the inputs is of type `BYTES`, there could be multiple "lists"
72             # serialised into multiple buffers.
73             # We will deserialise all of them and concatenate them together.
74             decoded = [np.frombuffer(buffer, dtype) for buffer in data]
75             return np.concatenate(decoded)
76 
77     return np.array(data, dtype)
78 
79 
80 def _encode_data(data: np.ndarray, datatype: str) -> list:
81     if datatype == "BYTES":
82         if np.issubdtype(data.dtype, str):
83             # Handle special case of a string Numpy array, where the diff elems
84             # need to be encoded as well
85             as_list = data.flatten().tolist()
86             return list(map(encode_str, as_list))
87 
88         if np.issubdtype(data.dtype, bytes):
89             # `tobytes` is way faster than tolist, although it's harder to serialise
90             # and only makes sense for actual bytes inputs (#253).
91             # Note that `.tobytes()` will return a single `bytes` payload, thus we
92             # need to encapsulate it into a list so that it's compatible.
93             return [data.tobytes()]
94 
95     return data.flatten().tolist()
96 
97 
98 @register_input_codec
99 class NumpyCodec(InputCodec):
100     """
101     Decodes an request input (response output) as a NumPy array.
102     """
103 
104     ContentType = "np"
105     TypeHint = np.ndarray
106 
107     @classmethod
108     def can_encode(csl, payload: Any) -> bool:
109         return isinstance(payload, np.ndarray)
110 
111     @classmethod
112     def encode_output(cls, name: str, payload: np.ndarray, **kwargs) -> ResponseOutput:
113         datatype = to_datatype(payload.dtype)
114 
115         shape = inject_batch_dimension(list(payload.shape))
116 
117         return ResponseOutput(
118             name=name,
119             datatype=datatype,
120             shape=shape,
121             data=_encode_data(payload, datatype),
122             parameters=Parameters(content_type=cls.ContentType),
123         )
124 
125     @classmethod
126     def decode_output(cls, response_output: ResponseOutput) -> np.ndarray:
127         return cls.decode_input(response_output)  # type: ignore
128 
129     @classmethod
130     def encode_input(cls, name: str, payload: np.ndarray, **kwargs) -> RequestInput:
131         output = cls.encode_output(name=name, payload=payload)
132 
133         return RequestInput(
134             name=output.name,
135             datatype=output.datatype,
136             shape=output.shape,
137             data=output.data,
138             parameters=Parameters(content_type=cls.ContentType),
139         )
140 
141     @classmethod
142     def decode_input(cls, request_input: RequestInput) -> np.ndarray:
143         model_data = _to_ndarray(request_input)
144 
145         # TODO: Check if reshape not valid
146         return model_data.reshape(request_input.shape)
147 
148 
149 @register_request_codec
150 class NumpyRequestCodec(SingleInputRequestCodec):
151     """
152     Decodes the first input (output) of request (response) as a NumPy array.
153     This codec can be useful for cases where the whole payload is a single
154     NumPy tensor.
155     """
156 
157     InputCodec = NumpyCodec
158     ContentType = NumpyCodec.ContentType
159
```

Path: `mlserver/codecs/pandas.py`
Content:
```
1 import pandas as pd
2 import numpy as np
3 
4 from typing import Optional, Any, List, Tuple
5 
6 from .base import RequestCodec, register_request_codec
7 from .numpy import to_datatype, to_dtype
8 from .string import encode_str, StringCodec
9 from .utils import get_decoded_or_raw, InputOrOutput, inject_batch_dimension
10 from .lists import ListElement
11 from ..types import (
12     InferenceRequest,
13     InferenceResponse,
14     RequestInput,
15     ResponseOutput,
16     Parameters,
17 )
18 
19 
20 def _to_series(input_or_output: InputOrOutput) -> pd.Series:
21     payload = get_decoded_or_raw(input_or_output)
22 
23     if input_or_output.datatype == "BYTES":
24         # Don't convert the dtype of BYTES
25         return pd.Series(payload)
26 
27     if isinstance(payload, np.ndarray):
28         # Necessary so that it's compatible with pd.Series
29         payload = list(payload)
30 
31     dtype = to_dtype(input_or_output)
32     return pd.Series(payload, dtype=dtype)
33 
34 
35 def _to_response_output(series: pd.Series, use_bytes: bool = True) -> ResponseOutput:
36     datatype = to_datatype(series.dtype)
37     data = series.tolist()
38     content_type = None
39 
40     if datatype == "BYTES":
41         data, content_type = _process_bytes(data, use_bytes)
42 
43     shape = inject_batch_dimension(list(series.shape))
44     parameters = None
45     if content_type:
46         parameters = Parameters(content_type=content_type)
47 
48     return ResponseOutput(
49         name=series.name,
50         shape=shape,
51         data=data,
52         datatype=datatype,
53         parameters=parameters,
54     )
55 
56 
57 def _process_bytes(
58     data: List[ListElement], use_bytes: bool = True
59 ) -> Tuple[List[ListElement], Optional[str]]:
60     # To ensure that "string" columns can be encoded in gRPC, we need to
61     # encode them as bytes.
62     # We'll also keep track of whether the list should be treated in the
63     # future as a list of strings.
64     processed = []
65     content_type: Optional[str] = StringCodec.ContentType
66     for elem in data:
67         converted = elem
68         if not isinstance(elem, str):
69             # There was a non-string element, so we can't determine a content
70             # type
71             content_type = None
72         elif use_bytes:
73             converted = encode_str(elem)
74 
75         processed.append(converted)
76 
77     return processed, content_type
78 
79 
80 @register_request_codec
81 class PandasCodec(RequestCodec):
82     """
83     Decodes a request (response) into a Pandas DataFrame, assuming each input
84     (output) head corresponds to a column of the DataFrame.
85     """
86 
87     ContentType = "pd"
88     TypeHint = pd.DataFrame
89 
90     @classmethod
91     def can_encode(cls, payload: Any) -> bool:
92         return isinstance(payload, pd.DataFrame)
93 
94     @classmethod
95     def encode_response(
96         cls,
97         model_name: str,
98         payload: pd.DataFrame,
99         model_version: Optional[str] = None,
100         use_bytes: bool = True,
101         **kwargs
102     ) -> InferenceResponse:
103         outputs = cls.encode_outputs(payload, use_bytes=use_bytes)
104 
105         return InferenceResponse(
106             model_name=model_name,
107             model_version=model_version,
108             parameters=Parameters(content_type=cls.ContentType),
109             outputs=outputs,
110         )
111 
112     @classmethod
113     def decode_response(cls, response: InferenceResponse) -> pd.DataFrame:
114         data = {
115             response_output.name: _to_series(response_output)
116             for response_output in response.outputs
117         }
118 
119         return pd.DataFrame(data)
120 
121     @classmethod
122     def encode_outputs(
123         cls, payload: pd.DataFrame, use_bytes: bool = True
124     ) -> List[ResponseOutput]:
125         return [
126             _to_response_output(payload[col], use_bytes=use_bytes) for col in payload
127         ]
128 
129     @classmethod
130     def encode_request(
131         cls, payload: pd.DataFrame, use_bytes: bool = True, **kwargs
132     ) -> InferenceRequest:
133         outputs = cls.encode_outputs(payload, use_bytes=use_bytes)
134 
135         return InferenceRequest(
136             parameters=Parameters(content_type=cls.ContentType),
137             inputs=[
138                 RequestInput(
139                     name=output.name,
140                     datatype=output.datatype,
141                     shape=output.shape,
142                     data=output.data,
143                     parameters=output.parameters,
144                 )
145                 for output in outputs
146             ],
147         )
148 
149     @classmethod
150     def decode_request(cls, request: InferenceRequest) -> pd.DataFrame:
151         data = {
152             request_input.name: _to_series(request_input)
153             for request_input in request.inputs
154         }
155 
156         return pd.DataFrame(data)
157
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mlserver/codecs/numpy.py b/mlserver/codecs/numpy.py
--- a/mlserver/codecs/numpy.py
+++ b/mlserver/codecs/numpy.py
@@ -92,7 +92,24 @@
             # need to encapsulate it into a list so that it's compatible.
             return [data.tobytes()]
 
-    return data.flatten().tolist()
+    flattened_list = data.flatten().tolist()
+
+    # Replace NaN with null
+    if datatype != "BYTES":
+        # The `isnan` method doesn't work on Numpy arrays with non-numeric
+        # types
+        has_nan = np.isnan(data).any()
+        if has_nan:
+            flattened_list = list(map(convert_nan, flattened_list))
+
+    return flattened_list
+
+
+def convert_nan(val):
+    if np.isnan(val):
+        return None
+
+    return val
 
 
 @register_input_codec
diff --git a/mlserver/codecs/pandas.py b/mlserver/codecs/pandas.py
--- a/mlserver/codecs/pandas.py
+++ b/mlserver/codecs/pandas.py
@@ -4,7 +4,7 @@
 from typing import Optional, Any, List, Tuple
 
 from .base import RequestCodec, register_request_codec
-from .numpy import to_datatype, to_dtype
+from .numpy import to_datatype, to_dtype, convert_nan
 from .string import encode_str, StringCodec
 from .utils import get_decoded_or_raw, InputOrOutput, inject_batch_dimension
 from .lists import ListElement
@@ -35,8 +35,13 @@
 def _to_response_output(series: pd.Series, use_bytes: bool = True) -> ResponseOutput:
     datatype = to_datatype(series.dtype)
     data = series.tolist()
-    content_type = None
 
+    # Replace NaN with null
+    has_nan = series.isnull().any()
+    if has_nan:
+        data = list(map(convert_nan, data))
+
+    content_type = None
     if datatype == "BYTES":
         data, content_type = _process_bytes(data, use_bytes)

{"golden_diff": "diff --git a/mlserver/codecs/numpy.py b/mlserver/codecs/numpy.py\n--- a/mlserver/codecs/numpy.py\n+++ b/mlserver/codecs/numpy.py\n@@ -92,7 +92,24 @@\n             # need to encapsulate it into a list so that it's compatible.\n             return [data.tobytes()]\n \n-    return data.flatten().tolist()\n+    flattened_list = data.flatten().tolist()\n+\n+    # Replace NaN with null\n+    if datatype != \"BYTES\":\n+        # The `isnan` method doesn't work on Numpy arrays with non-numeric\n+        # types\n+        has_nan = np.isnan(data).any()\n+        if has_nan:\n+            flattened_list = list(map(convert_nan, flattened_list))\n+\n+    return flattened_list\n+\n+\n+def convert_nan(val):\n+    if np.isnan(val):\n+        return None\n+\n+    return val\n \n \n @register_input_codec\ndiff --git a/mlserver/codecs/pandas.py b/mlserver/codecs/pandas.py\n--- a/mlserver/codecs/pandas.py\n+++ b/mlserver/codecs/pandas.py\n@@ -4,7 +4,7 @@\n from typing import Optional, Any, List, Tuple\n \n from .base import RequestCodec, register_request_codec\n-from .numpy import to_datatype, to_dtype\n+from .numpy import to_datatype, to_dtype, convert_nan\n from .string import encode_str, StringCodec\n from .utils import get_decoded_or_raw, InputOrOutput, inject_batch_dimension\n from .lists import ListElement\n@@ -35,8 +35,13 @@\n def _to_response_output(series: pd.Series, use_bytes: bool = True) -> ResponseOutput:\n     datatype = to_datatype(series.dtype)\n     data = series.tolist()\n-    content_type = None\n \n+    # Replace NaN with null\n+    has_nan = series.isnull().any()\n+    if has_nan:\n+        data = list(map(convert_nan, data))\n+\n+    content_type = None\n     if datatype == \"BYTES\":\n         data, content_type = _process_bytes(data, use_bytes)\n", "issue": "Support for NaN in np.ndarray codec?\nSending/receiving a `numpy` array containing NaN values to/from an mlserver with the `NumpyCodec` results in a `ValueError: Out of range float values are not JSON compliant` error. Is this a known limitation, and are there any good workarounds that would still allow me to use a pre-packaged server? I understand that it would probably be bad to assume than `NaN` can be mapped to `null` in the JSON, but maybe that could be an option?\n", "before_files": [{"content": "import numpy as np\n\nfrom typing import Any\n\nfrom ..types import RequestInput, ResponseOutput, Parameters\n\nfrom .base import InputCodec, register_input_codec, register_request_codec\nfrom .utils import SingleInputRequestCodec, InputOrOutput, inject_batch_dimension\nfrom .lists import is_list_of\nfrom .string import encode_str\n\n_DatatypeToNumpy = {\n    \"BOOL\": \"bool\",\n    \"UINT8\": \"uint8\",\n    \"UINT16\": \"uint16\",\n    \"UINT32\": \"uint32\",\n    \"UINT64\": \"uint64\",\n    \"INT8\": \"int8\",\n    \"INT16\": \"int16\",\n    \"INT32\": \"int32\",\n    \"INT64\": \"int64\",\n    \"FP16\": \"float16\",\n    \"FP32\": \"float32\",\n    \"FP64\": \"float64\",\n    \"BYTES\": \"bytes\",\n}\n\n_NumpyToDatatype = {value: key for key, value in _DatatypeToNumpy.items()}\n\n# NOTE: numpy has more types than v2 protocol\n_NumpyToDatatype[\"object\"] = \"BYTES\"\n_NumpyToDatatype[\"S\"] = \"BYTES\"\n_NumpyToDatatype[\"U\"] = \"BYTES\"\n\n\ndef to_dtype(input_or_output: InputOrOutput) -> \"np.dtype\":\n    dtype = _DatatypeToNumpy[input_or_output.datatype]\n\n    if input_or_output.datatype == \"BYTES\":\n        data = getattr(input_or_output.data, \"__root__\", input_or_output.data)\n        if is_list_of(data, str):\n            # Handle special case of strings being treated as Numpy arrays\n            return np.dtype(str)\n\n        # bytes have variable size, so need to specify as part of type\n        # TODO: Make elem size variable (and not just the last dimension)\n        elem_size = input_or_output.shape[-1]\n        return np.dtype((dtype, elem_size))\n\n    return np.dtype(dtype)\n\n\ndef to_datatype(dtype: np.dtype) -> str:\n    as_str = str(dtype)\n\n    if as_str not in _NumpyToDatatype:\n        # If not present, try with kind\n        as_str = getattr(dtype, \"kind\")\n\n    datatype = _NumpyToDatatype[as_str]\n\n    return datatype\n\n\ndef _to_ndarray(input_or_output: InputOrOutput) -> np.ndarray:\n    data = getattr(input_or_output.data, \"__root__\", input_or_output.data)\n    dtype = to_dtype(input_or_output)\n\n    if input_or_output.datatype == \"BYTES\":\n        if is_list_of(data, bytes):\n            # If the inputs is of type `BYTES`, there could be multiple \"lists\"\n            # serialised into multiple buffers.\n            # We will deserialise all of them and concatenate them together.\n            decoded = [np.frombuffer(buffer, dtype) for buffer in data]\n            return np.concatenate(decoded)\n\n    return np.array(data, dtype)\n\n\ndef _encode_data(data: np.ndarray, datatype: str) -> list:\n    if datatype == \"BYTES\":\n        if np.issubdtype(data.dtype, str):\n            # Handle special case of a string Numpy array, where the diff elems\n            # need to be encoded as well\n            as_list = data.flatten().tolist()\n            return list(map(encode_str, as_list))\n\n        if np.issubdtype(data.dtype, bytes):\n            # `tobytes` is way faster than tolist, although it's harder to serialise\n            # and only makes sense for actual bytes inputs (#253).\n            # Note that `.tobytes()` will return a single `bytes` payload, thus we\n            # need to encapsulate it into a list so that it's compatible.\n            return [data.tobytes()]\n\n    return data.flatten().tolist()\n\n\n@register_input_codec\nclass NumpyCodec(InputCodec):\n    \"\"\"\n    Decodes an request input (response output) as a NumPy array.\n    \"\"\"\n\n    ContentType = \"np\"\n    TypeHint = np.ndarray\n\n    @classmethod\n    def can_encode(csl, payload: Any) -> bool:\n        return isinstance(payload, np.ndarray)\n\n    @classmethod\n    def encode_output(cls, name: str, payload: np.ndarray, **kwargs) -> ResponseOutput:\n        datatype = to_datatype(payload.dtype)\n\n        shape = inject_batch_dimension(list(payload.shape))\n\n        return ResponseOutput(\n            name=name,\n            datatype=datatype,\n            shape=shape,\n            data=_encode_data(payload, datatype),\n            parameters=Parameters(content_type=cls.ContentType),\n        )\n\n    @classmethod\n    def decode_output(cls, response_output: ResponseOutput) -> np.ndarray:\n        return cls.decode_input(response_output)  # type: ignore\n\n    @classmethod\n    def encode_input(cls, name: str, payload: np.ndarray, **kwargs) -> RequestInput:\n        output = cls.encode_output(name=name, payload=payload)\n\n        return RequestInput(\n            name=output.name,\n            datatype=output.datatype,\n            shape=output.shape,\n            data=output.data,\n            parameters=Parameters(content_type=cls.ContentType),\n        )\n\n    @classmethod\n    def decode_input(cls, request_input: RequestInput) -> np.ndarray:\n        model_data = _to_ndarray(request_input)\n\n        # TODO: Check if reshape not valid\n        return model_data.reshape(request_input.shape)\n\n\n@register_request_codec\nclass NumpyRequestCodec(SingleInputRequestCodec):\n    \"\"\"\n    Decodes the first input (output) of request (response) as a NumPy array.\n    This codec can be useful for cases where the whole payload is a single\n    NumPy tensor.\n    \"\"\"\n\n    InputCodec = NumpyCodec\n    ContentType = NumpyCodec.ContentType\n", "path": "mlserver/codecs/numpy.py"}, {"content": "import pandas as pd\nimport numpy as np\n\nfrom typing import Optional, Any, List, Tuple\n\nfrom .base import RequestCodec, register_request_codec\nfrom .numpy import to_datatype, to_dtype\nfrom .string import encode_str, StringCodec\nfrom .utils import get_decoded_or_raw, InputOrOutput, inject_batch_dimension\nfrom .lists import ListElement\nfrom ..types import (\n    InferenceRequest,\n    InferenceResponse,\n    RequestInput,\n    ResponseOutput,\n    Parameters,\n)\n\n\ndef _to_series(input_or_output: InputOrOutput) -> pd.Series:\n    payload = get_decoded_or_raw(input_or_output)\n\n    if input_or_output.datatype == \"BYTES\":\n        # Don't convert the dtype of BYTES\n        return pd.Series(payload)\n\n    if isinstance(payload, np.ndarray):\n        # Necessary so that it's compatible with pd.Series\n        payload = list(payload)\n\n    dtype = to_dtype(input_or_output)\n    return pd.Series(payload, dtype=dtype)\n\n\ndef _to_response_output(series: pd.Series, use_bytes: bool = True) -> ResponseOutput:\n    datatype = to_datatype(series.dtype)\n    data = series.tolist()\n    content_type = None\n\n    if datatype == \"BYTES\":\n        data, content_type = _process_bytes(data, use_bytes)\n\n    shape = inject_batch_dimension(list(series.shape))\n    parameters = None\n    if content_type:\n        parameters = Parameters(content_type=content_type)\n\n    return ResponseOutput(\n        name=series.name,\n        shape=shape,\n        data=data,\n        datatype=datatype,\n        parameters=parameters,\n    )\n\n\ndef _process_bytes(\n    data: List[ListElement], use_bytes: bool = True\n) -> Tuple[List[ListElement], Optional[str]]:\n    # To ensure that \"string\" columns can be encoded in gRPC, we need to\n    # encode them as bytes.\n    # We'll also keep track of whether the list should be treated in the\n    # future as a list of strings.\n    processed = []\n    content_type: Optional[str] = StringCodec.ContentType\n    for elem in data:\n        converted = elem\n        if not isinstance(elem, str):\n            # There was a non-string element, so we can't determine a content\n            # type\n            content_type = None\n        elif use_bytes:\n            converted = encode_str(elem)\n\n        processed.append(converted)\n\n    return processed, content_type\n\n\n@register_request_codec\nclass PandasCodec(RequestCodec):\n    \"\"\"\n    Decodes a request (response) into a Pandas DataFrame, assuming each input\n    (output) head corresponds to a column of the DataFrame.\n    \"\"\"\n\n    ContentType = \"pd\"\n    TypeHint = pd.DataFrame\n\n    @classmethod\n    def can_encode(cls, payload: Any) -> bool:\n        return isinstance(payload, pd.DataFrame)\n\n    @classmethod\n    def encode_response(\n        cls,\n        model_name: str,\n        payload: pd.DataFrame,\n        model_version: Optional[str] = None,\n        use_bytes: bool = True,\n        **kwargs\n    ) -> InferenceResponse:\n        outputs = cls.encode_outputs(payload, use_bytes=use_bytes)\n\n        return InferenceResponse(\n            model_name=model_name,\n            model_version=model_version,\n            parameters=Parameters(content_type=cls.ContentType),\n            outputs=outputs,\n        )\n\n    @classmethod\n    def decode_response(cls, response: InferenceResponse) -> pd.DataFrame:\n        data = {\n            response_output.name: _to_series(response_output)\n            for response_output in response.outputs\n        }\n\n        return pd.DataFrame(data)\n\n    @classmethod\n    def encode_outputs(\n        cls, payload: pd.DataFrame, use_bytes: bool = True\n    ) -> List[ResponseOutput]:\n        return [\n            _to_response_output(payload[col], use_bytes=use_bytes) for col in payload\n        ]\n\n    @classmethod\n    def encode_request(\n        cls, payload: pd.DataFrame, use_bytes: bool = True, **kwargs\n    ) -> InferenceRequest:\n        outputs = cls.encode_outputs(payload, use_bytes=use_bytes)\n\n        return InferenceRequest(\n            parameters=Parameters(content_type=cls.ContentType),\n            inputs=[\n                RequestInput(\n                    name=output.name,\n                    datatype=output.datatype,\n                    shape=output.shape,\n                    data=output.data,\n                    parameters=output.parameters,\n                )\n                for output in outputs\n            ],\n        )\n\n    @classmethod\n    def decode_request(cls, request: InferenceRequest) -> pd.DataFrame:\n        data = {\n            request_input.name: _to_series(request_input)\n            for request_input in request.inputs\n        }\n\n        return pd.DataFrame(data)\n", "path": "mlserver/codecs/pandas.py"}], "after_files": [{"content": "import numpy as np\n\nfrom typing import Any\n\nfrom ..types import RequestInput, ResponseOutput, Parameters\n\nfrom .base import InputCodec, register_input_codec, register_request_codec\nfrom .utils import SingleInputRequestCodec, InputOrOutput, inject_batch_dimension\nfrom .lists import is_list_of\nfrom .string import encode_str\n\n_DatatypeToNumpy = {\n    \"BOOL\": \"bool\",\n    \"UINT8\": \"uint8\",\n    \"UINT16\": \"uint16\",\n    \"UINT32\": \"uint32\",\n    \"UINT64\": \"uint64\",\n    \"INT8\": \"int8\",\n    \"INT16\": \"int16\",\n    \"INT32\": \"int32\",\n    \"INT64\": \"int64\",\n    \"FP16\": \"float16\",\n    \"FP32\": \"float32\",\n    \"FP64\": \"float64\",\n    \"BYTES\": \"bytes\",\n}\n\n_NumpyToDatatype = {value: key for key, value in _DatatypeToNumpy.items()}\n\n# NOTE: numpy has more types than v2 protocol\n_NumpyToDatatype[\"object\"] = \"BYTES\"\n_NumpyToDatatype[\"S\"] = \"BYTES\"\n_NumpyToDatatype[\"U\"] = \"BYTES\"\n\n\ndef to_dtype(input_or_output: InputOrOutput) -> \"np.dtype\":\n    dtype = _DatatypeToNumpy[input_or_output.datatype]\n\n    if input_or_output.datatype == \"BYTES\":\n        data = getattr(input_or_output.data, \"__root__\", input_or_output.data)\n        if is_list_of(data, str):\n            # Handle special case of strings being treated as Numpy arrays\n            return np.dtype(str)\n\n        # bytes have variable size, so need to specify as part of type\n        # TODO: Make elem size variable (and not just the last dimension)\n        elem_size = input_or_output.shape[-1]\n        return np.dtype((dtype, elem_size))\n\n    return np.dtype(dtype)\n\n\ndef to_datatype(dtype: np.dtype) -> str:\n    as_str = str(dtype)\n\n    if as_str not in _NumpyToDatatype:\n        # If not present, try with kind\n        as_str = getattr(dtype, \"kind\")\n\n    datatype = _NumpyToDatatype[as_str]\n\n    return datatype\n\n\ndef _to_ndarray(input_or_output: InputOrOutput) -> np.ndarray:\n    data = getattr(input_or_output.data, \"__root__\", input_or_output.data)\n    dtype = to_dtype(input_or_output)\n\n    if input_or_output.datatype == \"BYTES\":\n        if is_list_of(data, bytes):\n            # If the inputs is of type `BYTES`, there could be multiple \"lists\"\n            # serialised into multiple buffers.\n            # We will deserialise all of them and concatenate them together.\n            decoded = [np.frombuffer(buffer, dtype) for buffer in data]\n            return np.concatenate(decoded)\n\n    return np.array(data, dtype)\n\n\ndef _encode_data(data: np.ndarray, datatype: str) -> list:\n    if datatype == \"BYTES\":\n        if np.issubdtype(data.dtype, str):\n            # Handle special case of a string Numpy array, where the diff elems\n            # need to be encoded as well\n            as_list = data.flatten().tolist()\n            return list(map(encode_str, as_list))\n\n        if np.issubdtype(data.dtype, bytes):\n            # `tobytes` is way faster than tolist, although it's harder to serialise\n            # and only makes sense for actual bytes inputs (#253).\n            # Note that `.tobytes()` will return a single `bytes` payload, thus we\n            # need to encapsulate it into a list so that it's compatible.\n            return [data.tobytes()]\n\n    flattened_list = data.flatten().tolist()\n\n    # Replace NaN with null\n    if datatype != \"BYTES\":\n        # The `isnan` method doesn't work on Numpy arrays with non-numeric\n        # types\n        has_nan = np.isnan(data).any()\n        if has_nan:\n            flattened_list = list(map(convert_nan, flattened_list))\n\n    return flattened_list\n\n\ndef convert_nan(val):\n    if np.isnan(val):\n        return None\n\n    return val\n\n\n@register_input_codec\nclass NumpyCodec(InputCodec):\n    \"\"\"\n    Decodes an request input (response output) as a NumPy array.\n    \"\"\"\n\n    ContentType = \"np\"\n    TypeHint = np.ndarray\n\n    @classmethod\n    def can_encode(csl, payload: Any) -> bool:\n        return isinstance(payload, np.ndarray)\n\n    @classmethod\n    def encode_output(cls, name: str, payload: np.ndarray, **kwargs) -> ResponseOutput:\n        datatype = to_datatype(payload.dtype)\n\n        shape = inject_batch_dimension(list(payload.shape))\n\n        return ResponseOutput(\n            name=name,\n            datatype=datatype,\n            shape=shape,\n            data=_encode_data(payload, datatype),\n            parameters=Parameters(content_type=cls.ContentType),\n        )\n\n    @classmethod\n    def decode_output(cls, response_output: ResponseOutput) -> np.ndarray:\n        return cls.decode_input(response_output)  # type: ignore\n\n    @classmethod\n    def encode_input(cls, name: str, payload: np.ndarray, **kwargs) -> RequestInput:\n        output = cls.encode_output(name=name, payload=payload)\n\n        return RequestInput(\n            name=output.name,\n            datatype=output.datatype,\n            shape=output.shape,\n            data=output.data,\n            parameters=Parameters(content_type=cls.ContentType),\n        )\n\n    @classmethod\n    def decode_input(cls, request_input: RequestInput) -> np.ndarray:\n        model_data = _to_ndarray(request_input)\n\n        # TODO: Check if reshape not valid\n        return model_data.reshape(request_input.shape)\n\n\n@register_request_codec\nclass NumpyRequestCodec(SingleInputRequestCodec):\n    \"\"\"\n    Decodes the first input (output) of request (response) as a NumPy array.\n    This codec can be useful for cases where the whole payload is a single\n    NumPy tensor.\n    \"\"\"\n\n    InputCodec = NumpyCodec\n    ContentType = NumpyCodec.ContentType\n", "path": "mlserver/codecs/numpy.py"}, {"content": "import pandas as pd\nimport numpy as np\n\nfrom typing import Optional, Any, List, Tuple\n\nfrom .base import RequestCodec, register_request_codec\nfrom .numpy import to_datatype, to_dtype, convert_nan\nfrom .string import encode_str, StringCodec\nfrom .utils import get_decoded_or_raw, InputOrOutput, inject_batch_dimension\nfrom .lists import ListElement\nfrom ..types import (\n    InferenceRequest,\n    InferenceResponse,\n    RequestInput,\n    ResponseOutput,\n    Parameters,\n)\n\n\ndef _to_series(input_or_output: InputOrOutput) -> pd.Series:\n    payload = get_decoded_or_raw(input_or_output)\n\n    if input_or_output.datatype == \"BYTES\":\n        # Don't convert the dtype of BYTES\n        return pd.Series(payload)\n\n    if isinstance(payload, np.ndarray):\n        # Necessary so that it's compatible with pd.Series\n        payload = list(payload)\n\n    dtype = to_dtype(input_or_output)\n    return pd.Series(payload, dtype=dtype)\n\n\ndef _to_response_output(series: pd.Series, use_bytes: bool = True) -> ResponseOutput:\n    datatype = to_datatype(series.dtype)\n    data = series.tolist()\n\n    # Replace NaN with null\n    has_nan = series.isnull().any()\n    if has_nan:\n        data = list(map(convert_nan, data))\n\n    content_type = None\n    if datatype == \"BYTES\":\n        data, content_type = _process_bytes(data, use_bytes)\n\n    shape = inject_batch_dimension(list(series.shape))\n    parameters = None\n    if content_type:\n        parameters = Parameters(content_type=content_type)\n\n    return ResponseOutput(\n        name=series.name,\n        shape=shape,\n        data=data,\n        datatype=datatype,\n        parameters=parameters,\n    )\n\n\ndef _process_bytes(\n    data: List[ListElement], use_bytes: bool = True\n) -> Tuple[List[ListElement], Optional[str]]:\n    # To ensure that \"string\" columns can be encoded in gRPC, we need to\n    # encode them as bytes.\n    # We'll also keep track of whether the list should be treated in the\n    # future as a list of strings.\n    processed = []\n    content_type: Optional[str] = StringCodec.ContentType\n    for elem in data:\n        converted = elem\n        if not isinstance(elem, str):\n            # There was a non-string element, so we can't determine a content\n            # type\n            content_type = None\n        elif use_bytes:\n            converted = encode_str(elem)\n\n        processed.append(converted)\n\n    return processed, content_type\n\n\n@register_request_codec\nclass PandasCodec(RequestCodec):\n    \"\"\"\n    Decodes a request (response) into a Pandas DataFrame, assuming each input\n    (output) head corresponds to a column of the DataFrame.\n    \"\"\"\n\n    ContentType = \"pd\"\n    TypeHint = pd.DataFrame\n\n    @classmethod\n    def can_encode(cls, payload: Any) -> bool:\n        return isinstance(payload, pd.DataFrame)\n\n    @classmethod\n    def encode_response(\n        cls,\n        model_name: str,\n        payload: pd.DataFrame,\n        model_version: Optional[str] = None,\n        use_bytes: bool = True,\n        **kwargs\n    ) -> InferenceResponse:\n        outputs = cls.encode_outputs(payload, use_bytes=use_bytes)\n\n        return InferenceResponse(\n            model_name=model_name,\n            model_version=model_version,\n            parameters=Parameters(content_type=cls.ContentType),\n            outputs=outputs,\n        )\n\n    @classmethod\n    def decode_response(cls, response: InferenceResponse) -> pd.DataFrame:\n        data = {\n            response_output.name: _to_series(response_output)\n            for response_output in response.outputs\n        }\n\n        return pd.DataFrame(data)\n\n    @classmethod\n    def encode_outputs(\n        cls, payload: pd.DataFrame, use_bytes: bool = True\n    ) -> List[ResponseOutput]:\n        return [\n            _to_response_output(payload[col], use_bytes=use_bytes) for col in payload\n        ]\n\n    @classmethod\n    def encode_request(\n        cls, payload: pd.DataFrame, use_bytes: bool = True, **kwargs\n    ) -> InferenceRequest:\n        outputs = cls.encode_outputs(payload, use_bytes=use_bytes)\n\n        return InferenceRequest(\n            parameters=Parameters(content_type=cls.ContentType),\n            inputs=[\n                RequestInput(\n                    name=output.name,\n                    datatype=output.datatype,\n                    shape=output.shape,\n                    data=output.data,\n                    parameters=output.parameters,\n                )\n                for output in outputs\n            ],\n        )\n\n    @classmethod\n    def decode_request(cls, request: InferenceRequest) -> pd.DataFrame:\n        data = {\n            request_input.name: _to_series(request_input)\n            for request_input in request.inputs\n        }\n\n        return pd.DataFrame(data)\n", "path": "mlserver/codecs/pandas.py"}]}