Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
fix: scope feature view name conflict check to current project in fil…
…e-based registry

_check_conflicting_feature_view_names built its lookup map from all
feature views in cached_registry_proto without filtering by project.
In a shared file-based registry with multiple projects, this caused
false ConflictingFeatureViewNames errors when two different projects
defined feature views with the same name.

Add a project parameter to both _check_conflicting_feature_view_names
and _existing_feature_view_names_to_fvs, and filter each collection
by fv.spec.project == project so only same-project views are compared.

Fixes #6209

Signed-off-by: Abhishek8108 <87538407+Abhishek8108@users.noreply.github.com>
  • Loading branch information
Abhishek8108 authored and ntkathole committed May 6, 2026
commit a7ff73862c46604d0e7149f8f2baf61bc13e74b5
21 changes: 15 additions & 6 deletions sdk/python/feast/infra/registry/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ def apply_feature_view(
self._prepare_registry_for_changes(project)
assert self.cached_registry_proto

self._check_conflicting_feature_view_names(feature_view)
self._check_conflicting_feature_view_names(feature_view, project)
existing_feature_views_of_same_type: RepeatedCompositeFieldContainer
if isinstance(feature_view, StreamFeatureView):
existing_feature_views_of_same_type = (
Expand Down Expand Up @@ -1360,23 +1360,32 @@ def _get_registry_proto(

return registry_proto

def _check_conflicting_feature_view_names(self, feature_view: BaseFeatureView):
name_to_fv_protos = self._existing_feature_view_names_to_fvs()
def _check_conflicting_feature_view_names(
self, feature_view: BaseFeatureView, project: str
):
name_to_fv_protos = self._existing_feature_view_names_to_fvs(project)
if feature_view.name in name_to_fv_protos:
if not isinstance(
name_to_fv_protos.get(feature_view.name), feature_view.proto_class
):
raise ConflictingFeatureViewNames(feature_view.name)

def _existing_feature_view_names_to_fvs(self) -> Dict[str, Message]:
def _existing_feature_view_names_to_fvs(self, project: str) -> Dict[str, Message]:
assert self.cached_registry_proto
odfvs = {
fv.spec.name: fv
for fv in self.cached_registry_proto.on_demand_feature_views
if fv.spec.project == project
}
fvs = {
fv.spec.name: fv
for fv in self.cached_registry_proto.feature_views
if fv.spec.project == project
}
fvs = {fv.spec.name: fv for fv in self.cached_registry_proto.feature_views}
sfv = {
fv.spec.name: fv for fv in self.cached_registry_proto.stream_feature_views
fv.spec.name: fv
for fv in self.cached_registry_proto.stream_feature_views
if fv.spec.project == project
}
return {**odfvs, **fvs, **sfv}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,53 @@ def test_apply_conflicting_feature_view_names(feature_store_with_local_registry)
feature_store_with_local_registry.teardown()


def test_cross_project_feature_view_names_do_not_conflict():
"""Feature views with the same name in different projects must not raise ConflictingFeatureViewNames."""
fd, registry_path = mkstemp()
fd, online_store_path = mkstemp()

def make_store(project: str) -> FeatureStore:
return FeatureStore(
config=RepoConfig(
registry=registry_path,
project=project,
provider="local",
online_store=SqliteOnlineStoreConfig(path=online_store_path),
entity_key_serialization_version=3,
)
)

store_a = make_store("project_a")
store_b = make_store("project_b")

entity = Entity(name="driver", join_keys=["driver_id"])
source = FileSource(path="driver_stats.parquet")

fv_a = FeatureView(
name="driver_stats",
entities=[entity],
schema=[Field(name="driver_id", dtype=Int64)],
ttl=timedelta(seconds=10),
online=False,
source=source,
)
store_a.apply([entity, fv_a])

fv_b = FeatureView(
name="driver_stats",
entities=[entity],
schema=[Field(name="driver_id", dtype=Int64)],
ttl=timedelta(seconds=10),
online=False,
source=source,
)
# Must not raise ConflictingFeatureViewNames — same name but different project.
store_b.apply([entity, fv_b])

store_a.teardown()
store_b.teardown()


@pytest.mark.parametrize(
"test_feature_store",
[lazy_fixture("feature_store_with_local_registry")],
Expand Down