From ecc942f48070a445aab2264746cd36b3e5487966 Mon Sep 17 00:00:00 2001 From: vishal Date: Wed, 25 Sep 2019 23:47:23 -0400 Subject: [PATCH 01/24] Cortex python client --- .../cortex/client/cortex/__init__.py | 0 pkg/workloads/cortex/client/cortex/client.py | 109 ++++++++++++++++++ pkg/workloads/cortex/client/setup.py | 12 ++ pkg/workloads/cortex/lib/context.py | 22 +++- pkg/workloads/cortex/lib/requirements.txt | 1 + 5 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 pkg/workloads/cortex/client/cortex/__init__.py create mode 100644 pkg/workloads/cortex/client/cortex/client.py create mode 100644 pkg/workloads/cortex/client/setup.py diff --git a/pkg/workloads/cortex/client/cortex/__init__.py b/pkg/workloads/cortex/client/cortex/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py new file mode 100644 index 0000000000..624f48749b --- /dev/null +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -0,0 +1,109 @@ +# Copyright 2019 Cortex Labs, Inc. +# +# 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. + +import pathlib +import os +import types +import subprocess +import sys +import shutil +import yaml +import urllib.parse + +import dill +import requests + + +class Client(object): + def __init__( + self, + aws_access_key_id, + aws_secret_access_key, + operator_url, + deployment_name, + workspace="./workspace", + ): + self.operator_url = operator_url + self.deployment_name = deployment_name + self.workspace = workspace + self.aws_access_key_id = aws_access_key_id + self.aws_secret_access_key = aws_secret_access_key + self.headers = { + "CortexAPIVersion": "master", + "Authorization": "CortexAWS {}|{}".format( + self.aws_access_key_id, self.aws_secret_access_key + ), + } + + pathlib.Path(self.workspace).mkdir(parents=True, exist_ok=True) + + def deploy(self, api_name, model_path, pre_inference, post_inference): + working_dir = os.path.join(self.workspace, self.deployment_name) + api_working_dir = os.path.join(working_dir, api_name) + pathlib.Path(api_working_dir).mkdir(parents=True, exist_ok=True) + + reqs = subprocess.check_output([sys.executable, "-m", "pip", "freeze"]) + + with open(os.path.join(api_working_dir, "requirements.txt"), "w") as f: + f.writelines(reqs.decode()) + + with open(os.path.join(api_working_dir, "request_handler.pickle"), "wb") as f: + dill.dump( + {"pre_inference": pre_inference, "post_inference": post_inference}, f, recurse=True + ) + + cortex_config = [ + {"kind": "deployment", "name": self.deployment_name}, + { + "kind": "api", + "model": model_path, + "name": api_name, + "request_handler": "request_handler.pickle", + }, + ] + + cortex_yaml_path = os.path.join(working_dir, "cortex.yaml") + with open(cortex_yaml_path, "w") as f: + f.write(yaml.dump(cortex_config)) + + shutil.make_archive("project", "zip", api_working_dir) + project_zip_path = os.path.join(working_dir, "project.zip") + + queries = {"force": "false", "ignoreCache": "false"} + + with open(cortex_yaml_path, "rb") as config, open(project_zip_path, "rb") as project: + files = {"cortex.yaml": config, "project.zip": project} + print(urllib.parse.urljoin(self.operator_url, "deploy")) + resp = requests.post( + urllib.parse.urljoin(self.operator_url, "deploy"), + params=queries, + files=files, + headers=self.headers, + verify=False, + ) + print(resp.json()) + + + def get(self, api_name): + queries = {"appName": self.deployment_name} + + resp = requests.get( + urllib.parse.urljoin(self.operator_url, "get"), + params=queries, + headers=self.headers, + verify=False, + ) + + resources = resp.json() + urllib.parse.urljoin(resources['apis_base_url'], resources[]) diff --git a/pkg/workloads/cortex/client/setup.py b/pkg/workloads/cortex/client/setup.py new file mode 100644 index 0000000000..2fe9ac83e7 --- /dev/null +++ b/pkg/workloads/cortex/client/setup.py @@ -0,0 +1,12 @@ +from setuptools import setup + + +setup( + name="cortex", + version="0.8.0", + description="", + author="Cortex Labs", + author_email="dev@cortexlabs.com", + install_requires=["dill==0.3.0", "requests==2.21.0"], + setup_requires=["setuptools"], +) diff --git a/pkg/workloads/cortex/lib/context.py b/pkg/workloads/cortex/lib/context.py index 1106b58deb..0b6efe45e1 100644 --- a/pkg/workloads/cortex/lib/context.py +++ b/pkg/workloads/cortex/lib/context.py @@ -15,8 +15,10 @@ import os import imp import inspect + import boto3 import datadog +import dill from cortex import consts from cortex.lib import util @@ -108,10 +110,22 @@ def download_python_file(self, impl_key, module_name): def load_module(self, module_prefix, module_name, impl_path): full_module_name = "{}_{}".format(module_prefix, module_name) - try: - impl = imp.load_source(full_module_name, impl_path) - except Exception as e: - raise UserException("unable to load python file", str(e)) from e + + if impl_path.endswith("pickle"): + impl = imp.new_module(full_module_name) + + logger.info(impl_path) + with open(impl_path, "rb") as pickle_file: + pickled_dict = dill.load(pickle_file) + logger.info(pickled_dict) + for key in pickled_dict: + logger.info(key) + setattr(impl, key, pickled_dict[key]) + else: + try: + impl = imp.load_source(full_module_name, impl_path) + except Exception as e: + raise UserException("unable to load python file", str(e)) from e return impl diff --git a/pkg/workloads/cortex/lib/requirements.txt b/pkg/workloads/cortex/lib/requirements.txt index 3a721783a3..af7fa9db73 100644 --- a/pkg/workloads/cortex/lib/requirements.txt +++ b/pkg/workloads/cortex/lib/requirements.txt @@ -4,3 +4,4 @@ numpy==1.17.2 json_tricks==3.13.2 requests==2.22.0 datadog==0.30.0 +dill==0.3.0 \ No newline at end of file From 5d383f818485159239d95a9ebf037ab57e7360ae Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 11:38:12 -0400 Subject: [PATCH 02/24] Progress --- pkg/workloads/cortex/client/cortex/client.py | 34 ++++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 624f48749b..2f37476fba 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -1,17 +1,3 @@ -# Copyright 2019 Cortex Labs, Inc. -# -# 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. - import pathlib import os import types @@ -77,8 +63,10 @@ def deploy(self, api_name, model_path, pre_inference, post_inference): with open(cortex_yaml_path, "w") as f: f.write(yaml.dump(cortex_config)) - shutil.make_archive("project", "zip", api_working_dir) - project_zip_path = os.path.join(working_dir, "project.zip") + print(api_working_dir) + project_zip_path = os.path.join(working_dir, "project") + shutil.make_archive(project_zip_path, "zip", api_working_dir) + project_zip_path += ".zip" queries = {"force": "false", "ignoreCache": "false"} @@ -94,16 +82,20 @@ def deploy(self, api_name, model_path, pre_inference, post_inference): ) print(resp.json()) - - def get(self, api_name): + def get_endpoint(self, api_name): queries = {"appName": self.deployment_name} resp = requests.get( - urllib.parse.urljoin(self.operator_url, "get"), + urllib.parse.urljoin(self.operator_url, "resources"), params=queries, headers=self.headers, verify=False, ) - + print(resp) resources = resp.json() - urllib.parse.urljoin(resources['apis_base_url'], resources[]) + print( + urllib.parse.urljoin( + resources["apis_base_url"], + resources["api_name_statuses"][api_name]["active_status"]["path"], + ) + ) From 8cfe00293e81566f14fa157d6ffc7f4b59308729 Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 11:48:02 -0400 Subject: [PATCH 03/24] Enforce python version in setup.py --- pkg/workloads/cortex/client/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/workloads/cortex/client/setup.py b/pkg/workloads/cortex/client/setup.py index 2fe9ac83e7..2c093f8916 100644 --- a/pkg/workloads/cortex/client/setup.py +++ b/pkg/workloads/cortex/client/setup.py @@ -9,4 +9,5 @@ author_email="dev@cortexlabs.com", install_requires=["dill==0.3.0", "requests==2.21.0"], setup_requires=["setuptools"], + python_requires=">=3.6", ) From afb98347a0298bc1776430547f5f920f3f532939 Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 11:56:47 -0400 Subject: [PATCH 04/24] another version --- pkg/workloads/cortex/client/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/workloads/cortex/client/setup.py b/pkg/workloads/cortex/client/setup.py index 2c093f8916..19b4ffbb91 100644 --- a/pkg/workloads/cortex/client/setup.py +++ b/pkg/workloads/cortex/client/setup.py @@ -9,5 +9,5 @@ author_email="dev@cortexlabs.com", install_requires=["dill==0.3.0", "requests==2.21.0"], setup_requires=["setuptools"], - python_requires=">=3.6", + python_requires=[">=3.6", "==2.7.*"], ) From 769512dda10b68e2ecf7728692367fdbfa3f2981 Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 12:00:52 -0400 Subject: [PATCH 05/24] Again --- pkg/workloads/cortex/client/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/workloads/cortex/client/setup.py b/pkg/workloads/cortex/client/setup.py index 19b4ffbb91..d3cb19ecc6 100644 --- a/pkg/workloads/cortex/client/setup.py +++ b/pkg/workloads/cortex/client/setup.py @@ -9,5 +9,5 @@ author_email="dev@cortexlabs.com", install_requires=["dill==0.3.0", "requests==2.21.0"], setup_requires=["setuptools"], - python_requires=[">=3.6", "==2.7.*"], + python_requires=[">=3.6", "==2.7"], ) From 23334cda253c34fa121b32b55ac50dbf546e7bb5 Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 12:03:45 -0400 Subject: [PATCH 06/24] Back to 3.6 --- pkg/workloads/cortex/client/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/workloads/cortex/client/setup.py b/pkg/workloads/cortex/client/setup.py index d3cb19ecc6..2c093f8916 100644 --- a/pkg/workloads/cortex/client/setup.py +++ b/pkg/workloads/cortex/client/setup.py @@ -9,5 +9,5 @@ author_email="dev@cortexlabs.com", install_requires=["dill==0.3.0", "requests==2.21.0"], setup_requires=["setuptools"], - python_requires=[">=3.6", "==2.7"], + python_requires=">=3.6", ) From 5d6cb68cbe2b7a49e77bdaf67d5223bdd3b519d3 Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 12:27:10 -0400 Subject: [PATCH 07/24] Remove python version --- pkg/workloads/cortex/client/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/workloads/cortex/client/setup.py b/pkg/workloads/cortex/client/setup.py index 2c093f8916..5fe0940e1c 100644 --- a/pkg/workloads/cortex/client/setup.py +++ b/pkg/workloads/cortex/client/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup +from setuptools import setup, find_packages setup( @@ -9,5 +9,5 @@ author_email="dev@cortexlabs.com", install_requires=["dill==0.3.0", "requests==2.21.0"], setup_requires=["setuptools"], - python_requires=">=3.6", + packages=find_packages(), ) From 75382253b74e5086336eec7423c3e1d88d8dcfaf Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 15:42:14 -0400 Subject: [PATCH 08/24] Get endpoint in deploy --- .../cortex/client/cortex/__init__.py | 15 ++++++ pkg/workloads/cortex/client/cortex/client.py | 51 ++++++++++--------- 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/__init__.py b/pkg/workloads/cortex/client/cortex/__init__.py index e69de29bb2..4005eec0fa 100644 --- a/pkg/workloads/cortex/client/cortex/__init__.py +++ b/pkg/workloads/cortex/client/cortex/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2019 Cortex Labs, Inc. +# +# 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. + +from cortex.client import Client diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 2f37476fba..ab56c121f3 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -13,15 +13,9 @@ class Client(object): def __init__( - self, - aws_access_key_id, - aws_secret_access_key, - operator_url, - deployment_name, - workspace="./workspace", + self, aws_access_key_id, aws_secret_access_key, operator_url, workspace="./workspace" ): self.operator_url = operator_url - self.deployment_name = deployment_name self.workspace = workspace self.aws_access_key_id = aws_access_key_id self.aws_secret_access_key = aws_secret_access_key @@ -34,8 +28,8 @@ def __init__( pathlib.Path(self.workspace).mkdir(parents=True, exist_ok=True) - def deploy(self, api_name, model_path, pre_inference, post_inference): - working_dir = os.path.join(self.workspace, self.deployment_name) + def deploy(self, deployment_name, api_name, model_path, pre_inference, post_inference): + working_dir = os.path.join(self.workspace, deployment_name) api_working_dir = os.path.join(working_dir, api_name) pathlib.Path(api_working_dir).mkdir(parents=True, exist_ok=True) @@ -50,7 +44,7 @@ def deploy(self, api_name, model_path, pre_inference, post_inference): ) cortex_config = [ - {"kind": "deployment", "name": self.deployment_name}, + {"kind": "deployment", "name": deployment_name}, { "kind": "api", "model": model_path, @@ -63,16 +57,16 @@ def deploy(self, api_name, model_path, pre_inference, post_inference): with open(cortex_yaml_path, "w") as f: f.write(yaml.dump(cortex_config)) - print(api_working_dir) project_zip_path = os.path.join(working_dir, "project") shutil.make_archive(project_zip_path, "zip", api_working_dir) project_zip_path += ".zip" queries = {"force": "false", "ignoreCache": "false"} + deployment_status = {} + with open(cortex_yaml_path, "rb") as config, open(project_zip_path, "rb") as project: files = {"cortex.yaml": config, "project.zip": project} - print(urllib.parse.urljoin(self.operator_url, "deploy")) resp = requests.post( urllib.parse.urljoin(self.operator_url, "deploy"), params=queries, @@ -80,10 +74,28 @@ def deploy(self, api_name, model_path, pre_inference, post_inference): headers=self.headers, verify=False, ) - print(resp.json()) + deployment_status = resp.json() + + if resp.status_code / 100 != 2: + return deployment_status + + if resp.status_code / 100 == 2: + print(deployment_status["message"]) + endpoint_response = self.get_endpoint(deployment_name, api_name) + + if endpoint_response.status_code / 100 != 2: + return endpoint_response - def get_endpoint(self, api_name): - queries = {"appName": self.deployment_name} + resources = endpoint_response.json() + return { + "url": urllib.parse.urljoin( + resources["apis_base_url"], + resources["api_name_statuses"][api_name]["active_status"]["path"], + ) + } + + def get_endpoint(self, deployment_name, api_name): + queries = {"appName": deployment_name} resp = requests.get( urllib.parse.urljoin(self.operator_url, "resources"), @@ -91,11 +103,4 @@ def get_endpoint(self, api_name): headers=self.headers, verify=False, ) - print(resp) - resources = resp.json() - print( - urllib.parse.urljoin( - resources["apis_base_url"], - resources["api_name_statuses"][api_name]["active_status"]["path"], - ) - ) + return resp From 6369e2ceb0f5113f2366da7aa5242e37b79fdc8b Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 15:48:47 -0400 Subject: [PATCH 09/24] Fix linting --- pkg/workloads/cortex/client/cortex/client.py | 14 ++++++++++++++ pkg/workloads/cortex/client/setup.py | 15 ++++++++++++++- pkg/workloads/cortex/lib/requirements.txt | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index ab56c121f3..9bb03699ce 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -1,3 +1,17 @@ +# Copyright 2019 Cortex Labs, Inc. +# +# 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. + import pathlib import os import types diff --git a/pkg/workloads/cortex/client/setup.py b/pkg/workloads/cortex/client/setup.py index 5fe0940e1c..fc771d8fb8 100644 --- a/pkg/workloads/cortex/client/setup.py +++ b/pkg/workloads/cortex/client/setup.py @@ -1,5 +1,18 @@ -from setuptools import setup, find_packages +# Copyright 2019 Cortex Labs, Inc. +# +# 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. +from setuptools import setup, find_packages setup( name="cortex", diff --git a/pkg/workloads/cortex/lib/requirements.txt b/pkg/workloads/cortex/lib/requirements.txt index af7fa9db73..fda1ae01b3 100644 --- a/pkg/workloads/cortex/lib/requirements.txt +++ b/pkg/workloads/cortex/lib/requirements.txt @@ -4,4 +4,4 @@ numpy==1.17.2 json_tricks==3.13.2 requests==2.22.0 datadog==0.30.0 -dill==0.3.0 \ No newline at end of file +dill==0.3.0 From f65d6b64169da18188201262e0e77990a2821fe7 Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 16:24:49 -0400 Subject: [PATCH 10/24] Raise exception when requests fail --- pkg/workloads/cortex/client/cortex/client.py | 67 +++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 9bb03699ce..236f04c149 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -23,6 +23,7 @@ import dill import requests +from requests.exceptions import HTTPError class Client(object): @@ -81,40 +82,46 @@ def deploy(self, deployment_name, api_name, model_path, pre_inference, post_infe with open(cortex_yaml_path, "rb") as config, open(project_zip_path, "rb") as project: files = {"cortex.yaml": config, "project.zip": project} - resp = requests.post( - urllib.parse.urljoin(self.operator_url, "deploy"), - params=queries, - files=files, - headers=self.headers, - verify=False, - ) - deployment_status = resp.json() - - if resp.status_code / 100 != 2: - return deployment_status + try: + resp = requests.post( + urllib.parse.urljoin(self.operator_url, "deploy"), + params=queries, + files=files, + headers=self.headers, + verify=False, + ) + resp.raise_for_status() + deployment_status = resp.json() + except HTTPError as err: + resp = err.response + if "error" in resp.json(): + raise Exception(resp.json()["error"]) from err + raise if resp.status_code / 100 == 2: - print(deployment_status["message"]) - endpoint_response = self.get_endpoint(deployment_name, api_name) - - if endpoint_response.status_code / 100 != 2: - return endpoint_response + return self.get_endpoint(deployment_name, api_name) - resources = endpoint_response.json() - return { - "url": urllib.parse.urljoin( - resources["apis_base_url"], - resources["api_name_statuses"][api_name]["active_status"]["path"], - ) - } + return None def get_endpoint(self, deployment_name, api_name): queries = {"appName": deployment_name} - resp = requests.get( - urllib.parse.urljoin(self.operator_url, "resources"), - params=queries, - headers=self.headers, - verify=False, - ) - return resp + try: + resp = requests.get( + urllib.parse.urljoin(self.operator_url, "resources"), + params=queries, + headers=self.headers, + verify=False, + ) + resp.raise_for_status() + + resources = resp.json() + return urllib.parse.urljoin( + resources["apis_base_url"], + resources["api_name_statuses"][api_name]["active_status"]["path"], + ) + except HTTPError as err: + resp = err.response + if "error" in resp.json(): + raise Exception(resp.json()["error"]) from err + raise From 64dba0b880079b43e44b20ea0d582be751446feb Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 16:47:00 -0400 Subject: [PATCH 11/24] Remove logging statements --- pkg/workloads/cortex/client/cortex/client.py | 2 +- pkg/workloads/cortex/lib/context.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 236f04c149..8a14925843 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -98,7 +98,7 @@ def deploy(self, deployment_name, api_name, model_path, pre_inference, post_infe raise Exception(resp.json()["error"]) from err raise - if resp.status_code / 100 == 2: + if resp.status_code == 200: return self.get_endpoint(deployment_name, api_name) return None diff --git a/pkg/workloads/cortex/lib/context.py b/pkg/workloads/cortex/lib/context.py index 0b6efe45e1..5c3dedba96 100644 --- a/pkg/workloads/cortex/lib/context.py +++ b/pkg/workloads/cortex/lib/context.py @@ -114,12 +114,9 @@ def load_module(self, module_prefix, module_name, impl_path): if impl_path.endswith("pickle"): impl = imp.new_module(full_module_name) - logger.info(impl_path) with open(impl_path, "rb") as pickle_file: pickled_dict = dill.load(pickle_file) - logger.info(pickled_dict) for key in pickled_dict: - logger.info(key) setattr(impl, key, pickled_dict[key]) else: try: From a8f5cd0c0518ce72a8a657b9c30e874f42c5a511 Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 16:58:04 -0400 Subject: [PATCH 12/24] Cleanup rount 1 --- pkg/workloads/cortex/client/cortex/client.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 8a14925843..6ad8967d48 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -78,8 +78,6 @@ def deploy(self, deployment_name, api_name, model_path, pre_inference, post_infe queries = {"force": "false", "ignoreCache": "false"} - deployment_status = {} - with open(cortex_yaml_path, "rb") as config, open(project_zip_path, "rb") as project: files = {"cortex.yaml": config, "project.zip": project} try: @@ -91,7 +89,6 @@ def deploy(self, deployment_name, api_name, model_path, pre_inference, post_infe verify=False, ) resp.raise_for_status() - deployment_status = resp.json() except HTTPError as err: resp = err.response if "error" in resp.json(): From eb4a6f620e3b6605e54581864b9165ca6ae6ea1d Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 18:07:59 -0400 Subject: [PATCH 13/24] Print resources --- pkg/workloads/cortex/client/cortex/client.py | 2 ++ pkg/workloads/cortex/client/setup.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 6ad8967d48..0829e79cf9 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -24,6 +24,7 @@ import dill import requests from requests.exceptions import HTTPError +import msgpack class Client(object): @@ -113,6 +114,7 @@ def get_endpoint(self, deployment_name, api_name): resp.raise_for_status() resources = resp.json() + print(resources) return urllib.parse.urljoin( resources["apis_base_url"], resources["api_name_statuses"][api_name]["active_status"]["path"], diff --git a/pkg/workloads/cortex/client/setup.py b/pkg/workloads/cortex/client/setup.py index fc771d8fb8..601ebb6fd5 100644 --- a/pkg/workloads/cortex/client/setup.py +++ b/pkg/workloads/cortex/client/setup.py @@ -20,7 +20,7 @@ description="", author="Cortex Labs", author_email="dev@cortexlabs.com", - install_requires=["dill==0.3.0", "requests==2.21.0"], + install_requires=["dill==0.3.0", "requests==2.21.0", "msgpack==0.6.1"], setup_requires=["setuptools"], packages=find_packages(), ) From 94e56ef813df89fb64ad77b271f0e17d98a34934 Mon Sep 17 00:00:00 2001 From: vishal Date: Thu, 26 Sep 2019 19:34:24 -0400 Subject: [PATCH 14/24] Get path from context --- pkg/workloads/cortex/client/cortex/client.py | 48 ++++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 0829e79cf9..415709b662 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -20,6 +20,7 @@ import shutil import yaml import urllib.parse +import base64 import dill import requests @@ -97,30 +98,27 @@ def deploy(self, deployment_name, api_name, model_path, pre_inference, post_infe raise if resp.status_code == 200: - return self.get_endpoint(deployment_name, api_name) + queries = {"appName": deployment_name} + + try: + resp = requests.get( + urllib.parse.urljoin(self.operator_url, "resources"), + params=queries, + headers=self.headers, + verify=False, + ) + resp.raise_for_status() + resources = resp.json() + b64_encoded_context = resources["context"] + context_msgpack_bytestring = base64.b64decode(b64_encoded_context) + ctx = msgpack.loads(context_msgpack_bytestring, raw=False) + return urllib.parse.urljoin( + resources["apis_base_url"], ctx["apis"][api_name]["path"] + ) + except HTTPError as err: + resp = err.response + if "error" in resp.json(): + raise Exception(resp.json()["error"]) from err + raise return None - - def get_endpoint(self, deployment_name, api_name): - queries = {"appName": deployment_name} - - try: - resp = requests.get( - urllib.parse.urljoin(self.operator_url, "resources"), - params=queries, - headers=self.headers, - verify=False, - ) - resp.raise_for_status() - - resources = resp.json() - print(resources) - return urllib.parse.urljoin( - resources["apis_base_url"], - resources["api_name_statuses"][api_name]["active_status"]["path"], - ) - except HTTPError as err: - resp = err.response - if "error" in resp.json(): - raise Exception(resp.json()["error"]) from err - raise From 38812bb1aa9d4cb273b779d2e7fdb049a7bc2ac2 Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 27 Sep 2019 16:56:16 -0400 Subject: [PATCH 15/24] Add docs and respond PR comments --- dev/versions.md | 1 + docs/cluster/python-client.md | 44 ++++++++ pkg/operator/api/schema/schema.go | 4 +- pkg/operator/endpoints/deploy.go | 23 ++-- pkg/workloads/cortex/client/cortex/client.py | 112 +++++++++++-------- pkg/workloads/cortex/client/setup.py | 2 +- pkg/workloads/cortex/lib/context.py | 15 ++- 7 files changed, 140 insertions(+), 61 deletions(-) create mode 100644 docs/cluster/python-client.md diff --git a/dev/versions.md b/dev/versions.md index c04bb4af42..57df5fff6b 100644 --- a/dev/versions.md +++ b/dev/versions.md @@ -103,6 +103,7 @@ Note: it's ok if example training notebooks aren't upgraded, as long as the expo * [flask](https://pypi.org/project/flask/) * [flask-api](https://pypi.org/project/flask-api/) * [waitress](https://pypi.org/project/waitress/) + * [dill](https://pypi.org/project/dill/) 1. Update the versions listed in "Pre-installed Packages" in `request-handlers.py` ## Istio diff --git a/docs/cluster/python-client.md b/docs/cluster/python-client.md new file mode 100644 index 0000000000..5999d08c0a --- /dev/null +++ b/docs/cluster/python-client.md @@ -0,0 +1,44 @@ +# Python Client + +Python Client can be used to programmatically deploy to a Cortex Cluster. + + +``` +pip install git+https://github.com/cortexlabs/cortex.git@master#egg=cortex\&subdirectory=pkg/workloads/cortex/client +``` + +Python client needs to be initialized with AWS credentials and a url to the operator your Cortex cluster. + +```python +from cortex import Client + +cortex = Client( + aws_access_key_id="", # AWS access key associated with the account that created the Cortex cluster + aws_secret_access_key="", # AWS secrey key associated with the provided AWS access key + operator_url="" # Operator URL of your cortex cluster +) + +api_url = cortex.deploy( + deployment_name="", # Deployment name (required) + api_name="", # API name (required) + model_path="", # S3 path to an exported model (required) + pre_inference=callable, # a function used to prepare requests for model input + post_inference=callable, # a function used to prepare model output for response + model_format="", # model format, must be "tensorflow" or "onnx" (default: "onnx" if model path ends with .onnx, "tensorflow" if model path ends with .zip or is a directory) + tf_serving_key="" # name of the signature def to use for prediction (required if your model has more than one signature def) +) +``` + +`api_url` is the url to the deployed API, it accepts JSON POST requests. + +```python +import requests + +sample = { + "feature_1" [0,1,2] +} + +resp = requests.post(api_url, json=sample) + +resp.json() # your model prediction +``` \ No newline at end of file diff --git a/pkg/operator/api/schema/schema.go b/pkg/operator/api/schema/schema.go index 1ded820ae0..05735d3291 100644 --- a/pkg/operator/api/schema/schema.go +++ b/pkg/operator/api/schema/schema.go @@ -24,7 +24,9 @@ import ( ) type DeployResponse struct { - Message string `json:"message"` + Message string `json:"message"` + Context *context.Context `json:"context"` + APIsBaseURL string `json:"apis_base_url"` } type DeleteResponse struct { diff --git a/pkg/operator/endpoints/deploy.go b/pkg/operator/endpoints/deploy.go index 968563c7ec..5a39bdaf9a 100644 --- a/pkg/operator/endpoints/deploy.go +++ b/pkg/operator/endpoints/deploy.go @@ -117,20 +117,29 @@ func Deploy(w http.ResponseWriter, r *http.Request) { return } + apisBaseURL, err := workloads.APIsBaseURL() + if err != nil { + RespondError(w, err) + return + } + + deployResponse := schema.DeployResponse{Context: ctx, APIsBaseURL: apisBaseURL} switch { case isUpdating && ignoreCache: - Respond(w, schema.DeployResponse{Message: ResCachedDeletedDeploymentStarted}) + deployResponse.Message = ResCachedDeletedDeploymentStarted case isUpdating && !ignoreCache: - Respond(w, schema.DeployResponse{Message: ResDeploymentUpdated}) + deployResponse.Message = ResDeploymentUpdated case !isUpdating && ignoreCache: - Respond(w, schema.DeployResponse{Message: ResCachedDeletedDeploymentStarted}) + deployResponse.Message = ResCachedDeletedDeploymentStarted case !isUpdating && !ignoreCache && existingCtx == nil: - Respond(w, schema.DeployResponse{Message: ResDeploymentStarted}) + deployResponse.Message = ResDeploymentStarted case !isUpdating && !ignoreCache && existingCtx != nil && !fullCtxMatch: - Respond(w, schema.DeployResponse{Message: ResDeploymentUpdated}) + deployResponse.Message = ResDeploymentUpdated case !isUpdating && !ignoreCache && existingCtx != nil && fullCtxMatch: - Respond(w, schema.DeployResponse{Message: ResDeploymentUpToDate}) + deployResponse.Message = ResDeploymentUpToDate default: - Respond(w, schema.DeployResponse{Message: ResDeploymentUpdated}) + deployResponse.Message = ResDeploymentUpdated } + + Respond(w, deployResponse) } diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 415709b662..9757b42899 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -13,6 +13,7 @@ # limitations under the License. import pathlib +from pathlib import Path import os import types import subprocess @@ -29,11 +30,17 @@ class Client(object): - def __init__( - self, aws_access_key_id, aws_secret_access_key, operator_url, workspace="./workspace" - ): + def __init__(self, aws_access_key_id, aws_secret_access_key, operator_url): + """Initialize a Client to a Cortex Operator + + Args: + aws_access_key_id (string): AWS access key associated with the account that created the Cortex Cluster + aws_secret_access_key (string): AWS secrey key associated with the provided AWS access key + operator_url (string): Operator URL of your cortex cluster + """ + self.operator_url = operator_url - self.workspace = workspace + self.workspace = str(Path.home() / ".cortex" / "workspace") self.aws_access_key_id = aws_access_key_id self.aws_secret_access_key = aws_secret_access_key self.headers = { @@ -45,30 +52,63 @@ def __init__( pathlib.Path(self.workspace).mkdir(parents=True, exist_ok=True) - def deploy(self, deployment_name, api_name, model_path, pre_inference, post_inference): + def deploy( + self, + deployment_name, + api_name, + model_path, + pre_inference=None, + post_inference=None, + model_format=None, + tf_serving_key=None, + ): + """Deploy an API + + Args: + deployment_name (string): Deployment name + api_name (string): API name + model_path (string): S3 path to an exported model + pre_inference (function, optional): a function used to prepare requests for model input. Defaults to None. + post_inference (function, optional): a function used to prepare model output for response. Defaults to None. + model_format (string, optional): model format, must be "tensorflow" or "onnx" (default: "onnx" if model path ends with .onnx, "tensorflow" if model path ends with .zip or is a directory) + tf_serving_key (string, optional): name of the signature def to use for prediction (required if your model has more than one signature def) + + Returns: + string: url to the deployed API + """ + working_dir = os.path.join(self.workspace, deployment_name) api_working_dir = os.path.join(working_dir, api_name) pathlib.Path(api_working_dir).mkdir(parents=True, exist_ok=True) - reqs = subprocess.check_output([sys.executable, "-m", "pip", "freeze"]) + api_config = { + "kind": "api", + "model": model_path, + "name": api_name, + "model_format": model_format, + "tf_serving_key": tf_serving_key, + } + + if pre_inference is not None or post_inference is not None: + reqs = subprocess.check_output([sys.executable, "-m", "pip", "freeze"]) + + with open(os.path.join(api_working_dir, "requirements.txt"), "w") as f: + f.writelines(reqs.decode()) + + handlers = {} + + if pre_inference is not None: + handlers["pre_inference"] = pre_inference + + if post_inference is not None: + handlers["post_inference"] = post_inference - with open(os.path.join(api_working_dir, "requirements.txt"), "w") as f: - f.writelines(reqs.decode()) + with open(os.path.join(api_working_dir, "request_handler.pickle"), "wb") as f: + dill.dump(handlers, f, recurse=True) - with open(os.path.join(api_working_dir, "request_handler.pickle"), "wb") as f: - dill.dump( - {"pre_inference": pre_inference, "post_inference": post_inference}, f, recurse=True - ) + api_config["request_handler"] = "request_handler.pickle" - cortex_config = [ - {"kind": "deployment", "name": deployment_name}, - { - "kind": "api", - "model": model_path, - "name": api_name, - "request_handler": "request_handler.pickle", - }, - ] + cortex_config = [{"kind": "deployment", "name": deployment_name}, api_config] cortex_yaml_path = os.path.join(working_dir, "cortex.yaml") with open(cortex_yaml_path, "w") as f: @@ -91,34 +131,14 @@ def deploy(self, deployment_name, api_name, model_path, pre_inference, post_infe verify=False, ) resp.raise_for_status() + resources = resp.json() except HTTPError as err: resp = err.response if "error" in resp.json(): raise Exception(resp.json()["error"]) from err raise - if resp.status_code == 200: - queries = {"appName": deployment_name} - - try: - resp = requests.get( - urllib.parse.urljoin(self.operator_url, "resources"), - params=queries, - headers=self.headers, - verify=False, - ) - resp.raise_for_status() - resources = resp.json() - b64_encoded_context = resources["context"] - context_msgpack_bytestring = base64.b64decode(b64_encoded_context) - ctx = msgpack.loads(context_msgpack_bytestring, raw=False) - return urllib.parse.urljoin( - resources["apis_base_url"], ctx["apis"][api_name]["path"] - ) - except HTTPError as err: - resp = err.response - if "error" in resp.json(): - raise Exception(resp.json()["error"]) from err - raise - - return None + b64_encoded_context = resources["context"] + context_msgpack_bytestring = base64.b64decode(b64_encoded_context) + ctx = msgpack.loads(context_msgpack_bytestring, raw=False) + return urllib.parse.urljoin(resources["apis_base_url"], ctx["apis"][api_name]["path"]) diff --git a/pkg/workloads/cortex/client/setup.py b/pkg/workloads/cortex/client/setup.py index 601ebb6fd5..bd00510ca8 100644 --- a/pkg/workloads/cortex/client/setup.py +++ b/pkg/workloads/cortex/client/setup.py @@ -20,7 +20,7 @@ description="", author="Cortex Labs", author_email="dev@cortexlabs.com", - install_requires=["dill==0.3.0", "requests==2.21.0", "msgpack==0.6.1"], + install_requires=["dill>=0.3.0", "requests>=2.20.0", "msgpack>=0.6.0"], setup_requires=["setuptools"], packages=find_packages(), ) diff --git a/pkg/workloads/cortex/lib/context.py b/pkg/workloads/cortex/lib/context.py index 5c3dedba96..e10617fdd9 100644 --- a/pkg/workloads/cortex/lib/context.py +++ b/pkg/workloads/cortex/lib/context.py @@ -111,13 +111,16 @@ def download_python_file(self, impl_key, module_name): def load_module(self, module_prefix, module_name, impl_path): full_module_name = "{}_{}".format(module_prefix, module_name) - if impl_path.endswith("pickle"): - impl = imp.new_module(full_module_name) + if impl_path.endswith(".pickle"): + try: + impl = imp.new_module(full_module_name) - with open(impl_path, "rb") as pickle_file: - pickled_dict = dill.load(pickle_file) - for key in pickled_dict: - setattr(impl, key, pickled_dict[key]) + with open(impl_path, "rb") as pickle_file: + pickled_dict = dill.load(pickle_file) + for key in pickled_dict: + setattr(impl, key, pickled_dict[key]) + except Exception as e: + raise UserException("unable to load pickle", str(e)) from e else: try: impl = imp.load_source(full_module_name, impl_path) From e6745c65ffc8a43f4b17e7f464d5fb07e9270b06 Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 27 Sep 2019 17:41:11 -0400 Subject: [PATCH 16/24] Use pathlib --- pkg/workloads/cortex/lib/util.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/workloads/cortex/lib/util.py b/pkg/workloads/cortex/lib/util.py index e28685a1f0..3f4f6244da 100644 --- a/pkg/workloads/cortex/lib/util.py +++ b/pkg/workloads/cortex/lib/util.py @@ -23,6 +23,7 @@ import zipfile import hashlib import msgpack +import pathlib from copy import deepcopy from datetime import datetime @@ -62,13 +63,7 @@ def snake_to_camel(input, sep="_", lower=True): def mkdir_p(dir_path): - try: - os.makedirs(dir_path) - except OSError as e: - if e.errno == errno.EEXIST and os.path.isdir(dir_path): - pass - else: - raise + pathlib.Path(dir_path).mkdir(parents=True, exist_ok=True) def rm_dir(dir_path): From bb6d8d2b3978aec9c5466ba9d2aaacc641363788 Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 27 Sep 2019 17:42:17 -0400 Subject: [PATCH 17/24] Drop articles --- docs/cluster/python-client.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/cluster/python-client.md b/docs/cluster/python-client.md index 5999d08c0a..b036995e47 100644 --- a/docs/cluster/python-client.md +++ b/docs/cluster/python-client.md @@ -19,11 +19,11 @@ cortex = Client( ) api_url = cortex.deploy( - deployment_name="", # Deployment name (required) + deployment_name="", # deployment name (required) api_name="", # API name (required) model_path="", # S3 path to an exported model (required) - pre_inference=callable, # a function used to prepare requests for model input - post_inference=callable, # a function used to prepare model output for response + pre_inference=callable, # function used to prepare requests for model input + post_inference=callable, # function used to prepare model output for response model_format="", # model format, must be "tensorflow" or "onnx" (default: "onnx" if model path ends with .onnx, "tensorflow" if model path ends with .zip or is a directory) tf_serving_key="" # name of the signature def to use for prediction (required if your model has more than one signature def) ) From 421bd086259a68d5641c3ac64a0b61f251f7e520 Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 27 Sep 2019 17:42:41 -0400 Subject: [PATCH 18/24] Drop leading articles in python docs --- pkg/workloads/cortex/client/cortex/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 9757b42899..ad5d5ca12a 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -65,11 +65,11 @@ def deploy( """Deploy an API Args: - deployment_name (string): Deployment name + deployment_name (string): deployment name api_name (string): API name model_path (string): S3 path to an exported model - pre_inference (function, optional): a function used to prepare requests for model input. Defaults to None. - post_inference (function, optional): a function used to prepare model output for response. Defaults to None. + pre_inference (function, optional): function used to prepare requests for model input. Defaults to None. + post_inference (function, optional): function used to prepare model output for response. Defaults to None. model_format (string, optional): model format, must be "tensorflow" or "onnx" (default: "onnx" if model path ends with .onnx, "tensorflow" if model path ends with .zip or is a directory) tf_serving_key (string, optional): name of the signature def to use for prediction (required if your model has more than one signature def) From 12645609ad956b77fe23b6e356f79fc67003ce9a Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 27 Sep 2019 17:44:33 -0400 Subject: [PATCH 19/24] Docs tweak --- docs/cluster/python-client.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/cluster/python-client.md b/docs/cluster/python-client.md index b036995e47..2aa87e645d 100644 --- a/docs/cluster/python-client.md +++ b/docs/cluster/python-client.md @@ -9,6 +9,8 @@ pip install git+https://github.com/cortexlabs/cortex.git@master#egg=cortex\&subd Python client needs to be initialized with AWS credentials and a url to the operator your Cortex cluster. +The Operator URL can be retrieved by running `./cortex.sh endpoints` + ```python from cortex import Client @@ -29,7 +31,7 @@ api_url = cortex.deploy( ) ``` -`api_url` is the url to the deployed API, it accepts JSON POST requests. +`api_url` contains the url to the deployed API. It accepts JSON POST requests. ```python import requests From dac09611073839bef5a83039582a2f3d5926ac40 Mon Sep 17 00:00:00 2001 From: Vishal Bollu Date: Fri, 27 Sep 2019 11:59:18 -0400 Subject: [PATCH 20/24] Prevent load balancer from timing out requests (#490) --- manager/manifests/istio-values.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/manager/manifests/istio-values.yaml b/manager/manifests/istio-values.yaml index a26482e56d..6b88abb988 100644 --- a/manager/manifests/istio-values.yaml +++ b/manager/manifests/istio-values.yaml @@ -76,6 +76,8 @@ gateways: limits: cpu: 2000m memory: 1024Mi + serviceAnnotations: + service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: 10 * 60 # 10 minutes type: LoadBalancer ports: - port: 80 From 0656e67302ca9eb19a07655fd70c5eb062e8db05 Mon Sep 17 00:00:00 2001 From: Omer Spillinger <42219498+ospillinger@users.noreply.github.com> Date: Fri, 27 Sep 2019 15:20:47 -0700 Subject: [PATCH 21/24] Update python-client.md --- docs/cluster/python-client.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/cluster/python-client.md b/docs/cluster/python-client.md index 2aa87e645d..d8906964d3 100644 --- a/docs/cluster/python-client.md +++ b/docs/cluster/python-client.md @@ -1,23 +1,21 @@ # Python Client -Python Client can be used to programmatically deploy to a Cortex Cluster. +The Python client can be used to programmatically deploy models to a Cortex Cluster. ``` pip install git+https://github.com/cortexlabs/cortex.git@master#egg=cortex\&subdirectory=pkg/workloads/cortex/client ``` -Python client needs to be initialized with AWS credentials and a url to the operator your Cortex cluster. - -The Operator URL can be retrieved by running `./cortex.sh endpoints` +The Python client needs to be initialized with AWS credentials and an operator URL for your Cortex cluster. You can find the operator URL by running `./cortex.sh endpoints`. ```python from cortex import Client cortex = Client( - aws_access_key_id="", # AWS access key associated with the account that created the Cortex cluster - aws_secret_access_key="", # AWS secrey key associated with the provided AWS access key - operator_url="" # Operator URL of your cortex cluster + aws_access_key_id="", # AWS access key associated with the account that the cluster is running on + aws_secret_access_key="", # AWS secret key associated with the AWS access key + operator_url="" # operator URL of your cluster ) api_url = cortex.deploy( @@ -31,16 +29,17 @@ api_url = cortex.deploy( ) ``` -`api_url` contains the url to the deployed API. It accepts JSON POST requests. +`api_url` contains the URL of the deployed API. The API accepts JSON POST requests. ```python import requests sample = { - "feature_1" [0,1,2] + "feature_1": 'a', + "feature_2": 'b', + "feature_3": 'c' } resp = requests.post(api_url, json=sample) - -resp.json() # your model prediction -``` \ No newline at end of file +resp.json() +``` From 03feb6e6a59519c08b612f909fd62a506460f3b0 Mon Sep 17 00:00:00 2001 From: vishal Date: Fri, 27 Sep 2019 18:25:08 -0400 Subject: [PATCH 22/24] Fix linting --- pkg/workloads/cortex/client/cortex/client.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index ad5d5ca12a..2c4e99067e 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -32,10 +32,10 @@ class Client(object): def __init__(self, aws_access_key_id, aws_secret_access_key, operator_url): """Initialize a Client to a Cortex Operator - + Args: aws_access_key_id (string): AWS access key associated with the account that created the Cortex Cluster - aws_secret_access_key (string): AWS secrey key associated with the provided AWS access key + aws_secret_access_key (string): AWS secrey key associated with the provided AWS access key operator_url (string): Operator URL of your cortex cluster """ @@ -63,16 +63,16 @@ def deploy( tf_serving_key=None, ): """Deploy an API - + Args: deployment_name (string): deployment name api_name (string): API name model_path (string): S3 path to an exported model - pre_inference (function, optional): function used to prepare requests for model input. Defaults to None. - post_inference (function, optional): function used to prepare model output for response. Defaults to None. + pre_inference (function, optional): function used to prepare requests for model input + post_inference (function, optional): function used to prepare model output for response model_format (string, optional): model format, must be "tensorflow" or "onnx" (default: "onnx" if model path ends with .onnx, "tensorflow" if model path ends with .zip or is a directory) tf_serving_key (string, optional): name of the signature def to use for prediction (required if your model has more than one signature def) - + Returns: string: url to the deployed API """ From a91a662e6a32989b52f3335e76ce68e4f827c388 Mon Sep 17 00:00:00 2001 From: vishal Date: Mon, 30 Sep 2019 19:18:57 +0000 Subject: [PATCH 23/24] Line up comments --- pkg/workloads/cortex/client/cortex/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 2c4e99067e..8da70c0378 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -34,9 +34,9 @@ def __init__(self, aws_access_key_id, aws_secret_access_key, operator_url): """Initialize a Client to a Cortex Operator Args: - aws_access_key_id (string): AWS access key associated with the account that created the Cortex Cluster - aws_secret_access_key (string): AWS secrey key associated with the provided AWS access key - operator_url (string): Operator URL of your cortex cluster + aws_access_key_id (string): AWS access key associated with the account that the cluster is running on + aws_secret_access_key (string): AWS secret key associated with the AWS access key + operator_url (string): operator URL of your cluster """ self.operator_url = operator_url From b74e425aee117fdb12157b162f6ba1cf9f5207db Mon Sep 17 00:00:00 2001 From: vishal Date: Mon, 30 Sep 2019 15:31:47 -0400 Subject: [PATCH 24/24] Do not add none params --- pkg/workloads/cortex/client/cortex/client.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/workloads/cortex/client/cortex/client.py b/pkg/workloads/cortex/client/cortex/client.py index 8da70c0378..3b5058cb5a 100644 --- a/pkg/workloads/cortex/client/cortex/client.py +++ b/pkg/workloads/cortex/client/cortex/client.py @@ -81,13 +81,13 @@ def deploy( api_working_dir = os.path.join(working_dir, api_name) pathlib.Path(api_working_dir).mkdir(parents=True, exist_ok=True) - api_config = { - "kind": "api", - "model": model_path, - "name": api_name, - "model_format": model_format, - "tf_serving_key": tf_serving_key, - } + api_config = {"kind": "api", "model": model_path, "name": api_name} + + if tf_serving_key is not None: + api_config["model_format"] = tf_serving_key + + if model_format is not None: + api_config["model_format"] = model_format if pre_inference is not None or post_inference is not None: reqs = subprocess.check_output([sys.executable, "-m", "pip", "freeze"])