From 3d910e5316f3f580b3e3f4e9212dfb41a041a621 Mon Sep 17 00:00:00 2001 From: bojiang Date: Sat, 19 Dec 2020 14:14:26 +0800 Subject: [PATCH 1/7] customize schema --- bentoml/adapters/base_input.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bentoml/adapters/base_input.py b/bentoml/adapters/base_input.py index 53f4b9e7d0b..3bf5b745455 100644 --- a/bentoml/adapters/base_input.py +++ b/bentoml/adapters/base_input.py @@ -43,6 +43,8 @@ class BaseInputAdapter: def __init__(self, http_input_example=None, **base_config): self._config = base_config self._http_input_example = http_input_example + if base_config.get('request_schema') is not None: + self.request_schema = base_config['request_schema'] @property def config(self): @@ -56,6 +58,10 @@ def request_schema(self): """ return {"application/json": {"schema": {"type": "object"}}} + @request_schema.setter + def _set_request_schema(self, schema): + setattr(self, 'request_schema', schema) + @property def pip_dependencies(self): """ From d1abb1bf03dee07416857020bebb1d9d283bf8af Mon Sep 17 00:00:00 2001 From: bojiang Date: Sat, 19 Dec 2020 14:38:06 +0800 Subject: [PATCH 2/7] test customezed_schema --- tests/integration/projects/general/service.py | 20 +++++++++++++++++++ .../general_non_batch/tests/test_meta.py | 10 ++++++++++ 2 files changed, 30 insertions(+) diff --git a/tests/integration/projects/general/service.py b/tests/integration/projects/general/service.py index e79c7d2ffd0..b6c23c28d5b 100644 --- a/tests/integration/projects/general/service.py +++ b/tests/integration/projects/general/service.py @@ -70,6 +70,26 @@ def predict_json(self, input_datas): def customezed_route(self, input_datas): return input_datas + @bentoml.api( + input=JsonInput( + request_schema={ + "application/json": { + "schema": { + "type": "object", + "required": ["field1", "field2"], + "properties": { + "field1": {"type": "string"}, + "field2": {"type": "uuid"}, + }, + }, + } + } + ), + batch=True, + ) + def customezed_schema(self, input_datas): + return input_datas + @bentoml.api(input=JsonInput(), batch=True) def predict_strict_json(self, input_datas, tasks: Sequence[InferenceTask] = None): filtered_jsons = [] diff --git a/tests/integration/projects/general_non_batch/tests/test_meta.py b/tests/integration/projects/general_non_batch/tests/test_meta.py index 5f2db207815..f394706c429 100644 --- a/tests/integration/projects/general_non_batch/tests/test_meta.py +++ b/tests/integration/projects/general_non_batch/tests/test_meta.py @@ -7,3 +7,13 @@ async def test_api_server_meta(host): await pytest.assert_request("GET", f"http://{host}/") await pytest.assert_request("GET", f"http://{host}/healthz") await pytest.assert_request("GET", f"http://{host}/docs.json") + + +@pytest.mark.asyncio +async def test_customized_request_schema(host): + await pytest.assert_request( + "GET", + f"http://{host}/docs.json", + headers=(("Content-Type", "application/json"),), + assert_data="", + ) From 238dd98e79cd5c1ba3644d1a3741dee764c0fb3c Mon Sep 17 00:00:00 2001 From: bojiang Date: Sat, 19 Dec 2020 15:12:53 +0800 Subject: [PATCH 3/7] fix --- bentoml/adapters/base_input.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bentoml/adapters/base_input.py b/bentoml/adapters/base_input.py index 3bf5b745455..c6b7c52d6b1 100644 --- a/bentoml/adapters/base_input.py +++ b/bentoml/adapters/base_input.py @@ -60,7 +60,7 @@ def request_schema(self): @request_schema.setter def _set_request_schema(self, schema): - setattr(self, 'request_schema', schema) + self.__dict__['request_schema'] = schema @property def pip_dependencies(self): From 02b43a0befaadfb828b9aaf1fe0b70483aa1feb7 Mon Sep 17 00:00:00 2001 From: bojiang Date: Thu, 25 Feb 2021 18:05:09 +0800 Subject: [PATCH 4/7] finish test --- tests/integration/projects/general/service.py | 31 +++++++++---------- .../general_non_batch/tests/test_meta.py | 6 +++- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/integration/projects/general/service.py b/tests/integration/projects/general/service.py index b6c23c28d5b..d7565b4bf94 100644 --- a/tests/integration/projects/general/service.py +++ b/tests/integration/projects/general/service.py @@ -70,23 +70,20 @@ def predict_json(self, input_datas): def customezed_route(self, input_datas): return input_datas - @bentoml.api( - input=JsonInput( - request_schema={ - "application/json": { - "schema": { - "type": "object", - "required": ["field1", "field2"], - "properties": { - "field1": {"type": "string"}, - "field2": {"type": "uuid"}, - }, - }, - } - } - ), - batch=True, - ) + CUSTOM_SCHEMA = { + "application/json": { + "schema": { + "type": "object", + "required": ["field1", "field2"], + "properties": { + "field1": {"type": "string"}, + "field2": {"type": "uuid"}, + }, + }, + } + } + + @bentoml.api(input=JsonInput(request_schema=CUSTOM_SCHEMA), batch=True) def customezed_schema(self, input_datas): return input_datas diff --git a/tests/integration/projects/general_non_batch/tests/test_meta.py b/tests/integration/projects/general_non_batch/tests/test_meta.py index f394706c429..f2d6522f8ab 100644 --- a/tests/integration/projects/general_non_batch/tests/test_meta.py +++ b/tests/integration/projects/general_non_batch/tests/test_meta.py @@ -11,9 +11,13 @@ async def test_api_server_meta(host): @pytest.mark.asyncio async def test_customized_request_schema(host): + def has_customized_schema(doc_bytes): + json_str = doc_bytes.decode() + return "field1" in json_str + await pytest.assert_request( "GET", f"http://{host}/docs.json", headers=(("Content-Type", "application/json"),), - assert_data="", + assert_data=has_customized_schema, ) From 0033d0e5cc4c983f67bf75189a2dddc0bc278838 Mon Sep 17 00:00:00 2001 From: bojiang Date: Thu, 25 Feb 2021 18:08:39 +0800 Subject: [PATCH 5/7] fix --- .../projects/general/tests/test_meta.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/integration/projects/general/tests/test_meta.py b/tests/integration/projects/general/tests/test_meta.py index bc601f1bce3..9f599bb4cb6 100644 --- a/tests/integration/projects/general/tests/test_meta.py +++ b/tests/integration/projects/general/tests/test_meta.py @@ -34,3 +34,17 @@ def path_in_docs(response_body): data=json.dumps("hello"), assert_data=bytes('"hello"', 'ascii'), ) + + +@pytest.mark.asyncio +async def test_customized_request_schema(host): + def has_customized_schema(doc_bytes): + json_str = doc_bytes.decode() + return "field1" in json_str + + await pytest.assert_request( + "GET", + f"http://{host}/docs.json", + headers=(("Content-Type", "application/json"),), + assert_data=has_customized_schema, + ) From 264386805535586736641cbb833386370630c451 Mon Sep 17 00:00:00 2001 From: bojiang Date: Thu, 25 Feb 2021 18:21:07 +0800 Subject: [PATCH 6/7] fix new-style property --- bentoml/adapters/base_input.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bentoml/adapters/base_input.py b/bentoml/adapters/base_input.py index c6b7c52d6b1..5ce0d3c826c 100644 --- a/bentoml/adapters/base_input.py +++ b/bentoml/adapters/base_input.py @@ -59,7 +59,7 @@ def request_schema(self): return {"application/json": {"schema": {"type": "object"}}} @request_schema.setter - def _set_request_schema(self, schema): + def request_schema(self, schema): self.__dict__['request_schema'] = schema @property From 7b5d0fc9a8f2dfeb3ad254156b4e4c1b647cf25e Mon Sep 17 00:00:00 2001 From: bojiang Date: Fri, 26 Feb 2021 02:42:25 +0800 Subject: [PATCH 7/7] refactor --- bentoml/adapters/base_input.py | 7 +----- bentoml/service/inference_api.py | 6 ++++- .../projects/general/tests/test_microbatch.py | 22 ++++++++--------- .../general_non_batch/tests/test_meta.py | 14 ----------- tests/integration/utils.py | 24 +++++++++++-------- 5 files changed, 31 insertions(+), 42 deletions(-) diff --git a/bentoml/adapters/base_input.py b/bentoml/adapters/base_input.py index 5ce0d3c826c..60663a338ca 100644 --- a/bentoml/adapters/base_input.py +++ b/bentoml/adapters/base_input.py @@ -43,8 +43,7 @@ class BaseInputAdapter: def __init__(self, http_input_example=None, **base_config): self._config = base_config self._http_input_example = http_input_example - if base_config.get('request_schema') is not None: - self.request_schema = base_config['request_schema'] + self.custom_request_schema = base_config.get('request_schema') @property def config(self): @@ -58,10 +57,6 @@ def request_schema(self): """ return {"application/json": {"schema": {"type": "object"}}} - @request_schema.setter - def request_schema(self, schema): - self.__dict__['request_schema'] = schema - @property def pip_dependencies(self): """ diff --git a/bentoml/service/inference_api.py b/bentoml/service/inference_api.py index b7152322c8a..d88dba6f86a 100644 --- a/bentoml/service/inference_api.py +++ b/bentoml/service/inference_api.py @@ -203,7 +203,11 @@ def request_schema(self): """ :return: the HTTP API request schema in OpenAPI/Swagger format """ - schema = self.input_adapter.request_schema + if self.input_adapter.custom_request_schema is None: + schema = self.input_adapter.request_schema + else: + schema = self.input_adapter.custom_request_schema + if schema.get('application/json'): schema.get('application/json')[ 'example' diff --git a/tests/integration/projects/general/tests/test_microbatch.py b/tests/integration/projects/general/tests/test_microbatch.py index 7910b26e406..3a1a5e9ea76 100644 --- a/tests/integration/projects/general/tests/test_microbatch.py +++ b/tests/integration/projects/general/tests/test_microbatch.py @@ -33,6 +33,15 @@ async def test_slow_server(host): await asyncio.gather(*tasks) assert time.time() - time_start < 12 + +@pytest.mark.asyncio +async def test_fast_server(host): + if not pytest.enable_microbatch: + pytest.skip() + + A, B = 0.0002, 0.01 + data = '{"a": %s, "b": %s}' % (A, B) + req_count = 100 tasks = tuple( pytest.assert_request( @@ -46,17 +55,8 @@ async def test_slow_server(host): ) await asyncio.gather(*tasks) - -@pytest.mark.asyncio -async def test_fast_server(host): - if not pytest.enable_microbatch: - pytest.skip() - - A, B = 0.0002, 0.01 - data = '{"a": %s, "b": %s}' % (A, B) - time_start = time.time() - req_count = 500 + req_count = 200 tasks = tuple( pytest.assert_request( "POST", @@ -70,4 +70,4 @@ async def test_fast_server(host): for i in range(req_count) ) await asyncio.gather(*tasks) - assert time.time() - time_start < 5 + assert time.time() - time_start < 2 diff --git a/tests/integration/projects/general_non_batch/tests/test_meta.py b/tests/integration/projects/general_non_batch/tests/test_meta.py index f2d6522f8ab..5f2db207815 100644 --- a/tests/integration/projects/general_non_batch/tests/test_meta.py +++ b/tests/integration/projects/general_non_batch/tests/test_meta.py @@ -7,17 +7,3 @@ async def test_api_server_meta(host): await pytest.assert_request("GET", f"http://{host}/") await pytest.assert_request("GET", f"http://{host}/healthz") await pytest.assert_request("GET", f"http://{host}/docs.json") - - -@pytest.mark.asyncio -async def test_customized_request_schema(host): - def has_customized_schema(doc_bytes): - json_str = doc_bytes.decode() - return "field1" in json_str - - await pytest.assert_request( - "GET", - f"http://{host}/docs.json", - headers=(("Content-Type", "application/json"),), - assert_data=has_customized_schema, - ) diff --git a/tests/integration/utils.py b/tests/integration/utils.py index 2fd91589fab..c6e076e960d 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -16,20 +16,21 @@ def _wait_until_api_server_ready(host_url, timeout, container=None, check_interval=1): start_time = time.time() + proxy_handler = urllib.request.ProxyHandler({}) + opener = urllib.request.build_opener(proxy_handler) + ex = None while time.time() - start_time < timeout: try: - if ( - urllib.request.urlopen(f'http://{host_url}/healthz', timeout=1).status - == 200 - ): - break + if opener.open(f'http://{host_url}/healthz', timeout=1).status == 200: + return elif container.status != "running": break else: logger.info("Waiting for host %s to be ready..", host_url) time.sleep(check_interval) except Exception as e: # pylint:disable=broad-except - logger.info(f"'{e}', retrying to connect to the host {host_url}...") + logger.info(f"retrying to connect to the host {host_url}...") + ex = e time.sleep(check_interval) finally: if container: @@ -40,7 +41,8 @@ def _wait_until_api_server_ready(host_url, timeout, container=None, check_interv logger.info(f">>> {log_record}") else: raise AssertionError( - f"Timed out waiting {timeout} seconds for Server {host_url} to be ready" + f"Timed out waiting {timeout} seconds for Server {host_url} to be ready, " + f"exception: {ex}" ) @@ -148,6 +150,8 @@ def print_log(p): ) as p: host_url = f"127.0.0.1:{port}" threading.Thread(target=print_log, args=(p,), daemon=True).start() - _wait_until_api_server_ready(host_url, timeout=timeout) - yield host_url - p.terminate() + try: + _wait_until_api_server_ready(host_url, timeout=timeout) + yield host_url + finally: + p.terminate()