From 88fbfb8959924d8f29f0692eb49aa1f6c0556a93 Mon Sep 17 00:00:00 2001 From: Ajeya Bhat Date: Mon, 11 Mar 2024 14:06:10 +0530 Subject: [PATCH] update deps --- outpostcli/endpoints.py | 203 ++++++++++++++++++++++------------------ outpostcli/utils.py | 38 +++++--- pyproject.toml | 2 +- requirements.txt | 2 +- 4 files changed, 141 insertions(+), 104 deletions(-) diff --git a/outpostcli/endpoints.py b/outpostcli/endpoints.py index 164d3e4..a082bbd 100644 --- a/outpostcli/endpoints.py +++ b/outpostcli/endpoints.py @@ -1,10 +1,20 @@ import json import os -from typing import Optional +from typing import List, Optional from urllib.parse import urlparse import click from outpostkit import Client, Endpoint, Endpoints +from outpostkit._types.endpoint import ( + EndpointAutogeneratedHFModelDetails, + EndpointAutogeneratedOutpostModelDetails, + EndpointAutogeneratedTemplateConfig, + EndpointCustomTemplateConfig, + EndpointPrebuiltContainerDetails, + EndpointSecret, + ReplicaScalingConfig, +) +from outpostkit.exceptions import OutpostError from outpostkit.utils import convert_outpost_date_str_to_date from rich.table import Table @@ -13,7 +23,7 @@ add_options, api_token_opt, click_group, - combine_inf_load_source_model, + condense_endpt_autogen_configs, console, entity_opt, ) @@ -45,8 +55,10 @@ def list_endpoints(api_token, entity): for inf in infs_resp.endpoints: inf_table.add_row( inf.name, - combine_inf_load_source_model( - inf.loadModelWeightsFrom, inf.outpostModel, inf.huggingfaceModel + ( + condense_endpt_autogen_configs(inf.autogeneratedTemplateConfig) + if inf.autogeneratedTemplateConfig is not None + else "custom" ), inf.status, inf.hardwareInstance.name, @@ -72,14 +84,7 @@ def list_endpoints(api_token, entity): type=str, default=None, required=False, - help="revision of the model to use.", -) -@click.option( - "--huggingface-token-id", - type=str, - default=None, - required=False, - help="revision of the model to use.", + help="huggingface token id to connect private models.", ) @click.option( "--hardware-instance", @@ -104,14 +109,11 @@ def list_endpoints(api_token, entity): required=False, ) @click.option( - "--base-image", + "--prebuilt-container", type=click.Choice( [ - "transformers-pt", - "transformers-tf", + "transformers", "python", - "diffusers-pt", - "diffusers-tf", "tensorflow", "diffusers", ] @@ -120,6 +122,12 @@ def list_endpoints(api_token, entity): help="base image", required=False, ) +@click.option( + "--prebuilt-container-config", + type=str, + help="prebuilt container config (JSON)", + required=False, +) @click.option( "--visibility", type=click.Choice(["private", "public", "internal"]), @@ -135,6 +143,16 @@ def list_endpoints(api_token, entity): help="minimum number of replicas", required=False, ) +@click.option( + "--secret", + "--env", + "-e", + type=str, + multiple=True, + default=None, + help="minimum number of replicas", + required=False, +) @click.option( "--replica-scaling-max", type=int, @@ -163,7 +181,6 @@ def create_endpoint( model_data: Optional[str], hardware_instance: str, huggingface_token_id, - base_image: Optional[str], name: Optional[str], template_path: Optional[str], task_type: str, @@ -172,6 +189,9 @@ def create_endpoint( visibility: str, replica_scaling_scaledown_period: int, replica_scaling_target_pending_req: int, + prebuilt_container: Optional[str], + prebuilt_container_config: Optional[str], + secret: Optional[List[str]], ): client = Client(api_token=api_token) if template_path: @@ -181,58 +201,56 @@ def create_endpoint( "Please specify the template classname along with the path.", err=True ) return - if not base_image: + if not prebuilt_container: click.echo("Please specify the base image you want to use.", err=True) return try: result = urlparse(actual_path) if all([result.scheme, result.netloc]): - data = { - "templateType": "custom", - "customTemplateConfig": { - "className": class_name, - "url": actual_path, - }, - "hardwareInstance": hardware_instance, - "taskType": task_type, - "name": name, - "containerType": "prebuilt", - "visibility": visibility, - "prebuiltImageName": base_image, - "replicaScalingConfig": { - "min": replica_scaling_min, - "max": replica_scaling_max, - "scaledownPeriod": replica_scaling_scaledown_period, - "targetPendingRequests": replica_scaling_target_pending_req, - }, - } - create_resp = Endpoints(client=client, entity=entity).create(json=data) + custom_template_config = EndpointCustomTemplateConfig(type="url",path=actual_path, className=class_name) + config = None + try: + config = json.loads(prebuilt_container_config) + except json.JSONDecodeError as e: + raise OutpostError(f"Invalid JSON: prebuilt_container_config: {str(e)}") from e + prebuilt_container_details = EndpointPrebuiltContainerDetails(name=prebuilt_container, config=config) + replica_scaling_config= ReplicaScalingConfig( + min= replica_scaling_min, + max= replica_scaling_max, + scaledownPeriod= replica_scaling_scaledown_period, + targetPendingRequests= replica_scaling_target_pending_req, + ) + secrets = None + if(secret is not None): + secrets = [] + for s in secret: + name,value = s.split('=',maxsplit=1) + if(name =='' or value ==''): + raise OutpostError(f"Invalid secret config: {"name" if name =='' else "value"} empty") + secrets.append(EndpointSecret(name=name,value=value)) + create_resp = Endpoints(client=client, entity=entity).create(template=custom_template_config, container=prebuilt_container_details, hardware_instance=hardware_instance, task_type=task_type, name=name,visibility=visibility, replica_scaling_config=replica_scaling_config, secrets=secrets) else: raise ValueError("Not an url.") except ValueError: if os.path.exists(actual_path) and os.path.isfile(actual_path): - data = { - "templateType": "custom", - "customTemplateConfig": { - "className": class_name, - }, - "taskType": task_type, - "name": name, - "containerType": "prebuilt", - "visibility": visibility, - "hardwareInstance": hardware_instance, - "prebuiltImageName": base_image, - "replicaScalingConfig": { - "min": replica_scaling_min, - "max": replica_scaling_max, - "scaledownPeriod": replica_scaling_scaledown_period, - "targetPendingRequests": replica_scaling_target_pending_req, - }, - } - create_resp = Endpoints(client=client, entity=entity).create( - data={"metadata": json.dumps(data)}, - files={"template": open(actual_path, mode="rb")}, + custom_template_config = EndpointCustomTemplateConfig(type="file",path=actual_path, className=class_name) + prebuilt_container_details = EndpointPrebuiltContainerDetails(name=prebuilt_container, config=config) + replica_scaling_config= ReplicaScalingConfig( + min= replica_scaling_min, + max= replica_scaling_max, + scaledownPeriod= replica_scaling_scaledown_period, + targetPendingRequests= replica_scaling_target_pending_req, ) + secrets = None + if(secret is not None): + secrets = [] + for s in secret: + name,value = s.split('=',maxsplit=1) + if(name =='' or value ==''): + raise OutpostError(f"Invalid secret config: {"name" if name =='' else "value"} empty") from None + secrets.append(EndpointSecret(name=name,value=value)) + + create_resp = Endpoints(client=client, entity=entity).create(template=custom_template_config, container=prebuilt_container_details, hardware_instance=hardware_instance, task_type=task_type, name=name,visibility=visibility, replica_scaling_config=replica_scaling_config, secrets=secrets) else: click.echo("Invalid template file path.", err=True) return @@ -245,41 +263,39 @@ def create_endpoint( click.echo("Please provided the model name.", err=True) return m_splits = model_data.split(":", 1) - model_details: dict[str] = None + template_config = None if len(m_splits) == 1: [model_name, revision] = model_data.split("@", 1) - model_details = { - "modelSource": "outpost", - "outpostModel": {"fullName": model_name, "commit": revision}, - } + template_config = EndpointAutogeneratedTemplateConfig(modelSource= "outpost", + outpostModel= EndpointAutogeneratedOutpostModelDetails(fullName= model_name), + revision=revision + ) else: if m_splits[0] == "hf" or m_splits[0] == "huggingface": [model_name, revision] = model_data.split("@", 1) - model_details = { - "modelSource": "huggingface", - "huggingfaceModel": { - "id": model_name, - "revision": revision, - "keyId": huggingface_token_id, - }, - } + template_config = EndpointAutogeneratedTemplateConfig(modelSource= 'huggingface', + outpostModel= EndpointAutogeneratedHFModelDetails(id= model_name,keyId=huggingface_token_id), + revision=revision + ) else: raise SourceNotSupportedError(f"source {m_splits[0]} not supported.") + prebuilt_container_details = EndpointPrebuiltContainerDetails(name=prebuilt_container, config=config) if prebuilt_container else None + replica_scaling_config= ReplicaScalingConfig( + min= replica_scaling_min, + max= replica_scaling_max, + scaledownPeriod= replica_scaling_scaledown_period, + targetPendingRequests= replica_scaling_target_pending_req, + ) + secrets = None + if(secret is not None): + secrets = [] + for s in secret: + name,value = s.split('=',maxsplit=1) + if(name =='' or value ==''): + raise OutpostError(f"Invalid secret config: {"name" if name =='' else "value"} empty") from None + secrets.append(EndpointSecret(name=name,value=value)) - create_body = { - "templateType": "autogenerated", - "autogeneratedTemplateConfig": model_details, - "hardwareInstance": hardware_instance, - "visibility": visibility, - "name": name, - "replicaScalingConfig": { - "min": replica_scaling_min, - "max": replica_scaling_max, - "scaledownPeriod": replica_scaling_scaledown_period, - "targetPendingRequests": replica_scaling_target_pending_req, - }, - } - create_resp = Endpoints(client=client, entity=entity).create(json=create_body) + create_resp = Endpoints(client=client, entity=entity).create(template=template_config, container=prebuilt_container_details, hardware_instance=hardware_instance, task_type=task_type, name=name,visibility=visibility, replica_scaling_config=replica_scaling_config, secrets=secrets) click.echo("endpoint created...") click.echo(f"name: {create_resp.name}") click.echo(f"id: {create_resp.id}") @@ -350,11 +366,20 @@ def delete_endpoint(api_token, entity, name): return "Aborted" -@endpoints.command(name="status") +@endpoints.command(name="replica-status") @click.argument("name", type=str, nargs=1) @add_options([api_token_opt, entity_opt]) -@click.option("--verbose", "-v", is_flag=True, help="Verbose") def inf_dep_status(api_token, entity, name): + client = Client(api_token=api_token) + status_data = Endpoint( + client=client, api_token=api_token, name=name, entity=entity + ).replica_status() + click.echo(status_data) + +@endpoints.command(name="status") +@click.argument("name", type=str, nargs=1) +@add_options([api_token_opt, entity_opt]) +def inf_status(api_token, entity, name): client = Client(api_token=api_token) status_data = Endpoint( client=client, api_token=api_token, name=name, entity=entity diff --git a/outpostcli/utils.py b/outpostcli/utils.py index 9296508..627909d 100644 --- a/outpostcli/utils.py +++ b/outpostcli/utils.py @@ -1,10 +1,7 @@ from typing import Optional import click -from outpostkit._types.endpoint import ( - EndpointAutogeneratedHFModelDetails, - EndpointAutogeneratedOutpostModelDetails, -) +from outpostkit._types.endpoint import EndpointAutogeneratedTemplateConfigDetails from rich.console import Console from outpostcli.config_utils import ( @@ -58,17 +55,32 @@ def check_token(token: str): return -1, None -def combine_inf_load_source_model( - load_source, - outpost_model: Optional[EndpointAutogeneratedOutpostModelDetails], - hf_model: Optional[EndpointAutogeneratedHFModelDetails], +def condense_endpt_autogen_configs( + autogen_configs: EndpointAutogeneratedTemplateConfigDetails, ): - if load_source == "hugginface" and hf_model: - return f"hf:{hf_model.id}" - elif load_source == "outpost" and outpost_model: - return f"{outpost_model.model.fullName}" + load_source = autogen_configs.modelSource + revision = ( + f"@{autogen_configs.revision}" if autogen_configs.revision is not None else "" + ) + if load_source == "hugginface": + gen_str: str = "hf:" + hf_model = autogen_configs.huggingfaceModel + if hf_model: + gen_str = gen_str + hf_model.id + else: + gen_str = gen_str + "" + return f"{gen_str}{revision}" + + if load_source == "outpost": + gen_str: str = "" + op_model = autogen_configs.outpostModel + if hf_model: + gen_str = op_model + else: + gen_str = "" + return f"{gen_str}:{revision}" else: - return "custom" + return "cannot-parse-model" def add_options(options): diff --git a/pyproject.toml b/pyproject.toml index 0e5e1ca..f52e1cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ license = { file = "LICENSE" } authors = [{ name = "Outpost Innovations, Inc." }] requires-python = ">=3.8" dependencies = [ - "outpostkit>=0.0.34", + "outpostkit>=0.0.44", "click", "rich", ] diff --git a/requirements.txt b/requirements.txt index 345db77..958fc3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,7 +28,7 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py -outpostkit==0.0.34 +outpostkit==0.0.44 # via outpostcli (pyproject.toml) packaging==23.2 # via outpostkit