From 5ca8ea51b4206fba8f0795465b83fe9e7088031f Mon Sep 17 00:00:00 2001 From: CakeHub Developer Date: Wed, 1 Apr 2026 14:00:22 +0500 Subject: [PATCH 1/2] fix: Scope feature view name conflict checks by project in file registry The file-based registry was incorrectly checking for feature view name conflicts across all projects in a shared registry, causing false positives when different projects used the same feature view names. This fix replaces the custom conflict checking logic with the base class method _ensure_feature_view_name_is_unique, which properly scopes checks by project. This aligns the file registry behavior with the SQL registry implementation. Changes: - Use _ensure_feature_view_name_is_unique from BaseRegistry (same as SQL registry) - Remove _check_conflicting_feature_view_names and _existing_feature_view_names_to_fvs methods - Add regression test for cross-project feature view names Fixes #6209 --- sdk/python/feast/infra/registry/registry.py | 22 +------- .../registration/test_universal_registry.py | 55 +++++++++++++++++++ 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/sdk/python/feast/infra/registry/registry.py b/sdk/python/feast/infra/registry/registry.py index 76da6ad831d..1c05d2ed152 100644 --- a/sdk/python/feast/infra/registry/registry.py +++ b/sdk/python/feast/infra/registry/registry.py @@ -755,7 +755,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._ensure_feature_view_name_is_unique(feature_view, project) existing_feature_views_of_same_type: RepeatedCompositeFieldContainer if isinstance(feature_view, StreamFeatureView): existing_feature_views_of_same_type = ( @@ -1351,26 +1351,6 @@ 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() - 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]: - assert self.cached_registry_proto - odfvs = { - fv.spec.name: fv - for fv in self.cached_registry_proto.on_demand_feature_views - } - 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 - } - return {**odfvs, **fvs, **sfv} - def get_permission( self, name: str, project: str, allow_cache: bool = False ) -> Permission: diff --git a/sdk/python/tests/integration/registration/test_universal_registry.py b/sdk/python/tests/integration/registration/test_universal_registry.py index fb09395d789..0b3e9d85c37 100644 --- a/sdk/python/tests/integration/registration/test_universal_registry.py +++ b/sdk/python/tests/integration/registration/test_universal_registry.py @@ -2192,3 +2192,58 @@ def shared_odfv_name(inputs: pd.DataFrame) -> pd.DataFrame: # Cleanup test_registry.delete_feature_view("shared_odfv_name", project) test_registry.teardown() + + +@pytest.mark.parametrize( + "test_registry", + all_fixtures, +) +def test_cross_project_feature_view_name_allowed(test_registry: BaseRegistry): + """ + Test that different projects can use the same feature view names. + This is a regression test for issue #6209. + """ + project_a = "project_a" + project_b = "project_b" + + # Create a FeatureView in project A + feature_view_a = FeatureView( + name="shared_name", + entities=[], + schema=[Field(name="feature1", dtype=Float32)], + source=FileSource(path="data.parquet"), + ) + + # Create a StreamFeatureView with the same name in project B + stream_feature_view_b = StreamFeatureView( + name="shared_name", + entities=[], + schema=[Field(name="feature2", dtype=Float32)], + source=KafkaSource( + name="kafka_source", + kafka_bootstrap_servers="localhost:9092", + topic="test_topic", + timestamp_field="event_timestamp", + batch_source=FileSource(path="stream_data.parquet"), + ), + aggregations=[], + ) + + # Both should apply successfully without ConflictingFeatureViewNames error + test_registry.apply_feature_view(feature_view_a, project_a) + test_registry.apply_feature_view(stream_feature_view_b, project_b) + + # Verify both exist in their respective projects + retrieved_fv_a = test_registry.get_feature_view("shared_name", project_a) + assert retrieved_fv_a.name == "shared_name" + assert isinstance(retrieved_fv_a, FeatureView) + assert not isinstance(retrieved_fv_a, StreamFeatureView) + + retrieved_sfv_b = test_registry.get_stream_feature_view("shared_name", project_b) + assert retrieved_sfv_b.name == "shared_name" + assert isinstance(retrieved_sfv_b, StreamFeatureView) + + # Cleanup + test_registry.delete_feature_view("shared_name", project_a) + test_registry.delete_feature_view("shared_name", project_b) + test_registry.teardown() From 8f74065bc01dcee4d2ab6a507425d7452a8c8899 Mon Sep 17 00:00:00 2001 From: CakeHub Developer Date: Wed, 1 Apr 2026 14:37:59 +0500 Subject: [PATCH 2/2] Fix cache overwrite during batch feature view apply Pass allow_cache=True to _ensure_feature_view_name_is_unique to prevent cache overwrites when applying multiple feature views with commit=False. The issue occurred because _ensure_feature_view_name_is_unique calls getter methods with allow_cache=False by default, which triggers _get_registry_proto to re-read from the store and overwrite cached_registry_proto, discarding uncommitted in-memory changes. This was triggered in feature_store.py where apply_feature_view is called in a loop with commit=False after other uncommitted apply_project and apply_data_source calls. Each uniqueness check would overwrite the cache, losing prior uncommitted changes. Since _prepare_registry_for_changes already prepares the cache at line 755, using allow_cache=True is safe and preserves uncommitted state during batch operations. --- sdk/python/feast/infra/registry/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/python/feast/infra/registry/registry.py b/sdk/python/feast/infra/registry/registry.py index 1c05d2ed152..42539b2e81d 100644 --- a/sdk/python/feast/infra/registry/registry.py +++ b/sdk/python/feast/infra/registry/registry.py @@ -755,7 +755,7 @@ def apply_feature_view( self._prepare_registry_for_changes(project) assert self.cached_registry_proto - self._ensure_feature_view_name_is_unique(feature_view, project) + self._ensure_feature_view_name_is_unique(feature_view, project, allow_cache=True) existing_feature_views_of_same_type: RepeatedCompositeFieldContainer if isinstance(feature_view, StreamFeatureView): existing_feature_views_of_same_type = (