Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion sdk/python/feast/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
28 changes: 28 additions & 0 deletions sdk/python/feast/repo_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Comment on lines +230 to +233
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor skip-feature-view-validation during early parsing

When feast plan/apply --skip-feature-view-validation is used, _get_repo_contents() still calls parse_repo() before the flag reaches FeatureStore.plan()/apply(), so this unconditional duplicate feature-view check now fails even though those methods explicitly skip _validate_all_feature_views under that option. Repos relying on the documented escape hatch for overly strict feature-view validation will no longer be able to run the command; pass the skip flag into parsing or keep this validation on the existing gated path.

Useful? React with 👍 / 👎.

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

Expand Down
Loading