From 0e7572dca8c93e0ea8ac3193a5696bab2868bfaa Mon Sep 17 00:00:00 2001 From: Miguel Varela Ramos Date: Thu, 6 May 2021 12:06:18 +0200 Subject: [PATCH 1/8] Move python code out of the pkg/ directory --- .../cortex_internal/lib/model/__init__.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/python/serve/cortex_internal/lib/model/__init__.py b/python/serve/cortex_internal/lib/model/__init__.py index 7bf6fd0eac..fa322d1739 100644 --- a/python/serve/cortex_internal/lib/model/__init__.py +++ b/python/serve/cortex_internal/lib/model/__init__.py @@ -12,6 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +from cortex_internal.lib.model.cron import ( + FileBasedModelsTreeUpdater, + FileBasedModelsGC, + find_ondisk_models_with_lock, + find_ondisk_model_ids_with_lock, + find_ondisk_model_info, + TFSModelLoader, + TFSAPIServingThreadUpdater, + find_ondisk_models, + ModelsGC, + ModelTreeUpdater, +) from cortex_internal.lib.model.model import ( ModelsHolder, LockedGlobalModelsGC, @@ -30,15 +42,3 @@ validate_model_paths, ModelVersion, ) -from cortex_internal.lib.model.cron import ( - FileBasedModelsTreeUpdater, - FileBasedModelsGC, - find_ondisk_models_with_lock, - find_ondisk_model_ids_with_lock, - find_ondisk_model_info, - TFSModelLoader, - TFSAPIServingThreadUpdater, - find_ondisk_models, - ModelsGC, - ModelTreeUpdater, -) From 3a18560281309631a4c2f9855e1b02fc18840f2c Mon Sep 17 00:00:00 2001 From: Miguel Varela Ramos Date: Thu, 6 May 2021 13:06:44 +0200 Subject: [PATCH 2/8] Fix import order --- .../cortex_internal/lib/model/__init__.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/python/serve/cortex_internal/lib/model/__init__.py b/python/serve/cortex_internal/lib/model/__init__.py index fa322d1739..7bf6fd0eac 100644 --- a/python/serve/cortex_internal/lib/model/__init__.py +++ b/python/serve/cortex_internal/lib/model/__init__.py @@ -12,18 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from cortex_internal.lib.model.cron import ( - FileBasedModelsTreeUpdater, - FileBasedModelsGC, - find_ondisk_models_with_lock, - find_ondisk_model_ids_with_lock, - find_ondisk_model_info, - TFSModelLoader, - TFSAPIServingThreadUpdater, - find_ondisk_models, - ModelsGC, - ModelTreeUpdater, -) from cortex_internal.lib.model.model import ( ModelsHolder, LockedGlobalModelsGC, @@ -42,3 +30,15 @@ validate_model_paths, ModelVersion, ) +from cortex_internal.lib.model.cron import ( + FileBasedModelsTreeUpdater, + FileBasedModelsGC, + find_ondisk_models_with_lock, + find_ondisk_model_ids_with_lock, + find_ondisk_model_info, + TFSModelLoader, + TFSAPIServingThreadUpdater, + find_ondisk_models, + ModelsGC, + ModelTreeUpdater, +) From e0fed29464a311a8e0c0a509812f5bb0c1faa862 Mon Sep 17 00:00:00 2001 From: Miguel Varela Ramos Date: Thu, 6 May 2021 17:18:48 +0200 Subject: [PATCH 3/8] Create methods for deploying each kind and rename two methods create_api -> deploy delete_api -> delete --- dev/generate_python_client_md.sh | 2 +- docs/clients/python.md | 129 ++++++++-- docs/workloads/batch/handler.md | 2 +- docs/workloads/dependencies/example.md | 2 +- .../workloads/realtime/multi-model/example.md | 2 +- .../realtime/traffic-splitter/example.md | 6 +- docs/workloads/task/definitions.md | 2 +- python/client/cortex/__init__.py | 4 +- python/client/cortex/client.py | 225 ++++++++++++++++-- python/client/cortex/exceptions.py | 7 + python/client/cortex/telemetry.py | 3 +- test/apis/pytorch/iris-classifier/deploy.py | 4 +- .../pytorch/text-generator/deploy_class.py | 4 +- test/apis/sleep/deploy.py | 4 +- test/e2e/e2e/tests.py | 22 +- 15 files changed, 360 insertions(+), 58 deletions(-) diff --git a/dev/generate_python_client_md.sh b/dev/generate_python_client_md.sh index 1973082ca5..b10446f501 100755 --- a/dev/generate_python_client_md.sh +++ b/dev/generate_python_client_md.sh @@ -48,7 +48,7 @@ sed -i "/\* \[Client](#cortex\.client\.Client)/d" $docs_path sed -i "s/\* \[cortex\.client](#cortex\.client)/\* [cortex\.client\.Client](#cortex-client-client)/g" $docs_path sed -i "s/# cortex\.client/# cortex\.client\.Client/g" $docs_path # delete unnecessary section body -sed -i "/# cortex.client.Client/,/## create\\\_api/{//!d}" $docs_path +sed -i "/# cortex.client.Client/,/## deploy/{//!d}" $docs_path sed -i "s/# cortex.client.Client/# cortex.client.Client\n/g" $docs_path # fix table of contents links diff --git a/docs/clients/python.md b/docs/clients/python.md index a832c6bea7..873e9945c3 100644 --- a/docs/clients/python.md +++ b/docs/clients/python.md @@ -6,7 +6,12 @@ * [env\_list](#env_list) * [env\_delete](#env_delete) * [cortex.client.Client](#cortex-client-client) - * [create\_api](#create_api) + * [deploy](#deploy) + * [deploy\_realtime\_api](#deploy_realtime_api) + * [deploy\_async\_api](#deploy_async_api) + * [deploy\_batch\_api](#deploy_batch_api) + * [deploy\_task\_api](#deploy_task_api) + * [deploy\_traffic\_splitter](#deploy_traffic_splitter) * [get\_api](#get_api) * [list\_apis](#list_apis) * [get\_job](#get_job) @@ -57,7 +62,7 @@ Create a new environment to connect to an existing cluster, and initialize a cli ## env\_list ```python -env_list() -> list +env_list() -> List ``` List all environments configured on this machine. @@ -76,28 +81,122 @@ Delete an environment configured on this machine. # cortex.client.Client -## create\_api - - +## deploy ```python - | create_api(api_spec: dict, handler=None, task=None, requirements=[], conda_packages=[], project_dir: Optional[str] = None, force: bool = True, wait: bool = False) -> list + | deploy(api_spec: Dict[str, Any], project_dir: str, force: bool = True, wait: bool = False) ``` -Deploy an API. +Deploy an API from a project directory. **Arguments**: - `api_spec` - A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/ for schema. -- `handler` - A Cortex handler class implementation. Not required for TaskAPI/TrafficSplitter kinds. -- `task` - A callable class/function implementation. Not required for RealtimeAPI/BatchAPI/TrafficSplitter kinds. +- `project_dir` - Path to a python project. +- `force` - Override any in-progress api updates. +- `wait` - Streams logs until the APIs are ready. + + +**Returns**: + + Deployment status, API specification, and endpoint for each API. + +## deploy\_realtime\_api + +```python + | deploy_realtime_api(api_spec: Dict[str, Any], handler, requirements: Optional[List] = None, conda_packages: Optional[List] = None, force: bool = True, wait: bool = False) -> Dict +``` + +Deploy a Realtime API. + +**Arguments**: + +- `api_spec` - A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/workloads/realtime-apis/configuration for schema. +- `handler` - A Cortex Handler class implementation. - `requirements` - A list of PyPI dependencies that will be installed before the handler class implementation is invoked. - `conda_packages` - A list of Conda dependencies that will be installed before the handler class implementation is invoked. -- `project_dir` - Path to a python project. - `force` - Override any in-progress api updates. - `wait` - Streams logs until the APIs are ready. +**Returns**: + + Deployment status, API specification, and endpoint for each API. + +## deploy\_async\_api + +```python + | deploy_async_api(api_spec: Dict[str, Any], handler, requirements: Optional[List] = None, conda_packages: Optional[List] = None, force: bool = True) -> Dict +``` + +Deploy an Async API. + +**Arguments**: + +- `api_spec` - A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/workloads/async-apis/configuration for schema. +- `handler` - A Cortex Handler class implementation. +- `requirements` - A list of PyPI dependencies that will be installed before the handler class implementation is invoked. +- `conda_packages` - A list of Conda dependencies that will be installed before the handler class implementation is invoked. +- `force` - Override any in-progress api updates. + + +**Returns**: + + Deployment status, API specification, and endpoint for each API. + +## deploy\_batch\_api + +```python + | deploy_batch_api(api_spec: Dict[str, Any], handler, requirements: Optional[List] = None, conda_packages: Optional[List] = None) -> Dict +``` + +Deploy a Batch API. + +**Arguments**: + +- `api_spec` - A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/workloads/batch-apis/configuration for schema. +- `handler` - A Cortex Handler class implementation. +- `requirements` - A list of PyPI dependencies that will be installed before the handler class implementation is invoked. +- `conda_packages` - A list of Conda dependencies that will be installed before the handler class implementation is invoked. + + +**Returns**: + + Deployment status, API specification, and endpoint for each API. + +## deploy\_task\_api + +```python + | deploy_task_api(api_spec: Dict[str, Any], task, requirements: Optional[List] = None, conda_packages: Optional[List] = None) -> Dict +``` + +Deploy a Task API. + +**Arguments**: + +- `api_spec` - A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/workloads/task-apis/configuration for schema. +- `task` - A callable class implementation. +- `requirements` - A list of PyPI dependencies that will be installed before the handler class implementation is invoked. +- `conda_packages` - A list of Conda dependencies that will be installed before the handler class implementation is invoked. + + +**Returns**: + + Deployment status, API specification, and endpoint for each API. + +## deploy\_traffic\_splitter + +```python + | deploy_traffic_splitter(api_spec: Dict[str, Any]) -> Dict +``` + +Deploy a Task API. + +**Arguments**: + +- `api_spec` - A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/workloads/realtime-apis/traffic-splitter/configuration for schema. + + **Returns**: Deployment status, API specification, and endpoint for each API. @@ -105,7 +204,7 @@ Deploy an API. ## get\_api ```python - | get_api(api_name: str) -> dict + | get_api(api_name: str) -> Dict ``` Get information about an API. @@ -122,7 +221,7 @@ Get information about an API. ## list\_apis ```python - | list_apis() -> list + | list_apis() -> List ``` List all APIs in the environment. @@ -134,7 +233,7 @@ List all APIs in the environment. ## get\_job ```python - | get_job(api_name: str, job_id: str) -> dict + | get_job(api_name: str, job_id: str) -> Dict ``` Get information about a submitted job. @@ -165,7 +264,7 @@ Restart all of the replicas for a Realtime API without downtime. ## patch ```python - | patch(api_spec: dict, force: bool = False) -> dict + | patch(api_spec: Dict, force: bool = False) -> Dict ``` Update the api specification for an API that has already been deployed. @@ -178,7 +277,7 @@ Update the api specification for an API that has already been deployed. ## delete\_api ```python - | delete_api(api_name: str, keep_cache: bool = False) + | delete(api_name: str, keep_cache: bool = False) ``` Delete an API. diff --git a/docs/workloads/batch/handler.md b/docs/workloads/batch/handler.md index 501cc1f17d..74e8050307 100644 --- a/docs/workloads/batch/handler.md +++ b/docs/workloads/batch/handler.md @@ -121,5 +121,5 @@ class Handler: # get client pointing to the default environment client = cortex.client() # deploy API in the existing cluster using the artifacts in the previous step - client.create_api(...) + client.deploy(...) ``` diff --git a/docs/workloads/dependencies/example.md b/docs/workloads/dependencies/example.md index efb6202f5f..2ff7a58197 100644 --- a/docs/workloads/dependencies/example.md +++ b/docs/workloads/dependencies/example.md @@ -41,7 +41,7 @@ api_spec = { } cx = cortex.client("aws") -cx.create_api(api_spec, project_dir=".") +cx.deploy(api_spec, project_dir=".") ``` ## Deploy using the CLI diff --git a/docs/workloads/realtime/multi-model/example.md b/docs/workloads/realtime/multi-model/example.md index 279abba8af..6eee9d9797 100644 --- a/docs/workloads/realtime/multi-model/example.md +++ b/docs/workloads/realtime/multi-model/example.md @@ -33,7 +33,7 @@ requirements = ["tensorflow", "transformers", "wget", "fasttext"] api_spec = {"name": "multi-model", "kind": "RealtimeAPI"} cx = cortex.client("aws") -cx.create_api(api_spec, handler=Handler, requirements=requirements) +cx.deploy_realtime_api(api_spec, handler=Handler, requirements=requirements) ``` ## Deploy diff --git a/docs/workloads/realtime/traffic-splitter/example.md b/docs/workloads/realtime/traffic-splitter/example.md index 3a26bfebcd..04034dc330 100644 --- a/docs/workloads/realtime/traffic-splitter/example.md +++ b/docs/workloads/realtime/traffic-splitter/example.md @@ -34,8 +34,8 @@ api_spec_gpu = { } cx = cortex.client("aws") -cx.create_api(api_spec_cpu, handler=Handler, requirements=requirements) -cx.create_api(api_spec_gpu, handler=Handler, requirements=requirements) +cx.deploy_realtime_api(api_spec_cpu, handler=Handler, requirements=requirements) +cx.deploy_realtime_api(api_spec_gpu, handler=Handler, requirements=requirements) ``` ## Deploy a traffic splitter @@ -50,7 +50,7 @@ traffic_splitter_spec = { ], } -cx.create_api(traffic_splitter_spec) +cx.deploy_traffic_splitter(traffic_splitter_spec) ``` ## Update the weights of the traffic splitter diff --git a/docs/workloads/task/definitions.md b/docs/workloads/task/definitions.md index 2f930c1cc9..edfffd93e4 100644 --- a/docs/workloads/task/definitions.md +++ b/docs/workloads/task/definitions.md @@ -87,5 +87,5 @@ class Task: # get client pointing to the default environment client = cortex.client() # deploy API in the existing cluster as part of your pipeline workflow - client.create_api(...) + client.deploy(...) ``` diff --git a/python/client/cortex/__init__.py b/python/client/cortex/__init__.py index 5a857999ab..dc400dedc6 100644 --- a/python/client/cortex/__init__.py +++ b/python/client/cortex/__init__.py @@ -13,7 +13,7 @@ # limitations under the License. import json -from typing import Optional +from typing import Optional, List from cortex.binary import run_cli from cortex.client import Client @@ -79,7 +79,7 @@ def new_client( @sentry_wrapper -def env_list() -> list: +def env_list() -> List: """ List all environments configured on this machine. """ diff --git a/python/client/cortex/client.py b/python/client/cortex/client.py index b73b6072ce..da55f33a51 100644 --- a/python/client/cortex/client.py +++ b/python/client/cortex/client.py @@ -22,19 +22,21 @@ import time import uuid from pathlib import Path -from typing import Optional +from typing import Optional, List, Dict, Any import dill import yaml + from cortex import util from cortex.binary import run_cli, get_cli_path from cortex.consts import EXPECTED_PYTHON_VERSION from cortex.telemetry import sentry_wrapper +from cortex.exceptions import InvalidKindForMethod class Client: @sentry_wrapper - def __init__(self, env: dict): + def __init__(self, env: Dict): """ A client to deploy and manage APIs in the specified environment. @@ -45,19 +47,212 @@ def __init__(self, env: dict): self.env = env self.env_name = env["name"] + # CORTEX_VERSION_MINOR + def deploy( + self, + api_spec: Dict[str, Any], + project_dir: str, + force: bool = True, + wait: bool = False, + ): + """ + Deploy an API from a project directory. + + Args: + api_spec: A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/ for schema. + project_dir: Path to a python project. + force: Override any in-progress api updates. + wait: Streams logs until the APIs are ready. + + Returns: + Deployment status, API specification, and endpoint for each API. + """ + return self._create_api( + api_spec=api_spec, + project_dir=project_dir, + force=force, + wait=wait, + ) + + # CORTEX_VERSION_MINOR + def deploy_realtime_api( + self, + api_spec: Dict[str, Any], + handler, + requirements: Optional[List] = None, + conda_packages: Optional[List] = None, + force: bool = True, + wait: bool = False, + ) -> Dict: + """ + Deploy a Realtime API. + + Args: + api_spec: A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/workloads/realtime-apis/configuration for schema. + handler: A Cortex Handler class implementation. + requirements: A list of PyPI dependencies that will be installed before the handler class implementation is invoked. + conda_packages: A list of Conda dependencies that will be installed before the handler class implementation is invoked. + force: Override any in-progress api updates. + wait: Streams logs until the APIs are ready. + + Returns: + Deployment status, API specification, and endpoint for each API. + """ + kind = api_spec.get("kind") + if kind != "RealtimeAPI": + raise InvalidKindForMethod( + f"expected an api_spec with kind 'RealtimeAPI', got kind '{kind}' instead" + ) + + return self._create_api( + api_spec=api_spec, + handler=handler, + requirements=requirements, + conda_packages=conda_packages, + force=force, + wait=wait, + ) + + # CORTEX_VERSION_MINOR + def deploy_async_api( + self, + api_spec: Dict[str, Any], + handler, + requirements: Optional[List] = None, + conda_packages: Optional[List] = None, + force: bool = True, + ) -> Dict: + """ + Deploy an Async API. + + Args: + api_spec: A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/workloads/async-apis/configuration for schema. + handler: A Cortex Handler class implementation. + requirements: A list of PyPI dependencies that will be installed before the handler class implementation is invoked. + conda_packages: A list of Conda dependencies that will be installed before the handler class implementation is invoked. + force: Override any in-progress api updates. + + Returns: + Deployment status, API specification, and endpoint for each API. + """ + kind = api_spec.get("kind") + if kind != "AsyncAPI": + raise InvalidKindForMethod( + f"expected an api_spec with kind 'AsyncAPI', got kind '{kind}' instead" + ) + + return self._create_api( + api_spec=api_spec, + handler=handler, + requirements=requirements, + conda_packages=conda_packages, + force=force, + ) + + # CORTEX_VERSION_MINOR + def deploy_batch_api( + self, + api_spec: Dict[str, Any], + handler, + requirements: Optional[List] = None, + conda_packages: Optional[List] = None, + ) -> Dict: + """ + Deploy a Batch API. + + Args: + api_spec: A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/workloads/batch-apis/configuration for schema. + handler: A Cortex Handler class implementation. + requirements: A list of PyPI dependencies that will be installed before the handler class implementation is invoked. + conda_packages: A list of Conda dependencies that will be installed before the handler class implementation is invoked. + + Returns: + Deployment status, API specification, and endpoint for each API. + """ + + kind = api_spec.get("kind") + if kind != "BatchAPI": + raise InvalidKindForMethod( + f"expected an api_spec with kind 'BatchAPI', got kind '{kind}' instead" + ) + + return self._create_api( + api_spec=api_spec, + handler=handler, + requirements=requirements, + conda_packages=conda_packages, + ) + + # CORTEX_VERSION_MINOR + def deploy_task_api( + self, + api_spec: Dict[str, Any], + task, + requirements: Optional[List] = None, + conda_packages: Optional[List] = None, + ) -> Dict: + """ + Deploy a Task API. + + Args: + api_spec: A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/workloads/task-apis/configuration for schema. + task: A callable class implementation. + requirements: A list of PyPI dependencies that will be installed before the handler class implementation is invoked. + conda_packages: A list of Conda dependencies that will be installed before the handler class implementation is invoked. + + Returns: + Deployment status, API specification, and endpoint for each API. + """ + kind = api_spec.get("kind") + if kind != "TaskAPI": + raise InvalidKindForMethod( + f"expected an api_spec with kind 'TaskAPI', got kind '{kind}' instead" + ) + + return self._create_api( + api_spec=api_spec, + task=task, + requirements=requirements, + conda_packages=conda_packages, + ) + + # CORTEX_VERSION_MINOR + def deploy_traffic_splitter( + self, + api_spec: Dict[str, Any], + ) -> Dict: + """ + Deploy a Task API. + + Args: + api_spec: A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/workloads/realtime-apis/traffic-splitter/configuration for schema. + + Returns: + Deployment status, API specification, and endpoint for each API. + """ + kind = api_spec.get("kind") + if kind != "TrafficSplitter": + raise InvalidKindForMethod( + f"expected an api_spec with kind 'TrafficSplitter', got kind '{kind}' instead" + ) + + return self._create_api( + api_spec=api_spec, + ) + # CORTEX_VERSION_MINOR @sentry_wrapper - def create_api( + def _create_api( self, - api_spec: dict, + api_spec: Dict, handler=None, task=None, - requirements=[], - conda_packages=[], + requirements: Optional[List] = None, + conda_packages: Optional[List] = None, project_dir: Optional[str] = None, force: bool = True, wait: bool = False, - ) -> list: + ) -> Dict: """ Deploy an API. @@ -144,11 +339,11 @@ def create_api( if not is_python_set: conda_packages = [f"python={actual_version}", "pip=19.*"] + conda_packages - if len(requirements) > 0: + if requirements is not None: with open(project_dir / "requirements.txt", "w") as requirements_file: requirements_file.write("\n".join(requirements)) - if len(conda_packages) > 0: + if conda_packages is not None: with open(project_dir / "conda-packages.txt", "w") as conda_file: conda_file.write("\n".join(conda_packages)) @@ -204,7 +399,7 @@ def _deploy( config_file: str, force: bool = False, wait: bool = False, - ) -> list: + ) -> Dict: """ Deploy or update APIs specified in the config_file. @@ -278,7 +473,7 @@ def stream_to_stdout(process): return api @sentry_wrapper - def get_api(self, api_name: str) -> dict: + def get_api(self, api_name: str) -> Dict: """ Get information about an API. @@ -294,7 +489,7 @@ def get_api(self, api_name: str) -> dict: return apis[0] @sentry_wrapper - def list_apis(self) -> list: + def list_apis(self) -> List: """ List all APIs in the environment. @@ -308,7 +503,7 @@ def list_apis(self) -> list: return json.loads(output.strip()) @sentry_wrapper - def get_job(self, api_name: str, job_id: str) -> dict: + def get_job(self, api_name: str, job_id: str) -> Dict: """ Get information about a submitted job. @@ -342,7 +537,7 @@ def refresh(self, api_name: str, force: bool = False): run_cli(args, hide_output=True) @sentry_wrapper - def patch(self, api_spec: dict, force: bool = False) -> dict: + def patch(self, api_spec: Dict, force: bool = False) -> Dict: """ Update the api specification for an API that has already been deployed. @@ -365,7 +560,7 @@ def patch(self, api_spec: dict, force: bool = False) -> dict: return json.loads(output.strip()) @sentry_wrapper - def delete_api(self, api_name: str, keep_cache: bool = False): + def delete(self, api_name: str, keep_cache: bool = False): """ Delete an API. diff --git a/python/client/cortex/exceptions.py b/python/client/cortex/exceptions.py index 5505156a65..e3ab698a2d 100644 --- a/python/client/cortex/exceptions.py +++ b/python/client/cortex/exceptions.py @@ -35,3 +35,10 @@ class NotFound(CortexException): """ pass + + +class InvalidKindForMethod(CortexException): + """ + Raise when the specified resource kind is not supported by the used python client method. + """ + pass diff --git a/python/client/cortex/telemetry.py b/python/client/cortex/telemetry.py index d60fe2ffd1..d8674c8ed4 100644 --- a/python/client/cortex/telemetry.py +++ b/python/client/cortex/telemetry.py @@ -13,6 +13,7 @@ # limitations under the License. import os +from typing import Dict from uuid import uuid4 import sentry_sdk @@ -70,7 +71,7 @@ def _sentry_client( return client -def _create_default_scope(optional_tags: dict = {}) -> sentry_sdk.Scope: +def _create_default_scope(optional_tags: Dict = {}) -> sentry_sdk.Scope: """ Creates default scope. Adds user ID as tag to the reported event. Can add optional tags. diff --git a/test/apis/pytorch/iris-classifier/deploy.py b/test/apis/pytorch/iris-classifier/deploy.py index f5e468b948..9124b5d206 100644 --- a/test/apis/pytorch/iris-classifier/deploy.py +++ b/test/apis/pytorch/iris-classifier/deploy.py @@ -17,6 +17,6 @@ }, } -print(cx.create_api(api_spec, project_dir=dir_path)) +print(cx.deploy(api_spec, project_dir=dir_path)) -# cx.delete_api("iris-classifier") +# cx.delete("iris-classifier") diff --git a/test/apis/pytorch/text-generator/deploy_class.py b/test/apis/pytorch/text-generator/deploy_class.py index 5b246b93c2..312d55d21e 100644 --- a/test/apis/pytorch/text-generator/deploy_class.py +++ b/test/apis/pytorch/text-generator/deploy_class.py @@ -22,7 +22,7 @@ def handle_post(self, payload): return self.model(payload["text"])[0] -api = cx.create_api( +api = cx.deploy_realtime_api( api_spec, handler=Handler, requirements=["torch", "transformers"], @@ -37,4 +37,4 @@ def handle_post(self, payload): print(response.status_code) print(response.text) -cx.delete_api(api_spec["name"]) +cx.delete(api_spec["name"]) diff --git a/test/apis/sleep/deploy.py b/test/apis/sleep/deploy.py index f8d277206f..67d226f9eb 100644 --- a/test/apis/sleep/deploy.py +++ b/test/apis/sleep/deploy.py @@ -14,6 +14,6 @@ }, } -print(cx.create_api(api_spec, project_dir=dir_path)) +print(cx.deploy(api_spec, project_dir=dir_path)) -# cx.delete_api("sleep") +# cx.delete("sleep") diff --git a/test/e2e/e2e/tests.py b/test/e2e/e2e/tests.py index 89c1dead3a..ac4723f7a4 100644 --- a/test/e2e/e2e/tests.py +++ b/test/e2e/e2e/tests.py @@ -58,7 +58,7 @@ def delete_apis(client: cx.Client, api_names: List[str]): for name in api_names: - client.delete_api(name) + client.delete(name) def test_realtime_api( @@ -79,7 +79,7 @@ def test_realtime_api( api_name = api_specs[0]["name"] for api_spec in api_specs: - client.create_api(api_spec=api_spec, project_dir=str(api_dir)) + client.deploy(api_spec=api_spec, project_dir=str(api_dir)) try: assert apis_ready( @@ -147,7 +147,7 @@ def test_batch_api( assert len(api_specs) == 1 api_name = api_specs[0]["name"] - client.create_api(api_spec=api_specs[0], project_dir=str(api_dir)) + client.deploy(api_spec=api_specs[0], project_dir=str(api_dir)) try: endpoint_override = f"http://localhost:8888/batch/{api_name}" if local_operator else None @@ -230,7 +230,7 @@ def test_async_api( assert len(api_specs) == 1 api_name = api_specs[0]["name"] - client.create_api(api_spec=api_specs[0], project_dir=str(api_dir)) + client.deploy(api_spec=api_specs[0], project_dir=str(api_dir)) try: assert apis_ready( @@ -338,7 +338,7 @@ def test_task_api( assert len(api_specs) == 1 api_name = api_specs[0]["name"] - client.create_api(api_spec=api_specs[0], project_dir=str(api_dir)) + client.deploy(api_spec=api_specs[0], project_dir=str(api_dir)) try: assert endpoint_ready( @@ -411,7 +411,7 @@ def test_autoscaling( "downscale_stabilization_period": "1m", } all_api_names.append(api_specs[0]["name"]) - client.create_api(api_spec=api_specs[0], project_dir=api_dir) + client.deploy(api_spec=api_specs[0], project_dir=api_dir) primary_api_name = all_api_names[0] autoscaling = client.get_api(primary_api_name)["spec"]["autoscaling"] @@ -523,7 +523,7 @@ def test_load_realtime( "max_replicas": desired_replicas, } api_name = api_specs[0]["name"] - client.create_api(api_spec=api_specs[0], project_dir=str(api_dir)) + client.deploy(api_spec=api_specs[0], project_dir=str(api_dir)) # controls the flow of requests request_stopper = td.Event() @@ -632,7 +632,7 @@ def test_load_async( "max_replicas": desired_replicas, } api_name = api_specs[0]["name"] - client.create_api(api_spec=api_specs[0], project_dir=str(api_dir)) + client.deploy(api_spec=api_specs[0], project_dir=str(api_dir)) request_stopper = td.Event() map_stopper = td.Event() @@ -756,7 +756,7 @@ def test_load_batch( sample_generator = load_generator(sample_generator_path) api_name = api_specs[0]["name"] - client.create_api(api_spec=api_specs[0], project_dir=str(api_dir)) + client.deploy(api_spec=api_specs[0], project_dir=str(api_dir)) api_endpoint = client.get_api(api_name)["endpoint"] try: @@ -849,7 +849,7 @@ def test_load_task( assert len(api_specs) == 1 api_name = api_specs[0]["name"] - client.create_api(api_spec=api_specs[0], project_dir=str(api_dir)) + client.deploy(api_spec=api_specs[0], project_dir=str(api_dir)) request_stopper = td.Event() map_stopper = td.Event() @@ -926,7 +926,7 @@ def test_long_running_realtime( api_name = api_specs[0]["name"] for api_spec in api_specs: - client.create_api(api_spec=api_spec, project_dir=str(api_dir)) + client.deploy(api_spec=api_spec, project_dir=str(api_dir)) try: assert apis_ready( From 2acfc7f350b30bf7ecf141549edb998efe0e7c63 Mon Sep 17 00:00:00 2001 From: Miguel Varela Ramos Date: Thu, 6 May 2021 17:24:20 +0200 Subject: [PATCH 4/8] Fix linting errors --- python/client/cortex/client.py | 10 +++++----- python/client/cortex/exceptions.py | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/python/client/cortex/client.py b/python/client/cortex/client.py index da55f33a51..0926f55442 100644 --- a/python/client/cortex/client.py +++ b/python/client/cortex/client.py @@ -49,11 +49,11 @@ def __init__(self, env: Dict): # CORTEX_VERSION_MINOR def deploy( - self, - api_spec: Dict[str, Any], - project_dir: str, - force: bool = True, - wait: bool = False, + self, + api_spec: Dict[str, Any], + project_dir: str, + force: bool = True, + wait: bool = False, ): """ Deploy an API from a project directory. diff --git a/python/client/cortex/exceptions.py b/python/client/cortex/exceptions.py index e3ab698a2d..16acded963 100644 --- a/python/client/cortex/exceptions.py +++ b/python/client/cortex/exceptions.py @@ -41,4 +41,5 @@ class InvalidKindForMethod(CortexException): """ Raise when the specified resource kind is not supported by the used python client method. """ + pass From f6c330d92c3759c4dd99324178db16f1be56d7de Mon Sep 17 00:00:00 2001 From: Miguel Varela Ramos Date: Thu, 6 May 2021 18:13:23 +0200 Subject: [PATCH 5/8] Update python.md --- docs/clients/python.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/clients/python.md b/docs/clients/python.md index 873e9945c3..105b05c71c 100644 --- a/docs/clients/python.md +++ b/docs/clients/python.md @@ -17,7 +17,7 @@ * [get\_job](#get_job) * [refresh](#refresh) * [patch](#patch) - * [delete\_api](#delete_api) + * [delete](#delete) * [stop\_job](#stop_job) * [stream\_api\_logs](#stream_api_logs) * [stream\_job\_logs](#stream_job_logs) @@ -274,7 +274,7 @@ Update the api specification for an API that has already been deployed. - `api_spec` - The new api specification to apply - `force` - Override an already in-progress API update. -## delete\_api +## delete ```python | delete(api_name: str, keep_cache: bool = False) From da2db5bbd5e2823e49b59b1faf99bf43eea43ead Mon Sep 17 00:00:00 2001 From: Miguel Varela Ramos Date: Fri, 7 May 2021 12:05:53 +0200 Subject: [PATCH 6/8] Address PR comments --- python/client/cortex/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/client/cortex/client.py b/python/client/cortex/client.py index 0926f55442..6f8b4ac572 100644 --- a/python/client/cortex/client.py +++ b/python/client/cortex/client.py @@ -56,7 +56,7 @@ def deploy( wait: bool = False, ): """ - Deploy an API from a project directory. + Deploy API(s) from a project directory. Args: api_spec: A dictionary defining a single Cortex API. See https://docs.cortex.dev/v/master/ for schema. @@ -339,11 +339,11 @@ def _create_api( if not is_python_set: conda_packages = [f"python={actual_version}", "pip=19.*"] + conda_packages - if requirements is not None: + if requirements is not None and len(requirements) > 0: with open(project_dir / "requirements.txt", "w") as requirements_file: requirements_file.write("\n".join(requirements)) - if conda_packages is not None: + if conda_packages is not None and len(conda_packages) > 0: with open(project_dir / "conda-packages.txt", "w") as conda_file: conda_file.write("\n".join(conda_packages)) From 319f340cf956e8d6cc2eae8d045b92a9e812f9d2 Mon Sep 17 00:00:00 2001 From: Miguel Varela Ramos Date: Fri, 7 May 2021 12:27:21 +0200 Subject: [PATCH 7/8] Add comments to facilitate finding cortex versions --- dev/generate_python_client_md.sh | 7 ++++++- docs/clients/python.md | 14 +++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/dev/generate_python_client_md.sh b/dev/generate_python_client_md.sh index b10446f501..0b0215e76f 100755 --- a/dev/generate_python_client_md.sh +++ b/dev/generate_python_client_md.sh @@ -64,7 +64,12 @@ sed -i 's/[[:space:]]*$//' $docs_path truncate -s -1 $docs_path # Cortex version comment -sed -i "s/^## create\\\_api/## create\\\_api\n\n/g" $docs_path +sed -i "s/^## deploy$/## deploy\n\n/g" $docs_path +sed -i "s/^## deploy\\\_realtime\\\_api$/## deploy\\\_realtime\\\_api\n\n/g" $docs_path +sed -i "s/^## deploy\\\_async\\\_api$/## deploy\\\_async\\\_api\n\n/g" $docs_path +sed -i "s/^## deploy\\\_batch\\\_api$/## deploy\\\_batch\\\_api\n\n/g" $docs_path +sed -i "s/^## deploy\\\_task\\\_api$/## deploy\\\_task\\\_api\n\n/g" $docs_path +sed -i "s/^## deploy\\\_traffic\\\_splitter$/## deploy\\\_traffic\\\_splitter\n\n/g" $docs_path pip3 uninstall -y cortex rm -rf $ROOT/python/client/cortex.egg-info diff --git a/docs/clients/python.md b/docs/clients/python.md index 105b05c71c..a12f1d4ba8 100644 --- a/docs/clients/python.md +++ b/docs/clients/python.md @@ -83,11 +83,13 @@ Delete an environment configured on this machine. ## deploy + + ```python | deploy(api_spec: Dict[str, Any], project_dir: str, force: bool = True, wait: bool = False) ``` -Deploy an API from a project directory. +Deploy API(s) from a project directory. **Arguments**: @@ -103,6 +105,8 @@ Deploy an API from a project directory. ## deploy\_realtime\_api + + ```python | deploy_realtime_api(api_spec: Dict[str, Any], handler, requirements: Optional[List] = None, conda_packages: Optional[List] = None, force: bool = True, wait: bool = False) -> Dict ``` @@ -125,6 +129,8 @@ Deploy a Realtime API. ## deploy\_async\_api + + ```python | deploy_async_api(api_spec: Dict[str, Any], handler, requirements: Optional[List] = None, conda_packages: Optional[List] = None, force: bool = True) -> Dict ``` @@ -146,6 +152,8 @@ Deploy an Async API. ## deploy\_batch\_api + + ```python | deploy_batch_api(api_spec: Dict[str, Any], handler, requirements: Optional[List] = None, conda_packages: Optional[List] = None) -> Dict ``` @@ -166,6 +174,8 @@ Deploy a Batch API. ## deploy\_task\_api + + ```python | deploy_task_api(api_spec: Dict[str, Any], task, requirements: Optional[List] = None, conda_packages: Optional[List] = None) -> Dict ``` @@ -186,6 +196,8 @@ Deploy a Task API. ## deploy\_traffic\_splitter + + ```python | deploy_traffic_splitter(api_spec: Dict[str, Any]) -> Dict ``` From 509502babe427405d88ef3a5396cc9e334e20820 Mon Sep 17 00:00:00 2001 From: Miguel Varela Ramos Date: Mon, 10 May 2021 11:07:58 +0100 Subject: [PATCH 8/8] Fix python client _create_api method --- python/client/cortex/client.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/python/client/cortex/client.py b/python/client/cortex/client.py index 6f8b4ac572..22e5bcf333 100644 --- a/python/client/cortex/client.py +++ b/python/client/cortex/client.py @@ -270,6 +270,9 @@ def _create_api( Deployment status, API specification, and endpoint for each API. """ + requirements = requirements if requirements is not None else [] + conda_packages = conda_packages if conda_packages is not None else [] + if project_dir is not None: if handler is not None: raise ValueError( @@ -305,7 +308,8 @@ def _create_api( raise ValueError(f"`task` parameter cannot be specified for {api_kind}") else: raise ValueError( - f"invalid {api_kind} kind, `api_spec` must have the `kind` field set to one of the following kinds: {['TrafficSplitter', 'TaskAPI', 'BatchAPI', 'RealtimeAPI']}" + f"invalid {api_kind} kind, `api_spec` must have the `kind` field set to one of the following kinds: " + f"{['TrafficSplitter', 'TaskAPI', 'BatchAPI', 'RealtimeAPI']}" ) if api_spec.get("name") is None: @@ -339,11 +343,11 @@ def _create_api( if not is_python_set: conda_packages = [f"python={actual_version}", "pip=19.*"] + conda_packages - if requirements is not None and len(requirements) > 0: + if len(requirements) > 0: with open(project_dir / "requirements.txt", "w") as requirements_file: requirements_file.write("\n".join(requirements)) - if conda_packages is not None and len(conda_packages) > 0: + if len(conda_packages) > 0: with open(project_dir / "conda-packages.txt", "w") as conda_file: conda_file.write("\n".join(conda_packages)) @@ -351,9 +355,6 @@ def _create_api( if not inspect.isclass(handler): raise ValueError("`handler` parameter must be a class definition") - impl_rel_path = self._save_impl(handler, project_dir, "handler") - api_spec["handler"]["path"] = impl_rel_path - if api_spec.get("handler") is None: raise ValueError("`api_spec` must have the `handler` section defined") @@ -362,10 +363,14 @@ def _create_api( "the `type` field in the `handler` section of the `api_spec` must be set (tensorflow or python)" ) + impl_rel_path = self._save_impl(handler, project_dir, "handler") + api_spec["handler"]["path"] = impl_rel_path + if api_kind == "TaskAPI": if not callable(task): raise ValueError( - "`task` parameter must be a callable (e.g. a function definition or a class definition called `Task` with a `__call__` method implemented" + "`task` parameter must be a callable (e.g. a function definition or a class definition called " + "`Task` with a `__call__` method implemented " ) impl_rel_path = self._save_impl(task, project_dir, "task")