From acdf7e3be8c7631fc3b890076c02774f2e72949f Mon Sep 17 00:00:00 2001 From: Venkateswarlu Boggavarapu Date: Wed, 22 Apr 2026 15:54:50 -0400 Subject: [PATCH 1/5] feat(cli): Add `feast projects delete` command (closes #5095) Exposes project deletion via the CLI. The new `feast projects delete ` command calls store.registry.delete_project() which is already implemented in all concrete registries. - Adds interactive confirmation prompt with --yes/-y flag for scripting - Catches both FeastObjectNotFoundException and ProjectNotFoundException - Exits with code 1 for non-existent projects Signed-off-by: Venkateswarlu Boggavarapu --- sdk/python/feast/cli/projects.py | 71 +++++++++++++++++++------------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/sdk/python/feast/cli/projects.py b/sdk/python/feast/cli/projects.py index ea8d5b573a6..3ab3734d2a4 100644 --- a/sdk/python/feast/cli/projects.py +++ b/sdk/python/feast/cli/projects.py @@ -3,16 +3,15 @@ from feast import utils from feast.cli.cli_options import tagsOption -from feast.errors import FeastObjectNotFoundException +from feast.errors import FeastObjectNotFoundException, ProjectNotFoundException from feast.repo_operations import create_feature_store @click.group(name="projects") def projects_cmd(): """ - Access projects + Access and manage projects """ - pass @projects_cmd.command("describe") @@ -27,8 +26,8 @@ def project_describe(ctx: click.Context, name: str): try: project = store.get_project(name) except FeastObjectNotFoundException as e: - print(e) - exit(1) + print(str(e)) + raise SystemExit(1) print( yaml.dump( @@ -41,24 +40,13 @@ def project_describe(ctx: click.Context, name: str): @click.pass_context def project_current(ctx: click.Context): """ - Returns the current project configured with FeatureStore object + Returns the current project configured with FeatureStore """ store = create_feature_store(ctx) - - try: - project = store.get_project(name=None) - except FeastObjectNotFoundException as e: - print(e) - exit(1) - - print( - yaml.dump( - yaml.safe_load(str(project)), default_flow_style=False, sort_keys=False - ) - ) + print(store.project) -@projects_cmd.command(name="list") +@projects_cmd.command("list") @tagsOption @click.pass_context def project_list(ctx: click.Context, tags: list[str]): @@ -66,15 +54,42 @@ def project_list(ctx: click.Context, tags: list[str]): List all projects """ store = create_feature_store(ctx) - table = [] - tags_filter = utils.tags_list_to_dict(tags) - for project in store.list_projects(tags=tags_filter): - table.append([project.name, project.description, project.tags, project.owner]) - from tabulate import tabulate + projects = store.list_projects( + tags=dict(tag.split(":", 1) for tag in tags), + ) - print( - tabulate( - table, headers=["NAME", "DESCRIPTION", "TAGS", "OWNER"], tablefmt="plain" + for project in projects: + print(utils.py_object_to_proto(project).name) + + +@projects_cmd.command("delete") +@click.argument("name", type=click.STRING) +@click.option( + "-y", + "--yes", + is_flag=True, + default=False, + help="Skip confirmation prompt and delete immediately.", +) +@click.pass_context +def project_delete(ctx: click.Context, name: str, yes: bool): + """ + Delete a project and all its resources from the registry. + """ + store = create_feature_store(ctx) + + if not yes: + click.confirm( + f"Are you sure you want to delete project '{name}'? " + "This will remove all associated resources from the registry.", + abort=True, ) - ) + + try: + store.registry.delete_project(name, commit=True) + except (FeastObjectNotFoundException, ProjectNotFoundException) as e: + print(str(e)) + raise SystemExit(1) + + print(f"Project '{name}' deleted successfully.") From d113a3a21254b41540b992c582bef4467fc51889 Mon Sep 17 00:00:00 2001 From: Venkateswarlu Boggavarapu Date: Wed, 22 Apr 2026 16:17:26 -0400 Subject: [PATCH 2/5] fix(cli): align project_list and project_current with master implementation Addresses Devin Review findings: - Fix project_list: use utils.tags_list_to_dict() instead of manual tag.split(), and print tabulated output matching master - Fix project_current: call store.get_project(name=None) with error handling instead of store.project Both functions now match master branch exactly. Only project_delete is new code from this PR. Signed-off-by: Venkateswarlu Boggavarapu --- sdk/python/feast/cli/projects.py | 39 +++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/sdk/python/feast/cli/projects.py b/sdk/python/feast/cli/projects.py index 3ab3734d2a4..8946d6c34c2 100644 --- a/sdk/python/feast/cli/projects.py +++ b/sdk/python/feast/cli/projects.py @@ -10,8 +10,9 @@ @click.group(name="projects") def projects_cmd(): """ - Access and manage projects + Access projects """ + pass @projects_cmd.command("describe") @@ -26,8 +27,8 @@ def project_describe(ctx: click.Context, name: str): try: project = store.get_project(name) except FeastObjectNotFoundException as e: - print(str(e)) - raise SystemExit(1) + print(e) + exit(1) print( yaml.dump( @@ -40,13 +41,24 @@ def project_describe(ctx: click.Context, name: str): @click.pass_context def project_current(ctx: click.Context): """ - Returns the current project configured with FeatureStore + Returns the current project configured with FeatureStore object """ store = create_feature_store(ctx) - print(store.project) + try: + project = store.get_project(name=None) + except FeastObjectNotFoundException as e: + print(e) + exit(1) -@projects_cmd.command("list") + print( + yaml.dump( + yaml.safe_load(str(project)), default_flow_style=False, sort_keys=False + ) + ) + + +@projects_cmd.command(name="list") @tagsOption @click.pass_context def project_list(ctx: click.Context, tags: list[str]): @@ -54,13 +66,18 @@ def project_list(ctx: click.Context, tags: list[str]): List all projects """ store = create_feature_store(ctx) + table = [] + tags_filter = utils.tags_list_to_dict(tags) + for project in store.list_projects(tags=tags_filter): + table.append([project.name, project.description, project.tags, project.owner]) - projects = store.list_projects( - tags=dict(tag.split(":", 1) for tag in tags), - ) + from tabulate import tabulate - for project in projects: - print(utils.py_object_to_proto(project).name) + print( + tabulate( + table, headers=["NAME", "DESCRIPTION", "TAGS", "OWNER"], tablefmt="plain" + ) + ) @projects_cmd.command("delete") From c713be7d671b151d08abfba58cfd4338bd940289 Mon Sep 17 00:00:00 2001 From: Venkateswarlu Boggavarapu Date: Tue, 5 May 2026 21:39:33 -0400 Subject: [PATCH 3/5] refactor(cli): use store.delete_project() instead of registry directly Refactor project listing and deletion commands in CLI. --- sdk/python/feast/cli/projects.py | 59 ++++++++++++++++---------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/sdk/python/feast/cli/projects.py b/sdk/python/feast/cli/projects.py index 8946d6c34c2..c552a058635 100644 --- a/sdk/python/feast/cli/projects.py +++ b/sdk/python/feast/cli/projects.py @@ -23,16 +23,16 @@ def project_describe(ctx: click.Context, name: str): Describe a project """ store = create_feature_store(ctx) - try: project = store.get_project(name) except FeastObjectNotFoundException as e: print(e) exit(1) - print( yaml.dump( - yaml.safe_load(str(project)), default_flow_style=False, sort_keys=False + yaml.safe_load(str(project)), + default_flow_style=False, + sort_keys=False, ) ) @@ -44,38 +44,16 @@ def project_current(ctx: click.Context): Returns the current project configured with FeatureStore object """ store = create_feature_store(ctx) - try: project = store.get_project(name=None) except FeastObjectNotFoundException as e: print(e) exit(1) - print( yaml.dump( - yaml.safe_load(str(project)), default_flow_style=False, sort_keys=False - ) - ) - - -@projects_cmd.command(name="list") -@tagsOption -@click.pass_context -def project_list(ctx: click.Context, tags: list[str]): - """ - List all projects - """ - store = create_feature_store(ctx) - table = [] - tags_filter = utils.tags_list_to_dict(tags) - for project in store.list_projects(tags=tags_filter): - table.append([project.name, project.description, project.tags, project.owner]) - - from tabulate import tabulate - - print( - tabulate( - table, headers=["NAME", "DESCRIPTION", "TAGS", "OWNER"], tablefmt="plain" + yaml.safe_load(str(project)), + default_flow_style=False, + sort_keys=False, ) ) @@ -104,9 +82,32 @@ def project_delete(ctx: click.Context, name: str, yes: bool): ) try: - store.registry.delete_project(name, commit=True) + store.delete_project(name) except (FeastObjectNotFoundException, ProjectNotFoundException) as e: print(str(e)) raise SystemExit(1) print(f"Project '{name}' deleted successfully.") + + +@projects_cmd.command(name="list") +@tagsOption +@click.pass_context +def project_list(ctx: click.Context, tags: list[str]): + """ + List all projects + """ + store = create_feature_store(ctx) + table = [] + tags_filter = utils.tags_list_to_dict(tags) + for project in store.list_projects(tags=tags_filter): + table.append([project.name, project.description, project.tags, project.owner]) + from tabulate import tabulate + + print( + tabulate( + table, + headers=["NAME", "DESCRIPTION", "TAGS", "OWNER"], + tablefmt="plain", + ) + ) From b18ba31e7cdf50fa89ad72685938a6c526bceb6f Mon Sep 17 00:00:00 2001 From: Venkateswarlu Boggavarapu Date: Tue, 5 May 2026 21:41:17 -0400 Subject: [PATCH 4/5] feat(cli): add delete_project method to FeatureStore Added a method to delete a project from the registry. --- sdk/python/feast/feature_store.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py index f95bbf10c03..987103f1fe4 100644 --- a/sdk/python/feast/feature_store.py +++ b/sdk/python/feast/feature_store.py @@ -3401,7 +3401,20 @@ def get_project(self, name: Optional[str]) -> Project: """ return self.registry.get_project(name or self.project) - def list_saved_datasets( + def delete_project(self, name: str, commit: bool = True) -> None: + """ + Deletes a project from the registry. + + Args: + name: Name of the project to delete. + commit: Whether the change should be persisted immediately. + + Raises: + ProjectNotFoundException: The project could not be found. + """ + return self.registry.delete_project(name, commit=commit) + + def list_saved_datasets( self, allow_cache: bool = False, tags: Optional[dict[str, str]] = None ) -> List[SavedDataset]: """ From 5d6419ada51eba5b3db3bb85b9b08a7e7ab69d52 Mon Sep 17 00:00:00 2001 From: Venkateswarlu Boggavarapu Date: Tue, 5 May 2026 22:47:25 -0400 Subject: [PATCH 5/5] fix(store): fix list_saved_datasets indentation in feature_store.py --- sdk/python/feast/feature_store.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py index 987103f1fe4..bfc71db875e 100644 --- a/sdk/python/feast/feature_store.py +++ b/sdk/python/feast/feature_store.py @@ -3414,7 +3414,7 @@ def delete_project(self, name: str, commit: bool = True) -> None: """ return self.registry.delete_project(name, commit=commit) - def list_saved_datasets( + def list_saved_datasets( self, allow_cache: bool = False, tags: Optional[dict[str, str]] = None ) -> List[SavedDataset]: """