diff --git a/examples/models/sklearn_iris/sklearn_iris.ipynb b/examples/models/sklearn_iris/sklearn_iris.ipynb index f128be2466..a1758ca0cf 100644 --- a/examples/models/sklearn_iris/sklearn_iris.ipynb +++ b/examples/models/sklearn_iris/sklearn_iris.ipynb @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [ { @@ -55,14 +55,14 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Context \"kind-kind\" modified.\r\n" + "Context \"docker-desktop\" modified.\r\n" ] } ], @@ -79,7 +79,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -117,7 +117,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -134,7 +134,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -153,7 +153,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -177,7 +177,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -186,16 +186,16 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "['{\"data\":{\"names\":[\"t:0\",\"t:1\",\"t:2\"],\"ndarray\":[[0.9548873249364059,0.04505474761562512,5.7927447968953825e-05]]},\"meta\":{}}']" + "['{\"data\":{\"names\":[\"t:0\",\"t:1\",\"t:2\"],\"ndarray\":[[0.9548873249364059,0.04505474761562512,5.7927447968953825e-05]]},\"meta\":{\"requestPath\":{\"sklearn-iris-classifier\":\"seldonio/sklearn-iris:0.2\"}}}']" ] }, - "execution_count": 9, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -206,14 +206,14 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "['{\"data\":{\"names\":[\"t:0\",\"t:1\",\"t:2\"],\"ndarray\":[[0.9548873249364059,0.04505474761562512,5.7927447968953825e-05]]},\"meta\":{}}']\n" + "['{\"data\":{\"names\":[\"t:0\",\"t:1\",\"t:2\"],\"ndarray\":[[0.9548873249364059,0.04505474761562512,5.7927447968953825e-05]]},\"meta\":{\"requestPath\":{\"sklearn-iris-classifier\":\"seldonio/sklearn-iris:0.2\"}}}']\n" ] } ], @@ -224,10 +224,70 @@ "assert(j[\"data\"][\"ndarray\"][0][0]>0.0)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "REST request with raw_data" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'data': {'names': ['t:0', 't:1', 't:2'], 'ndarray': [[0.9548873249364059, 0.04505474761562512, 5.7927447968953825e-05]]}, 'meta': {'requestPath': {'sklearn-iris-classifier': 'seldonio/sklearn-iris:0.2'}}}\n" + ] + } + ], + "source": [ + "from seldon_core.seldon_client import SeldonClient\n", + "sc = SeldonClient(deployment_name=\"seldon-deployment-example\",namespace=\"seldon\")\n", + "res = sc.predict(gateway=\"istio\",gateway_endpoint=\"localhost:8003\", transport=\"rest\",raw_data = {\"data\":{\"ndarray\":[[5.964,4.006,2.081,1.031]]}})\n", + "print(res.response)\n", + "assert(res.success==True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "gRCP request with proto raw_data" + ] + }, { "cell_type": "code", "execution_count": 11, "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success:True message:\n", + "Request:\n", + "{'data': {'ndarray': [[5.964, 4.006, 2.081, 1.031]]}}\n", + "Response:\n", + "{'meta': {'requestPath': {'sklearn-iris-classifier': 'seldonio/sklearn-iris:0.2'}}, 'data': {'names': ['t:0', 't:1', 't:2'], 'ndarray': [[0.9548873249364059, 0.04505474761562512, 5.7927447968953825e-05]]}}\n" + ] + } + ], + "source": [ + "from seldon_core.utils import json_to_seldon_message\n", + "proto_raw_data = json_to_seldon_message({\"data\":{\"ndarray\":[[5.964,4.006,2.081,1.031]]}})\n", + "res = sc.predict(gateway=\"istio\",gateway_endpoint=\"localhost:8003\", transport=\"grpc\",raw_data = proto_raw_data)\n", + "print(res)\n", + "assert(res.success==True)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -265,7 +325,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.7.4" }, "varInspector": { "cols": { diff --git a/python/seldon_core/seldon_client.py b/python/seldon_core/seldon_client.py index e009c3deb7..c818fc1305 100644 --- a/python/seldon_core/seldon_client.py +++ b/python/seldon_core/seldon_client.py @@ -282,6 +282,7 @@ def predict( http_path: str = None, meta: Dict = None, client_return_type: str = None, + raw_data: Dict = None, ) -> SeldonClientPrediction: """ @@ -327,6 +328,8 @@ def predict( Custom meta map client_return_type the return type of all functions can be either dict or proto + raw_data + Raw payload, a dictionary representing the json request or the raw grpc proto Returns ------- @@ -353,6 +356,7 @@ def predict( http_path=http_path, meta=meta, client_return_type=client_return_type, + raw_data=raw_data, ) self._validate_args(**k) if k["gateway"] == "ambassador" or k["gateway"] == "istio": @@ -1196,6 +1200,7 @@ def rest_predict_seldon( json_data: Union[str, List, Dict] = None, names: Iterable[str] = None, client_return_type: str = "proto", + raw_data: Dict = None, **kwargs, ) -> SeldonClientPrediction: """ @@ -1221,6 +1226,8 @@ def rest_predict_seldon( column names client_return_type the return type of all functions can be either dict or proto + raw_data + Raw payload (dictionary) given by the user kwargs Returns @@ -1228,18 +1235,22 @@ def rest_predict_seldon( Seldon Client Prediction """ - if bin_data is not None: - request = prediction_pb2.SeldonMessage(binData=bin_data) - elif str_data is not None: - request = prediction_pb2.SeldonMessage(strData=str_data) - elif json_data is not None: - request = json_to_seldon_message({"jsonData": json_data}) + if raw_data: + request = json_to_seldon_message(raw_data) + payload = raw_data else: - if data is None: - data = np.random.rand(*shape) - datadef = array_to_grpc_datadef(payload_type, data, names=names) - request = prediction_pb2.SeldonMessage(data=datadef) - payload = seldon_message_to_json(request) + if bin_data is not None: + request = prediction_pb2.SeldonMessage(binData=bin_data) + elif str_data is not None: + request = prediction_pb2.SeldonMessage(strData=str_data) + elif json_data is not None: + request = json_to_seldon_message({"jsonData": json_data}) + else: + if data is None: + data = np.random.rand(*shape) + datadef = array_to_grpc_datadef(payload_type, data, names=names) + request = prediction_pb2.SeldonMessage(data=datadef) + payload = seldon_message_to_json(request) response_raw = requests.post( "http://" + gateway_endpoint + "/api/v0.1/predictions", @@ -1283,6 +1294,7 @@ def grpc_predict_seldon( grpc_max_receive_message_length: int = 4 * 1024 * 1024, names: Iterable[str] = None, client_return_type: str = "proto", + raw_data: Dict = None, **kwargs, ) -> SeldonClientPrediction: """ @@ -1314,6 +1326,8 @@ def grpc_predict_seldon( Column names client_return_type the return type of all functions can be either dict or proto + raw_data + Raw payload (dictionary or proto) given by the user kwargs Returns @@ -1321,19 +1335,24 @@ def grpc_predict_seldon( A SeldonMessage proto """ - if bin_data is not None: - request = prediction_pb2.SeldonMessage(binData=bin_data) - elif str_data is not None: - request = prediction_pb2.SeldonMessage(strData=str_data) - elif json_data is not None: - request = json_to_seldon_message({"jsonData": json_data}) - elif custom_data is not None: - request = prediction_pb2.SeldonMessage(customData=custom_data) + if isinstance(raw_data, prediction_pb2.SeldonMessage): + request = raw_data + elif raw_data: + request = json_to_seldon_message(raw_data) else: - if data is None: - data = np.random.rand(*shape) - datadef = array_to_grpc_datadef(payload_type, data, names=names) - request = prediction_pb2.SeldonMessage(data=datadef) + if bin_data is not None: + request = prediction_pb2.SeldonMessage(binData=bin_data) + elif str_data is not None: + request = prediction_pb2.SeldonMessage(strData=str_data) + elif json_data is not None: + request = json_to_seldon_message({"jsonData": json_data}) + elif custom_data is not None: + request = prediction_pb2.SeldonMessage(customData=custom_data) + else: + if data is None: + data = np.random.rand(*shape) + datadef = array_to_grpc_datadef(payload_type, data, names=names) + request = prediction_pb2.SeldonMessage(data=datadef) channel = grpc.insecure_channel( gateway_endpoint, @@ -1375,6 +1394,7 @@ def rest_predict_gateway( http_path: str = None, meta: Dict = {}, client_return_type: str = "proto", + raw_data: Dict = None, **kwargs, ) -> SeldonClientPrediction: """ @@ -1416,6 +1436,8 @@ def rest_predict_gateway( Custom meta map client_return_type the return type of all functions can be either dict or proto + raw_data + Raw payload (dictionary) given by the user Returns ------- @@ -1426,19 +1448,22 @@ def rest_predict_gateway( metaKV = prediction_pb2.Meta() metaJson = {"tags": meta} json_format.ParseDict(metaJson, metaKV) - - if bin_data is not None: - request = prediction_pb2.SeldonMessage(binData=bin_data, meta=metaKV) - elif str_data is not None: - request = prediction_pb2.SeldonMessage(strData=str_data, meta=metaKV) - elif json_data is not None: - request = json_to_seldon_message({"jsonData": json_data}) + if raw_data is not None: + request = json_to_seldon_message(raw_data) + payload = raw_data else: - if data is None: - data = np.random.rand(*shape) - datadef = array_to_grpc_datadef(payload_type, data, names=names) - request = prediction_pb2.SeldonMessage(data=datadef, meta=metaKV) - payload = seldon_message_to_json(request) + if bin_data is not None: + request = prediction_pb2.SeldonMessage(binData=bin_data, meta=metaKV) + elif str_data is not None: + request = prediction_pb2.SeldonMessage(strData=str_data, meta=metaKV) + elif json_data is not None: + request = json_to_seldon_message({"jsonData": json_data}) + else: + if data is None: + data = np.random.rand(*shape) + datadef = array_to_grpc_datadef(payload_type, data, names=names) + request = prediction_pb2.SeldonMessage(data=datadef, meta=metaKV) + payload = seldon_message_to_json(request) if not headers is None: req_headers = headers.copy() @@ -1733,6 +1758,7 @@ def grpc_predict_gateway( channel_credentials: SeldonChannelCredentials = None, meta: Dict = {}, client_return_type: str = "proto", + raw_data: Dict = None, **kwargs, ) -> SeldonClientPrediction: """ @@ -1776,7 +1802,8 @@ def grpc_predict_gateway( Custom meta data map client_return_type the return type of all functions can be either dict or proto - + raw_data + Raw payload (dictionary or proto) given by the user Returns ------- @@ -1789,19 +1816,24 @@ def grpc_predict_gateway( metaJson = {"tags": meta} json_format.ParseDict(metaJson, metaKV) - if bin_data is not None: - request = prediction_pb2.SeldonMessage(binData=bin_data, meta=metaKV) - elif str_data is not None: - request = prediction_pb2.SeldonMessage(strData=str_data, meta=metaKV) - elif json_data is not None: - request = json_to_seldon_message({"jsonData": json_data}) - elif custom_data is not None: - request = prediction_pb2.SeldonMessage(customData=custom_data, meta=metaKV) + if isinstance(raw_data, prediction_pb2.SeldonMessage): + request = raw_data + elif raw_data is not None: + request = json_to_seldon_message(raw_data) else: - if data is None: - data = np.random.rand(*shape) - datadef = array_to_grpc_datadef(payload_type, data, names=names) - request = prediction_pb2.SeldonMessage(data=datadef, meta=metaKV) + if bin_data is not None: + request = prediction_pb2.SeldonMessage(binData=bin_data, meta=metaKV) + elif str_data is not None: + request = prediction_pb2.SeldonMessage(strData=str_data, meta=metaKV) + elif json_data is not None: + request = json_to_seldon_message({"jsonData": json_data}) + elif custom_data is not None: + request = prediction_pb2.SeldonMessage(customData=custom_data, meta=metaKV) + else: + if data is None: + data = np.random.rand(*shape) + datadef = array_to_grpc_datadef(payload_type, data, names=names) + request = prediction_pb2.SeldonMessage(data=datadef, meta=metaKV) options = [ ("grpc.max_send_message_length", grpc_max_send_message_length), ("grpc.max_receive_message_length", grpc_max_receive_message_length), diff --git a/python/tests/test_seldon_client.py b/python/tests/test_seldon_client.py index d3e9790648..43fc195800 100644 --- a/python/tests/test_seldon_client.py +++ b/python/tests/test_seldon_client.py @@ -18,6 +18,7 @@ ) JSON_TEST_DATA = {"test": [0.0, 1.0]} +RAW_DATA_TEST = {"data": {"ndarray": [[0.0, 1.0, 2.0]]}} CUSTOM_TEST_DATA = any_pb2.Any(value=b"test") @@ -118,6 +119,30 @@ def test_predict_rest_json_data_seldon(mock_post): assert mock_post.call_count == 1 +@mock.patch("requests.post", side_effect=mocked_requests_post_success) +def test_predict_rest_raw_data_seldon_proto(mock_post): + sc = SeldonClient( + deployment_name="mymodel", gateway="seldon", client_return_type="proto" + ) + response = sc.predict(raw_data=RAW_DATA_TEST) + json_response = seldon_message_to_json(response.response) + assert mock_post.call_args[1]["json"] == RAW_DATA_TEST + assert response.success is True + assert mock_post.call_count == 1 + + +@mock.patch("requests.post", side_effect=mocked_requests_post_success) +def test_predict_rest_raw_data_gateway_dict(mock_post): + sc = SeldonClient( + deployment_name="mymodel", gateway="istio", client_return_type="dict" + ) + response = sc.predict(raw_data=RAW_DATA_TEST) + json_response = response.response + assert mock_post.call_args[1]["json"] == RAW_DATA_TEST + assert response.success is True + assert mock_post.call_count == 1 + + @mock.patch("requests.post", side_effect=mocked_requests_post_success_json_data) def test_predict_rest_json_data_seldon_return_type(mock_post): sc = SeldonClient( @@ -294,6 +319,23 @@ def test_predict_grpc_seldon(): assert response.response.strData == "predict" +@mock.patch("seldon_core.seldon_client.prediction_pb2_grpc.SeldonStub", new=MyStub) +def test_predict_grpc_proto_raw_data_seldon(): + sc = SeldonClient(deployment_name="mymodel", transport="grpc", gateway="seldon") + proto_raw_data = json_to_seldon_message(RAW_DATA_TEST) + response = sc.predict(raw_data=proto_raw_data, client_return_type="proto") + request = seldon_message_to_json(response.request) + assert request == RAW_DATA_TEST + + +@mock.patch("seldon_core.seldon_client.prediction_pb2_grpc.SeldonStub", new=MyStub) +def test_predict_grpc_raw_data_gateway(): + sc = SeldonClient(deployment_name="mymodel", transport="grpc", gateway="istio") + response = sc.predict(raw_data=RAW_DATA_TEST, client_return_type="proto") + request = seldon_message_to_json(response.request) + assert request == RAW_DATA_TEST + + @mock.patch("seldon_core.seldon_client.prediction_pb2_grpc.SeldonStub", new=MyStub) def test_grpc_predict_json_data_seldon(): sc = SeldonClient(