From 833342f1ec2f385cfbd9a803e3ea059fec87ceff Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Tue, 18 Jul 2023 11:11:44 -0400 Subject: [PATCH 1/9] feat(cli): add --cloud-context for --push Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- src/bentoml_cli/bentos.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/bentoml_cli/bentos.py b/src/bentoml_cli/bentos.py index 8a70aefa12d..f3afdc42c67 100644 --- a/src/bentoml_cli/bentos.py +++ b/src/bentoml_cli/bentos.py @@ -326,6 +326,17 @@ def push(bento_tag: str, force: bool, threads: int, context: str) -> None: # ty type=click.BOOL, help="Whether to push the result bento to BentoCloud. Make sure to login with 'bentoml cloud login' first.", ) + @click.option( + "--context", + "context", + type=click.STRING, + default=None, + help="Yatai context name.", + ) + @click.option( + "--force", is_flag=True, default=False, help="Forced push to BentoCloud" + ) + @click.option("--threads", default=10, help="Number of threads to use for upload") @click.pass_context @inject def build( # type: ignore (not accessed) @@ -335,6 +346,9 @@ def build( # type: ignore (not accessed) version: str, output: t.Literal["tag", "default"], push: bool, + force: bool, + threads: int, + context: str | None, containerize: bool, _bento_store: BentoStore = Provide[BentoMLContainer.bento_store], _cloud_client: BentoCloudClient = Provide[BentoMLContainer.bentocloud_client], @@ -390,7 +404,9 @@ def build( # type: ignore (not accessed) if push: if not get_quiet_mode(): click.secho(f"\nPushing {bento} to BentoCloud...", fg="magenta") - _cloud_client.push_bento(bento) + _cloud_client.push_bento( + bento, force=force, threads=threads, context=context + ) elif containerize: backend: DefaultBuilder = t.cast( "DefaultBuilder", os.getenv("BENTOML_CONTAINERIZE_BACKEND", "docker") From 718aad8edbf25ed7347fc972b85d219780a09298 Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Thu, 20 Jul 2023 08:56:52 -0400 Subject: [PATCH 2/9] fix: setting context via obj Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- src/bentoml_cli/bentos.py | 27 ++++------ src/bentoml_cli/models.py | 25 +++++---- src/bentoml_cli/utils.py | 108 +++++++++----------------------------- 3 files changed, 47 insertions(+), 113 deletions(-) diff --git a/src/bentoml_cli/bentos.py b/src/bentoml_cli/bentos.py index f3afdc42c67..f15e6ab1192 100644 --- a/src/bentoml_cli/bentos.py +++ b/src/bentoml_cli/bentos.py @@ -25,6 +25,8 @@ from bentoml._internal.cloud import BentoCloudClient from bentoml._internal.container import DefaultBuilder + from .utils import Cli + BENTOML_FIGLET = """ ██████╗ ███████╗███╗ ██╗████████╗ ██████╗ ███╗ ███╗██╗ ██╔══██╗██╔════╝████╗ ██║╚══██╔══╝██╔═══██╗████╗ ████║██║ @@ -254,12 +256,10 @@ def import_bento_(bento_path: str) -> None: # type: ignore (not accessed) default=False, help="Force pull from yatai to local and overwrite even if it already exists in local", ) - @click.option( - "--context", type=click.STRING, default=None, help="Yatai context name." - ) - def pull(bento_tag: str, force: bool, context: str) -> None: # type: ignore (not accessed) + @click.pass_obj + def pull(obj: Cli, bento_tag: str, force: bool) -> None: # type: ignore (not accessed) """Pull Bento from a yatai server.""" - cloud_client.pull_bento(bento_tag, force=force, context=context) + cloud_client.pull_bento(bento_tag, force=force, context=obj.context) @cli.command() @click.argument("bento_tag", type=click.STRING) @@ -276,16 +276,14 @@ def pull(bento_tag: str, force: bool, context: str) -> None: # type: ignore (no default=10, help="Number of threads to use for upload", ) - @click.option( - "--context", type=click.STRING, default=None, help="Yatai context name." - ) - def push(bento_tag: str, force: bool, threads: int, context: str) -> None: # type: ignore (not accessed) + @click.pass_obj + def push(obj: Cli, bento_tag: str, force: bool, threads: int) -> None: # type: ignore (not accessed) """Push Bento to a yatai server.""" bento_obj = bento_store.get(bento_tag) if not bento_obj: raise click.ClickException(f"Bento {bento_tag} not found in local store") cloud_client.push_bento( - bento_obj, force=force, threads=threads, context=context + bento_obj, force=force, threads=threads, context=obj.context ) @cli.command() @@ -326,13 +324,6 @@ def push(bento_tag: str, force: bool, threads: int, context: str) -> None: # ty type=click.BOOL, help="Whether to push the result bento to BentoCloud. Make sure to login with 'bentoml cloud login' first.", ) - @click.option( - "--context", - "context", - type=click.STRING, - default=None, - help="Yatai context name.", - ) @click.option( "--force", is_flag=True, default=False, help="Forced push to BentoCloud" ) @@ -405,7 +396,7 @@ def build( # type: ignore (not accessed) if not get_quiet_mode(): click.secho(f"\nPushing {bento} to BentoCloud...", fg="magenta") _cloud_client.push_bento( - bento, force=force, threads=threads, context=context + bento, force=force, threads=threads, context=ctx.obj.context ) elif containerize: backend: DefaultBuilder = t.cast( diff --git a/src/bentoml_cli/models.py b/src/bentoml_cli/models.py index 570eba3461e..1dbb0e3a65a 100644 --- a/src/bentoml_cli/models.py +++ b/src/bentoml_cli/models.py @@ -2,7 +2,6 @@ import json import typing as t -from typing import TYPE_CHECKING import click import yaml @@ -13,11 +12,13 @@ from bentoml_cli.utils import is_valid_bento_name from bentoml_cli.utils import is_valid_bento_tag -if TYPE_CHECKING: +if t.TYPE_CHECKING: from click import Context from click import Group from click import Parameter + from .utils import Cli + def parse_delete_targets_argument_callback( ctx: Context, params: Parameter, value: t.Any # pylint: disable=unused-argument @@ -250,9 +251,6 @@ def import_from(model_path: str) -> None: # type: ignore (not accessed) default=False, help="Force pull from yatai to local and overwrite even if it already exists in local", ) - @click.option( - "--context", type=click.STRING, default=None, help="Yatai context name." - ) @click.option( "-F", "--bentofile", @@ -261,7 +259,7 @@ def import_from(model_path: str) -> None: # type: ignore (not accessed) help="Path to bentofile. Default to 'bentofile.yaml'", ) @click.pass_context - def pull(ctx: click.Context, model_tag: str | None, force: bool, context: str, bentofile: str): # type: ignore (not accessed) + def pull(ctx: click.Context, model_tag: str | None, force: bool, bentofile: str): # type: ignore (not accessed) """Pull Model from a yatai server. If model_tag is not provided, it will pull models defined in bentofile.yaml. """ @@ -270,7 +268,7 @@ def pull(ctx: click.Context, model_tag: str | None, force: bool, context: str, b if model_tag is not None: if ctx.get_parameter_source("bentofile") != ParameterSource.DEFAULT: click.echo("-f bentofile is ignored when model_tag is provided") - cloud_client.pull_model(model_tag, force=force, context=context) + cloud_client.pull_model(model_tag, force=force, context=ctx.obj.context) return try: @@ -287,7 +285,10 @@ def pull(ctx: click.Context, model_tag: str | None, force: bool, context: str, b ) for model_spec in build_config.models: cloud_client.pull_model( - model_spec.tag, force=force, context=context, query=model_spec.filter + model_spec.tag, + force=force, + context=ctx.obj.context, + query=model_spec.filter, ) @model_cli.command() @@ -305,14 +306,12 @@ def pull(ctx: click.Context, model_tag: str | None, force: bool, context: str, b default=10, help="Number of threads to use for upload", ) - @click.option( - "--context", type=click.STRING, default=None, help="Yatai context name." - ) - def push(model_tag: str, force: bool, threads: int, context: str): # type: ignore (not accessed) + @click.pass_obj + def push(obj: Cli, model_tag: str, force: bool, threads: int): # type: ignore (not accessed) """Push Model to a yatai server.""" model_obj = model_store.get(model_tag) if not model_obj: raise click.ClickException(f"Model {model_tag} not found in local store") cloud_client.push_model( - model_obj, force=force, threads=threads, context=context + model_obj, force=force, threads=threads, context=obj.context ) diff --git a/src/bentoml_cli/utils.py b/src/bentoml_cli/utils.py index 1a1643a3956..e0ec0ffdbe9 100644 --- a/src/bentoml_cli/utils.py +++ b/src/bentoml_cli/utils.py @@ -7,13 +7,14 @@ import re import time import typing as t -from typing import TYPE_CHECKING +import attr import click +import click_option_group as cog from click import ClickException from click.exceptions import UsageError -if TYPE_CHECKING: +if t.TYPE_CHECKING: from click import Command from click import Context from click import Group @@ -191,6 +192,11 @@ def opt_callback(ctx: Context, param: Parameter, value: ClickParamType): return value +@attr.define +class Cli: + context: str | None + + class BentoMLCommandGroup(click.Group): """ Click command class customized for BentoML CLI, allow specifying a default @@ -212,10 +218,10 @@ def cli(): ... def serve(): ... """ - NUMBER_OF_COMMON_PARAMS = 3 + NUMBER_OF_COMMON_PARAMS = 5 # NOTE: 4 shared options and a option group title @staticmethod - def bentoml_common_params(func: F[P]) -> WrappedCLI[bool, bool]: + def bentoml_common_params(func: F[P]) -> WrappedCLI[bool, bool, str | None]: # NOTE: update NUMBER_OF_COMMON_PARAMS when adding option. from bentoml._internal.configuration import DEBUG_ENV_VAR from bentoml._internal.configuration import QUIET_ENV_VAR @@ -224,7 +230,8 @@ def bentoml_common_params(func: F[P]) -> WrappedCLI[bool, bool]: from bentoml._internal.log import configure_logging from bentoml._internal.utils.analytics import BENTOML_DO_NOT_TRACK - @click.option( + @cog.optgroup.group("Global options") + @cog.optgroup.option( "-q", "--quiet", is_flag=True, @@ -232,28 +239,39 @@ def bentoml_common_params(func: F[P]) -> WrappedCLI[bool, bool]: envvar=QUIET_ENV_VAR, help="Suppress all warnings and info logs", ) - @click.option( + @cog.optgroup.option( "--verbose", "--debug", + "verbose", is_flag=True, default=False, envvar=DEBUG_ENV_VAR, help="Generate debug information", ) - @click.option( + @cog.optgroup.option( "--do-not-track", is_flag=True, default=False, envvar=BENTOML_DO_NOT_TRACK, help="Do not send usage info", ) + @cog.optgroup.option( + "--context", + type=click.STRING, + default=None, + help="BentoCloud context name.", + ) + @click.pass_context @functools.wraps(func) def wrapper( + ctx: click.Context, quiet: bool, verbose: bool, + context: str | None, *args: P.args, **kwargs: P.kwargs, ) -> t.Any: + ctx.obj = Cli(context=context) if quiet: set_quiet_mode(True) if verbose: @@ -269,7 +287,7 @@ def wrapper( @staticmethod def bentoml_track_usage( - func: F[P] | WrappedCLI[bool, bool], + func: F[P] | WrappedCLI[bool, bool, str | None], cmd_group: click.Group, **kwargs: t.Any, ) -> WrappedCLI[bool]: @@ -459,77 +477,3 @@ def is_valid_bento_tag(value: str) -> bool: def is_valid_bento_name(value: str) -> bool: return re.match(r"^[A-Za-z_0-9]*$", value) is not None - - -def unparse_click_params( - params: dict[str, t.Any], - command_params: list[Parameter], - *, - factory: t.Callable[..., t.Any] | None = None, -) -> list[str]: - """ - Unparse click call to a list of arguments. Used to modify some parameters and - restore to system command. The goal is to unpack cases where parameters can be parsed multiple times. - - Args: - params: The dictionary of the parameters that is parsed from click.Context. - command_params: The list of paramters (Arguments/Options) that is part of a given command. - - Returns: - Unparsed list of arguments that can be redirected to system commands. - - Implementation: - For cases where options is None, or the default value is None, we will remove it from the first params list. - Currently it doesn't support unpacking `prompt_required` or `confirmation_prompt`. - """ - args: list[str] = [] - - # first filter out all parsed parameters that have value None - # This means that the parameter was not set by the user - params = {k: v for k, v in params.items() if v not in [None, (), []]} - - for command_param in command_params: - if isinstance(command_param, click.Argument): - # Arguments.nargs, Arguments.required - if command_param.name in params: - if command_param.nargs > 1: - # multiple arguments are passed as a list. - # In this case we try to convert all None to an empty string - args.extend( - list( - filter( - lambda x: "" if x is None else x, - params[command_param.name], - ) - ) - ) - else: - args.append(params[command_param.name]) - elif isinstance(command_param, click.Option): - if command_param.name in params: - if ( - command_param.confirmation_prompt - or command_param.prompt is not None - ): - logger.warning( - "%s is a prompt, skip parsing it for now.", command_params - ) - if command_param.is_flag: - args.append(command_param.opts[-1]) - else: - cmd = f"--{command_param.name.replace('_','-')}" - if command_param.multiple: - for var in params[command_param.name]: - args.extend([cmd, var]) - else: - args.extend([cmd, params[command_param.name]]) - else: - logger.warning( - "Given command params is a subclass of click.Parameter, but not a click.Argument or click.Option. Passing through..." - ) - - # We will also convert values if factory is parsed: - if factory is not None: - return list(map(factory, args)) - - return args From 317a8bcd11cf2b4cdb62af5ec0742a67d5630281 Mon Sep 17 00:00:00 2001 From: aarnphm-ec2-dev <29749331+aarnphm@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:52:00 +0000 Subject: [PATCH 3/9] fix: deployment to use global context Signed-off-by: aarnphm-ec2-dev <29749331+aarnphm@users.noreply.github.com> --- src/bentoml_cli/bentos.py | 1 - src/bentoml_cli/cloud.py | 37 +++++++++++++++++--------- src/bentoml_cli/deployment.py | 49 ++++++++++++++++------------------- src/bentoml_cli/utils.py | 5 +++- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/src/bentoml_cli/bentos.py b/src/bentoml_cli/bentos.py index f15e6ab1192..a318d5d9613 100644 --- a/src/bentoml_cli/bentos.py +++ b/src/bentoml_cli/bentos.py @@ -339,7 +339,6 @@ def build( # type: ignore (not accessed) push: bool, force: bool, threads: int, - context: str | None, containerize: bool, _bento_store: BentoStore = Provide[BentoMLContainer.bento_store], _cloud_client: BentoCloudClient = Provide[BentoMLContainer.bentocloud_client], diff --git a/src/bentoml_cli/cloud.py b/src/bentoml_cli/cloud.py index f44bce7a82a..685d427a9c7 100644 --- a/src/bentoml_cli/cloud.py +++ b/src/bentoml_cli/cloud.py @@ -1,10 +1,14 @@ from __future__ import annotations import json +import typing as t import click import click_option_group as cog +if t.TYPE_CHECKING: + from .utils import Cli + def add_cloud_command(cli: click.Group) -> click.Group: from bentoml._internal.cloud.client import RestApiClient @@ -12,7 +16,6 @@ def add_cloud_command(cli: click.Group) -> click.Group: from bentoml._internal.cloud.config import CloudClientContext from bentoml._internal.cloud.config import add_context from bentoml._internal.cloud.config import default_context_name - from bentoml._internal.configuration import get_quiet_mode from bentoml._internal.utils import bentoml_cattr from bentoml.exceptions import CLIException from bentoml_cli.utils import BentoMLCommandGroup @@ -40,13 +43,8 @@ def cloud(): type=click.STRING, help="BentoCloud or Yatai user API token", ) - @click.option( - "--context", - type=click.STRING, - help="BentoCloud or Yatai context name for the endpoint and API token", - default=default_context_name, - ) - def login(endpoint: str, api_token: str, context: str) -> None: # type: ignore (not accessed) + @click.pass_obj + def login(obj: Cli, endpoint: str, api_token: str) -> None: # type: ignore (not accessed) """Login to BentoCloud or Yatai server.""" cloud_rest_client = RestApiClient(endpoint, api_token) user = cloud_rest_client.get_current_user() @@ -60,7 +58,7 @@ def login(endpoint: str, api_token: str, context: str) -> None: # type: ignore raise CLIException("current organization is not found") ctx = CloudClientContext( - name=context, + name=obj.context if obj.context is not None else default_context_name, endpoint=endpoint, api_token=api_token, email=user.email, @@ -74,9 +72,24 @@ def login(endpoint: str, api_token: str, context: str) -> None: # type: ignore @cloud.command(aliases=["current-context"]) def get_current_context() -> None: # type: ignore (not accessed) """Get current cloud context.""" - cur = CloudClientConfig.get_config().get_current_context() - if not get_quiet_mode(): - click.echo(json.dumps(bentoml_cattr.unstructure(cur), indent=2)) + click.echo( + json.dumps( + bentoml_cattr.unstructure( + CloudClientConfig.get_config().get_current_context() + ), + indent=2, + ) + ) + + @cloud.command() + def list_context() -> None: # type: ignore (not accessed) + """List all available context.""" + config = CloudClientConfig.get_config() + click.echo( + json.dumps( + bentoml_cattr.unstructure([i.name for i in config.contexts]), indent=2 + ) + ) @cloud.command() @click.argument("context", type=click.STRING) diff --git a/src/bentoml_cli/deployment.py b/src/bentoml_cli/deployment.py index 3cd475f7e72..5d38b476d5f 100644 --- a/src/bentoml_cli/deployment.py +++ b/src/bentoml_cli/deployment.py @@ -8,6 +8,8 @@ TupleStrAny = tuple[str, ...] from bentoml._internal.cloud.schemas import DeploymentListSchema from bentoml._internal.cloud.schemas import DeploymentSchema + + from .utils import Cli else: TupleStrAny = tuple @@ -38,9 +40,6 @@ def shared_decorator(f: t.Callable[..., t.Any]): type=click.STRING, required=True, ), - click.option( - "--context", type=click.STRING, default=None, help="Yatai context name." - ), click.option( "--cluster-name", type=click.STRING, @@ -70,13 +69,11 @@ def deployment_cli(): type=click.File(), help="JSON file path for the deployment configuration", ) - @click.option( - "--context", type=click.STRING, default=None, help="Yatai context name." - ) @output_option + @click.pass_obj def create( # type: ignore + obj: Cli, file: str, - context: str, output: t.Literal["json", "default"], ) -> DeploymentSchema: """Create a deployment on BentoCloud. @@ -85,7 +82,9 @@ def create( # type: ignore A deployment can be created using a json file with configurations. The json file has the exact format as the one on BentoCloud Deployment UI. """ - res = client.deployment.create_from_file(path_or_stream=file, context=context) + res = client.deployment.create_from_file( + path_or_stream=file, context=obj.context + ) if output == "default": console.print(res) elif output == "json": @@ -101,15 +100,13 @@ def create( # type: ignore ) @click.option("-n", "--name", type=click.STRING, help="Deployment name") @click.option("--bento", type=click.STRING, help="Bento tag") - @click.option( - "--context", type=click.STRING, default=None, help="Yatai context name." - ) @output_option + @click.pass_obj def update( # type: ignore + obj: Cli, file: str | None, name: str | None, bento: str | None, - context: str, output: t.Literal["json", "default"], ) -> DeploymentSchema: """Update a deployment on BentoCloud. @@ -122,12 +119,11 @@ def update( # type: ignore if name is not None: click.echo("Reading from file, ignoring --name", err=True) res = client.deployment.update_from_file( - path_or_stream=file, - context=context, + path_or_stream=file, context=obj.context ) elif name is not None: res = client.deployment.update( - name, bento=bento, context=context, latest_bento=True + name, bento=bento, context=obj.context, latest_bento=True ) else: raise click.BadArgumentUsage( @@ -142,9 +138,10 @@ def update( # type: ignore @deployment_cli.command() @shared_decorator + @click.pass_obj def get( # type: ignore + obj: Cli, deployment_name: str, - context: str, cluster_name: str, kube_namespace: str, output: t.Literal["json", "default"], @@ -152,7 +149,7 @@ def get( # type: ignore """Get a deployment on BentoCloud.""" res = client.deployment.get( deployment_name=deployment_name, - context=context, + context=obj.context, cluster_name=cluster_name, kube_namespace=kube_namespace, ) @@ -165,9 +162,10 @@ def get( # type: ignore @deployment_cli.command() @shared_decorator + @click.pass_obj def terminate( # type: ignore + obj: Cli, deployment_name: str, - context: str, cluster_name: str, kube_namespace: str, output: t.Literal["json", "default"], @@ -175,7 +173,7 @@ def terminate( # type: ignore """Terminate a deployment on BentoCloud.""" res = client.deployment.terminate( deployment_name=deployment_name, - context=context, + context=obj.context, cluster_name=cluster_name, kube_namespace=kube_namespace, ) @@ -188,9 +186,10 @@ def terminate( # type: ignore @deployment_cli.command() @shared_decorator + @click.pass_obj def delete( # type: ignore + obj: Cli, deployment_name: str, - context: str, cluster_name: str, kube_namespace: str, output: t.Literal["json", "default"], @@ -198,7 +197,7 @@ def delete( # type: ignore """Delete a deployment on BentoCloud.""" res = client.deployment.delete( deployment_name=deployment_name, - context=context, + context=obj.context, cluster_name=cluster_name, kube_namespace=kube_namespace, ) @@ -210,9 +209,6 @@ def delete( # type: ignore return res @deployment_cli.command() - @click.option( - "--context", type=click.STRING, default=None, help="Yatai context name." - ) @click.option( "--cluster-name", type=click.STRING, default=None, help="Name of the cluster." ) @@ -235,8 +231,9 @@ def delete( # type: ignore type=click.Choice(["json", "default", "table"]), default="table", ) + @click.pass_obj def list( # type: ignore - context: str, + obj: Cli, cluster_name: str, query: str, search: str, @@ -246,7 +243,7 @@ def list( # type: ignore ) -> DeploymentListSchema: """List existing deployments on BentoCloud.""" res = client.deployment.list( - context=context, + context=obj.context, cluster_name=cluster_name, query=query, search=search, diff --git a/src/bentoml_cli/utils.py b/src/bentoml_cli/utils.py index e0ec0ffdbe9..3dc351576dd 100644 --- a/src/bentoml_cli/utils.py +++ b/src/bentoml_cli/utils.py @@ -194,7 +194,10 @@ def opt_callback(ctx: Context, param: Parameter, value: ClickParamType): @attr.define class Cli: - context: str | None + context: str | None = attr.field(default=None) + + def with_options(self, **attrs: t.Any) -> t.Any: + return attr.evolve(self, **attrs) class BentoMLCommandGroup(click.Group): From ab6190f1bd65873691f644953d98183034f7acd5 Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Thu, 20 Jul 2023 13:06:08 -0400 Subject: [PATCH 4/9] fix: name collision with global context var Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- src/bentoml_cli/cloud.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bentoml_cli/cloud.py b/src/bentoml_cli/cloud.py index 685d427a9c7..2d5e77c7837 100644 --- a/src/bentoml_cli/cloud.py +++ b/src/bentoml_cli/cloud.py @@ -92,10 +92,10 @@ def list_context() -> None: # type: ignore (not accessed) ) @cloud.command() - @click.argument("context", type=click.STRING) - def update_current_context(context: str) -> None: # type: ignore (not accessed) + @click.argument("context_name", type=click.STRING) + def update_current_context(context_name: str) -> None: # type: ignore (not accessed) """Update current context""" - ctx = CloudClientConfig.get_config().set_current_context(context) + ctx = CloudClientConfig.get_config().set_current_context(context_name) click.echo(f"Successfully switched to context: {ctx.name}") return cli From d106ef9377bf93b9314dbc4ceabc13a4fd66b4d6 Mon Sep 17 00:00:00 2001 From: aarnphm-ec2-dev <29749331+aarnphm@users.noreply.github.com> Date: Thu, 20 Jul 2023 17:13:10 +0000 Subject: [PATCH 5/9] chore: remove long name options Signed-off-by: aarnphm-ec2-dev <29749331+aarnphm@users.noreply.github.com> --- src/bentoml_cli/cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bentoml_cli/cloud.py b/src/bentoml_cli/cloud.py index 2d5e77c7837..5aaa941dfcd 100644 --- a/src/bentoml_cli/cloud.py +++ b/src/bentoml_cli/cloud.py @@ -69,8 +69,8 @@ def login(obj: Cli, endpoint: str, api_token: str) -> None: # type: ignore (not f"Successfully logged in to BentoCloud for {user.name} in {org.name}" ) - @cloud.command(aliases=["current-context"]) - def get_current_context() -> None: # type: ignore (not accessed) + @cloud.command() + def current_context() -> None: # type: ignore (not accessed) """Get current cloud context.""" click.echo( json.dumps( From d072dcb4001d86b7998af86594ec9d946b4ce731 Mon Sep 17 00:00:00 2001 From: aarnphm-ec2-dev <29749331+aarnphm@users.noreply.github.com> Date: Thu, 20 Jul 2023 17:22:06 +0000 Subject: [PATCH 6/9] chore: add docstring about the click object Signed-off-by: aarnphm-ec2-dev <29749331+aarnphm@users.noreply.github.com> --- src/bentoml_cli/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bentoml_cli/utils.py b/src/bentoml_cli/utils.py index 3dc351576dd..0c5b3acc106 100644 --- a/src/bentoml_cli/utils.py +++ b/src/bentoml_cli/utils.py @@ -194,6 +194,8 @@ def opt_callback(ctx: Context, param: Parameter, value: ClickParamType): @attr.define class Cli: + """This is the click.Context object that will be used in BentoML CLI.""" + context: str | None = attr.field(default=None) def with_options(self, **attrs: t.Any) -> t.Any: From 7d6f51f7637615e3bd89b6bbd036d4454985fb58 Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Thu, 20 Jul 2023 13:47:18 -0400 Subject: [PATCH 7/9] chore: renaming internal object context to cloud_context Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- src/bentoml_cli/bentos.py | 20 ++++++++++++++------ src/bentoml_cli/cloud.py | 8 +++++--- src/bentoml_cli/deployment.py | 31 +++++++++++++++++-------------- src/bentoml_cli/models.py | 11 +++++++---- src/bentoml_cli/utils.py | 9 +++++---- 5 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/bentoml_cli/bentos.py b/src/bentoml_cli/bentos.py index a318d5d9613..c39e43acd00 100644 --- a/src/bentoml_cli/bentos.py +++ b/src/bentoml_cli/bentos.py @@ -25,7 +25,7 @@ from bentoml._internal.cloud import BentoCloudClient from bentoml._internal.container import DefaultBuilder - from .utils import Cli + from .utils import SharedOptions BENTOML_FIGLET = """ ██████╗ ███████╗███╗ ██╗████████╗ ██████╗ ███╗ ███╗██╗ @@ -257,9 +257,11 @@ def import_bento_(bento_path: str) -> None: # type: ignore (not accessed) help="Force pull from yatai to local and overwrite even if it already exists in local", ) @click.pass_obj - def pull(obj: Cli, bento_tag: str, force: bool) -> None: # type: ignore (not accessed) + def pull(shared_options: SharedOptions, bento_tag: str, force: bool) -> None: # type: ignore (not accessed) """Pull Bento from a yatai server.""" - cloud_client.pull_bento(bento_tag, force=force, context=obj.context) + cloud_client.pull_bento( + bento_tag, force=force, context=shared_options.cloud_context + ) @cli.command() @click.argument("bento_tag", type=click.STRING) @@ -277,13 +279,16 @@ def pull(obj: Cli, bento_tag: str, force: bool) -> None: # type: ignore (not ac help="Number of threads to use for upload", ) @click.pass_obj - def push(obj: Cli, bento_tag: str, force: bool, threads: int) -> None: # type: ignore (not accessed) + def push(shared_options: SharedOptions, bento_tag: str, force: bool, threads: int) -> None: # type: ignore (not accessed) """Push Bento to a yatai server.""" bento_obj = bento_store.get(bento_tag) if not bento_obj: raise click.ClickException(f"Bento {bento_tag} not found in local store") cloud_client.push_bento( - bento_obj, force=force, threads=threads, context=obj.context + bento_obj, + force=force, + threads=threads, + context=shared_options.cloud_context, ) @cli.command() @@ -395,7 +400,10 @@ def build( # type: ignore (not accessed) if not get_quiet_mode(): click.secho(f"\nPushing {bento} to BentoCloud...", fg="magenta") _cloud_client.push_bento( - bento, force=force, threads=threads, context=ctx.obj.context + bento, + force=force, + threads=threads, + context=t.cast("SharedOptions", ctx.obj).cloud_context, ) elif containerize: backend: DefaultBuilder = t.cast( diff --git a/src/bentoml_cli/cloud.py b/src/bentoml_cli/cloud.py index 5aaa941dfcd..be0cab7ee91 100644 --- a/src/bentoml_cli/cloud.py +++ b/src/bentoml_cli/cloud.py @@ -7,7 +7,7 @@ import click_option_group as cog if t.TYPE_CHECKING: - from .utils import Cli + from .utils import SharedOptions def add_cloud_command(cli: click.Group) -> click.Group: @@ -44,7 +44,7 @@ def cloud(): help="BentoCloud or Yatai user API token", ) @click.pass_obj - def login(obj: Cli, endpoint: str, api_token: str) -> None: # type: ignore (not accessed) + def login(shared_options: SharedOptions, endpoint: str, api_token: str) -> None: # type: ignore (not accessed) """Login to BentoCloud or Yatai server.""" cloud_rest_client = RestApiClient(endpoint, api_token) user = cloud_rest_client.get_current_user() @@ -58,7 +58,9 @@ def login(obj: Cli, endpoint: str, api_token: str) -> None: # type: ignore (not raise CLIException("current organization is not found") ctx = CloudClientContext( - name=obj.context if obj.context is not None else default_context_name, + name=shared_options.context + if shared_options.context is not None + else default_context_name, endpoint=endpoint, api_token=api_token, email=user.email, diff --git a/src/bentoml_cli/deployment.py b/src/bentoml_cli/deployment.py index 5d38b476d5f..371a755b172 100644 --- a/src/bentoml_cli/deployment.py +++ b/src/bentoml_cli/deployment.py @@ -9,7 +9,7 @@ from bentoml._internal.cloud.schemas import DeploymentListSchema from bentoml._internal.cloud.schemas import DeploymentSchema - from .utils import Cli + from .utils import SharedOptions else: TupleStrAny = tuple @@ -72,7 +72,7 @@ def deployment_cli(): @output_option @click.pass_obj def create( # type: ignore - obj: Cli, + shared_options: SharedOptions, file: str, output: t.Literal["json", "default"], ) -> DeploymentSchema: @@ -83,7 +83,7 @@ def create( # type: ignore The json file has the exact format as the one on BentoCloud Deployment UI. """ res = client.deployment.create_from_file( - path_or_stream=file, context=obj.context + path_or_stream=file, context=shared_options.cloud_context ) if output == "default": console.print(res) @@ -103,7 +103,7 @@ def create( # type: ignore @output_option @click.pass_obj def update( # type: ignore - obj: Cli, + shared_options: SharedOptions, file: str | None, name: str | None, bento: str | None, @@ -119,11 +119,14 @@ def update( # type: ignore if name is not None: click.echo("Reading from file, ignoring --name", err=True) res = client.deployment.update_from_file( - path_or_stream=file, context=obj.context + path_or_stream=file, context=shared_options.cloud_context ) elif name is not None: res = client.deployment.update( - name, bento=bento, context=obj.context, latest_bento=True + name, + bento=bento, + context=shared_options.cloud_context, + latest_bento=True, ) else: raise click.BadArgumentUsage( @@ -140,7 +143,7 @@ def update( # type: ignore @shared_decorator @click.pass_obj def get( # type: ignore - obj: Cli, + shared_options: SharedOptions, deployment_name: str, cluster_name: str, kube_namespace: str, @@ -149,7 +152,7 @@ def get( # type: ignore """Get a deployment on BentoCloud.""" res = client.deployment.get( deployment_name=deployment_name, - context=obj.context, + context=shared_options.cloud_context, cluster_name=cluster_name, kube_namespace=kube_namespace, ) @@ -164,7 +167,7 @@ def get( # type: ignore @shared_decorator @click.pass_obj def terminate( # type: ignore - obj: Cli, + shared_options: SharedOptions, deployment_name: str, cluster_name: str, kube_namespace: str, @@ -173,7 +176,7 @@ def terminate( # type: ignore """Terminate a deployment on BentoCloud.""" res = client.deployment.terminate( deployment_name=deployment_name, - context=obj.context, + context=shared_options.cloud_context, cluster_name=cluster_name, kube_namespace=kube_namespace, ) @@ -188,7 +191,7 @@ def terminate( # type: ignore @shared_decorator @click.pass_obj def delete( # type: ignore - obj: Cli, + shared_options: SharedOptions, deployment_name: str, cluster_name: str, kube_namespace: str, @@ -197,7 +200,7 @@ def delete( # type: ignore """Delete a deployment on BentoCloud.""" res = client.deployment.delete( deployment_name=deployment_name, - context=obj.context, + context=shared_options.cloud_context, cluster_name=cluster_name, kube_namespace=kube_namespace, ) @@ -233,7 +236,7 @@ def delete( # type: ignore ) @click.pass_obj def list( # type: ignore - obj: Cli, + shared_options: SharedOptions, cluster_name: str, query: str, search: str, @@ -243,7 +246,7 @@ def list( # type: ignore ) -> DeploymentListSchema: """List existing deployments on BentoCloud.""" res = client.deployment.list( - context=obj.context, + context=shared_options.cloud_context, cluster_name=cluster_name, query=query, search=search, diff --git a/src/bentoml_cli/models.py b/src/bentoml_cli/models.py index 1dbb0e3a65a..494b5c18dde 100644 --- a/src/bentoml_cli/models.py +++ b/src/bentoml_cli/models.py @@ -17,7 +17,7 @@ from click import Group from click import Parameter - from .utils import Cli + from .utils import SharedOptions def parse_delete_targets_argument_callback( @@ -287,7 +287,7 @@ def pull(ctx: click.Context, model_tag: str | None, force: bool, bentofile: str) cloud_client.pull_model( model_spec.tag, force=force, - context=ctx.obj.context, + context=t.cast("SharedOptions", ctx.obj).cloud_context, query=model_spec.filter, ) @@ -307,11 +307,14 @@ def pull(ctx: click.Context, model_tag: str | None, force: bool, bentofile: str) help="Number of threads to use for upload", ) @click.pass_obj - def push(obj: Cli, model_tag: str, force: bool, threads: int): # type: ignore (not accessed) + def push(shared_options: SharedOptions, model_tag: str, force: bool, threads: int): # type: ignore (not accessed) """Push Model to a yatai server.""" model_obj = model_store.get(model_tag) if not model_obj: raise click.ClickException(f"Model {model_tag} not found in local store") cloud_client.push_model( - model_obj, force=force, threads=threads, context=obj.context + model_obj, + force=force, + threads=threads, + context=shared_options.cloud_context, ) diff --git a/src/bentoml_cli/utils.py b/src/bentoml_cli/utils.py index 0c5b3acc106..4525234da5a 100644 --- a/src/bentoml_cli/utils.py +++ b/src/bentoml_cli/utils.py @@ -193,10 +193,10 @@ def opt_callback(ctx: Context, param: Parameter, value: ClickParamType): @attr.define -class Cli: +class SharedOptions: """This is the click.Context object that will be used in BentoML CLI.""" - context: str | None = attr.field(default=None) + cloud_context: str | None = attr.field(default=None) def with_options(self, **attrs: t.Any) -> t.Any: return attr.evolve(self, **attrs) @@ -262,6 +262,7 @@ def bentoml_common_params(func: F[P]) -> WrappedCLI[bool, bool, str | None]: ) @cog.optgroup.option( "--context", + "cloud_context", type=click.STRING, default=None, help="BentoCloud context name.", @@ -272,11 +273,11 @@ def wrapper( ctx: click.Context, quiet: bool, verbose: bool, - context: str | None, + cloud_context: str | None, *args: P.args, **kwargs: P.kwargs, ) -> t.Any: - ctx.obj = Cli(context=context) + ctx.obj = SharedOptions(cloud_context=cloud_context) if quiet: set_quiet_mode(True) if verbose: From 32ceeb804ce19a0184dba94dd215a4de31300cd2 Mon Sep 17 00:00:00 2001 From: Aaron Pham <29749331+aarnphm@users.noreply.github.com> Date: Thu, 20 Jul 2023 15:22:49 -0400 Subject: [PATCH 8/9] fix: item Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- src/bentoml_cli/cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bentoml_cli/cloud.py b/src/bentoml_cli/cloud.py index be0cab7ee91..5860191a788 100644 --- a/src/bentoml_cli/cloud.py +++ b/src/bentoml_cli/cloud.py @@ -58,8 +58,8 @@ def login(shared_options: SharedOptions, endpoint: str, api_token: str) -> None: raise CLIException("current organization is not found") ctx = CloudClientContext( - name=shared_options.context - if shared_options.context is not None + name=shared_options.cloud_context + if shared_options.cloud_context is not None else default_context_name, endpoint=endpoint, api_token=api_token, From 097b90d471fa7fd0fcd629c4d66bd64b4b1c50af Mon Sep 17 00:00:00 2001 From: Aaron <29749331+aarnphm@users.noreply.github.com> Date: Thu, 20 Jul 2023 15:25:12 -0400 Subject: [PATCH 9/9] chore: update warning message it is no longer Yatai specific anymore Signed-off-by: Aaron <29749331+aarnphm@users.noreply.github.com> --- src/bentoml/_internal/cloud/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bentoml/_internal/cloud/config.py b/src/bentoml/_internal/cloud/config.py index 768636dd4ea..633aed22581 100644 --- a/src/bentoml/_internal/cloud/config.py +++ b/src/bentoml/_internal/cloud/config.py @@ -111,7 +111,7 @@ def add_context(context: CloudClientContext, *, ignore_warning: bool = False) -> for idx, ctx in enumerate(config.contexts): if ctx.name == context.name: if not ignore_warning: - logger.warning("Overriding existing Yatai context config: %s", ctx.name) + logger.warning("Overriding existing cloud context config: %s", ctx.name) config.contexts[idx] = context break else: