From f159c2285ff3d8812c65a2c59bb2868df975c395 Mon Sep 17 00:00:00 2001 From: Linda Oraegbunam <108290852+obielin@users.noreply.github.com> Date: Fri, 29 May 2026 21:25:53 +0100 Subject: [PATCH 1/2] fix: detect duplicate feature view/data source names early in parse_repo (#6417) Moves duplicate feature view name and data source name detection into parse_repo() before FeatureStore initialization. This prevents flaky test failures caused by slow atexit handlers (Dask, PySpark) blocking process exit when the error is raised late. --- sdk/python/feast/repo_operations.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sdk/python/feast/repo_operations.py b/sdk/python/feast/repo_operations.py index 28fe86602ad..ec1a218bd02 100644 --- a/sdk/python/feast/repo_operations.py +++ b/sdk/python/feast/repo_operations.py @@ -28,6 +28,7 @@ from feast.infra.registry.base_registry import BaseRegistry from feast.infra.registry.registry import FEAST_OBJECT_TYPES, FeastObjectType, Registry from feast.names import adjectives, animals +from feast.errors import ConflictingFeatureViewNames, DataSourceRepeatNamesException from feast.on_demand_feature_view import OnDemandFeatureView from feast.permissions.permission import Permission from feast.project import Project @@ -219,6 +220,33 @@ def parse_repo(repo_root: Path) -> RepoContents: elif isinstance(obj, Project) and not any((obj is p) for p in res.projects): res.projects.append(obj) + # Early duplicate detection: validate feature view names are case-insensitively unique + # This runs before FeatureStore initialization to avoid slow cleanup on error + fv_names_seen = {} + all_feature_views = ( + res.feature_views + res.stream_feature_views + res.on_demand_feature_views + ) + for fv in all_feature_views: + lower_name = fv.name.lower() + if lower_name in fv_names_seen: + existing_fv = fv_names_seen[lower_name] + raise ConflictingFeatureViewNames( + fv.name, + existing_type=type(existing_fv).__name__, + new_type=type(fv).__name__, + ) + fv_names_seen[lower_name] = fv + + # Early duplicate detection: validate data source names are case-insensitively unique + ds_names_seen = {} + for ds in res.data_sources: + if ds.name is None: + continue + lower_name = ds.name.lower() + if lower_name in ds_names_seen: + raise DataSourceRepeatNamesException(ds.name) + ds_names_seen[lower_name] = ds + res.entities.append(DUMMY_ENTITY) return res From bbcf25aaf6c94777d276aae5c95da1ef2cbd2d75 Mon Sep 17 00:00:00 2001 From: Linda Oraegbunam <108290852+obielin@users.noreply.github.com> Date: Fri, 29 May 2026 21:32:17 +0100 Subject: [PATCH 2/2] fix: add FeastError handler in CLI plan/apply commands (#6417) Add sys import and FeastError catch blocks to plan_command and apply_total_command in cli.py. This ensures clean error output and immediate exit (code 1) when validation errors like ConflictingFeatureViewNames are raised, preventing unhandled traceback and slow process exit. --- sdk/python/feast/cli/cli.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sdk/python/feast/cli/cli.py b/sdk/python/feast/cli/cli.py index 886c91f69ae..1e03c97e3a4 100644 --- a/sdk/python/feast/cli/cli.py +++ b/sdk/python/feast/cli/cli.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import json +import sys import logging from datetime import datetime from importlib.metadata import version as importlib_version @@ -49,7 +50,7 @@ from feast.cli.ui import ui from feast.cli.validation_references import validation_references_cmd from feast.constants import FEAST_FS_YAML_FILE_PATH_ENV_NAME -from feast.errors import FeastProviderLoginError +from feast.errors import FeastError, FeastProviderLoginError from feast.repo_config import load_repo_config from feast.repo_operations import ( apply_total, @@ -258,6 +259,9 @@ def plan_command( plan(repo_config, repo, skip_source_validation, skip_feature_view_validation) except FeastProviderLoginError as e: print(str(e)) + except FeastError as e: + print(str(e)) + sys.exit(1) @cli.command("apply", cls=NoOptionDefaultFormat) @@ -316,6 +320,9 @@ def apply_total_command( ) except FeastProviderLoginError as e: print(str(e)) + except FeastError as e: + print(str(e)) + sys.exit(1) @cli.command("teardown", cls=NoOptionDefaultFormat)