From 885f7eda0293874af868c7136cc3d8389231a5f1 Mon Sep 17 00:00:00 2001 From: jianshen92 Date: Wed, 25 Mar 2026 16:51:18 +0800 Subject: [PATCH 1/2] feat: add remote bento deletion support via CLI and Python API Add the ability to delete bentos from BentoCloud by calling the existing server-side DELETE /api/v1/bento_repositories/{name}/bentos/{version} endpoint, which was previously not exposed in the Python SDK. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/bentoml/_internal/cloud/bento.py | 13 ++++++++ src/bentoml/_internal/cloud/client.py | 7 ++++ src/bentoml/bentos.py | 12 ++++++- src/bentoml_cli/bentos.py | 46 ++++++++++++++++++++------- 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/src/bentoml/_internal/cloud/bento.py b/src/bentoml/_internal/cloud/bento.py index e45cf053e4e..f24018900b4 100644 --- a/src/bentoml/_internal/cloud/bento.py +++ b/src/bentoml/_internal/cloud/bento.py @@ -562,6 +562,19 @@ def list(self) -> BentoWithRepositoryListSchema: ] return res + def delete(self, name: str, version: str) -> None: + """Delete a bento from the remote bento store + + Args: + name: The name of the bento repository + version: The version of the bento to delete + """ + rest_client = self._client + res = rest_client.v1.get_bento(bento_repository_name=name, version=version) + if res is None: + raise NotFound(f'Bento "{name}:{version}" not found on remote') + rest_client.v1.delete_bento(bento_repository_name=name, version=version) + def get(self, name: str, version: str | None) -> BentoSchema: """Get a bento by name and version diff --git a/src/bentoml/_internal/cloud/client.py b/src/bentoml/_internal/cloud/client.py index 77dad1136af..9f312a700f4 100644 --- a/src/bentoml/_internal/cloud/client.py +++ b/src/bentoml/_internal/cloud/client.py @@ -231,6 +231,13 @@ def finish_upload_bento( self._check_resp(resp) return schema_from_object(resp.json(), BentoSchema) + def delete_bento( + self, bento_repository_name: str, version: str + ) -> None: + url = f"/api/v1/bento_repositories/{bento_repository_name}/bentos/{version}" + resp = self.session.delete(url) + self._check_resp(resp) + def upload_bento( self, bento_repository_name: str, version: str, data: t.IO[bytes] ) -> None: diff --git a/src/bentoml/bentos.py b/src/bentoml/bentos.py index fa46a72d9ab..2621fa3774d 100644 --- a/src/bentoml/bentos.py +++ b/src/bentoml/bentos.py @@ -76,9 +76,19 @@ def get( def delete( tag: Tag | str, *, + cloud: bool = False, _bento_store: BentoStore = Provide[BentoMLContainer.bento_store], + _cloud_client: BentoCloudClient = Provide[BentoMLContainer.bentocloud_client], ): - _bento_store.delete(tag) + if cloud: + _tag = Tag.from_taglike(tag) + if _tag.version is None: + raise BentoMLException( + f'Bento version is required for cloud deletion. Please specify a version, e.g. "{_tag.name}:"' + ) + _cloud_client.bento.delete(_tag.name, _tag.version) + else: + _bento_store.delete(tag) @inject diff --git a/src/bentoml_cli/bentos.py b/src/bentoml_cli/bentos.py index 6090b058719..d50af197c45 100644 --- a/src/bentoml_cli/bentos.py +++ b/src/bentoml_cli/bentos.py @@ -220,13 +220,21 @@ def list_bentos( is_flag=True, help="Skip confirmation when deleting a specific bento bundle", ) + @click.option( + "--cloud", + is_flag=True, + default=False, + help="Delete Bento from BentoCloud remote server", + ) @inject def delete( delete_targets: list[str], yes: bool, + cloud: bool, bento_store: BentoStore = Provide[BentoMLContainer.bento_store], + cloud_client: BentoCloudClient = Provide[BentoMLContainer.bentocloud_client], ) -> None: # type: ignore (not accessed) - """Delete Bento in local bento store. + """Delete Bento in local bento store or from BentoCloud. \b Examples: @@ -235,25 +243,39 @@ def delete( * Bulk delete multiple bento bundles by name and version, separated by ",", e.g.: `bentoml delete Irisclassifier:v1,MyPredictService:v2` * Bulk delete multiple bento bundles by name and version, separated by " ", e.g.: `bentoml delete Irisclassifier:v1 MyPredictService:v2` * Bulk delete without confirmation, e.g.: `bentoml delete IrisClassifier --yes` + * Delete from BentoCloud, e.g.: `bentoml delete IrisClassifier:v1 --cloud` """ def delete_target(target: str) -> None: tag = Tag.from_str(target) - if tag.version is None: - to_delete_bentos = bento_store.list(target) + if cloud: + if tag.version is None: + raise click.BadParameter( + f'Deleting all versions is not supported for cloud bentos. Please specify a version, e.g. "{tag.name}:"' + ) + if not yes: + if not click.confirm( + f"delete bento {tag} from BentoCloud?" + ): + return + cloud_client.bento.delete(tag.name, tag.version) + rich.print(f"Bento {tag} deleted from BentoCloud.") else: - to_delete_bentos = [bento_store.get(tag)] - - for bento in to_delete_bentos: - if yes: - delete_confirmed = True + if tag.version is None: + to_delete_bentos = bento_store.list(target) else: - delete_confirmed = click.confirm(f"delete bento {bento.tag}?") + to_delete_bentos = [bento_store.get(tag)] + + for bento in to_delete_bentos: + if yes: + delete_confirmed = True + else: + delete_confirmed = click.confirm(f"delete bento {bento.tag}?") - if delete_confirmed: - bento_store.delete(bento.tag) - rich.print(f"{bento} deleted.") + if delete_confirmed: + bento_store.delete(bento.tag) + rich.print(f"{bento} deleted.") for target in delete_targets: delete_target(target) From 411a70380002d616b41e988cc41a140291f6bcf2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 08:52:34 +0000 Subject: [PATCH 2/2] ci: auto fixes from pre-commit.ci For more information, see https://pre-commit.ci --- src/bentoml/_internal/cloud/client.py | 4 +--- src/bentoml_cli/bentos.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/bentoml/_internal/cloud/client.py b/src/bentoml/_internal/cloud/client.py index 9f312a700f4..1fba7a4a5f3 100644 --- a/src/bentoml/_internal/cloud/client.py +++ b/src/bentoml/_internal/cloud/client.py @@ -231,9 +231,7 @@ def finish_upload_bento( self._check_resp(resp) return schema_from_object(resp.json(), BentoSchema) - def delete_bento( - self, bento_repository_name: str, version: str - ) -> None: + def delete_bento(self, bento_repository_name: str, version: str) -> None: url = f"/api/v1/bento_repositories/{bento_repository_name}/bentos/{version}" resp = self.session.delete(url) self._check_resp(resp) diff --git a/src/bentoml_cli/bentos.py b/src/bentoml_cli/bentos.py index d50af197c45..eb62af42ee8 100644 --- a/src/bentoml_cli/bentos.py +++ b/src/bentoml_cli/bentos.py @@ -255,9 +255,7 @@ def delete_target(target: str) -> None: f'Deleting all versions is not supported for cloud bentos. Please specify a version, e.g. "{tag.name}:"' ) if not yes: - if not click.confirm( - f"delete bento {tag} from BentoCloud?" - ): + if not click.confirm(f"delete bento {tag} from BentoCloud?"): return cloud_client.bento.delete(tag.name, tag.version) rich.print(f"Bento {tag} deleted from BentoCloud.")