Skip to content

Commit e9c4c68

Browse files
fix: Address Devin review feedback on versioning
- Fix version-qualified features dropped with full_feature_names=True: use _parse_feature_ref to build clean requested_result_row_names - Fix retrieve_online_documents breaking with @vn refs: use _parse_feature_ref instead of split(":") for FV name extraction - Fix metadata-only updates not committed: add self.commit() after _update_metadata_fields in file registry - Fix ODFV transforms broken by version-qualified refs: use _parse_feature_ref in _augment_response_with_on_demand_transforms - Fix _update_metadata_fields not updating spec.version: add version field update so pinned-to-latest transitions persist - Fix _resolve_feature_counts inflating FV count: strip @vn from feature view names in metrics - Fix version snapshots storing stale current_version_number: set version number before serializing snapshot in both file and SQL registries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fd776fc commit e9c4c68

File tree

5 files changed

+45
-32
lines changed

5 files changed

+45
-32
lines changed

sdk/python/feast/feature_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def _resolve_feature_counts(
138138
feat_count = sum(len(p.features) for p in projections)
139139
elif isinstance(features, list):
140140
feat_count = len(features)
141-
fv_names = {ref.split(":")[0] for ref in features if ":" in ref}
141+
fv_names = {ref.split(":")[0].split("@")[0] for ref in features if ":" in ref}
142142
fv_count = len(fv_names)
143143
else:
144144
feat_count = 0

sdk/python/feast/feature_store.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2614,13 +2614,15 @@ def retrieve_online_documents(
26142614
)
26152615
feature_view_set = set()
26162616
for _feature in features:
2617-
feature_view_name = _feature.split(":")[0]
2618-
feature_view = self.get_feature_view(feature_view_name)
2617+
fv_name, _, _ = utils._parse_feature_ref(_feature)
2618+
feature_view = self.get_feature_view(fv_name)
26192619
feature_view_set.add(feature_view.name)
26202620
if len(feature_view_set) > 1:
26212621
raise ValueError("Document retrieval only supports a single feature view.")
26222622
requested_features = [
2623-
f.split(":")[1] for f in features if isinstance(f, str) and ":" in f
2623+
utils._parse_feature_ref(f)[2]
2624+
for f in features
2625+
if isinstance(f, str) and ":" in f
26242626
]
26252627
requested_feature_view_name = list(feature_view_set)[0]
26262628
for feature_view in available_feature_views:
@@ -2817,18 +2819,20 @@ def retrieve_online_documents_v2(
28172819
)
28182820
feature_view_set = set()
28192821
for feature in features:
2820-
feature_view_name = feature.split(":")[0]
2821-
if feature_view_name in [fv.name for fv in available_odfv_views]:
2822+
fv_name, _, _ = utils._parse_feature_ref(feature)
2823+
if fv_name in [fv.name for fv in available_odfv_views]:
28222824
feature_view: Union[OnDemandFeatureView, FeatureView] = (
2823-
self.get_on_demand_feature_view(feature_view_name)
2825+
self.get_on_demand_feature_view(fv_name)
28242826
)
28252827
else:
2826-
feature_view = self.get_feature_view(feature_view_name)
2828+
feature_view = self.get_feature_view(fv_name)
28272829
feature_view_set.add(feature_view.name)
28282830
if len(feature_view_set) > 1:
28292831
raise ValueError("Document retrieval only supports a single feature view.")
28302832
requested_features = [
2831-
f.split(":")[1] for f in features if isinstance(f, str) and ":" in f
2833+
utils._parse_feature_ref(f)[2]
2834+
for f in features
2835+
if isinstance(f, str) and ":" in f
28322836
]
28332837
if len(available_feature_views) == 0:
28342838
available_feature_views.extend(available_odfv_views) # type: ignore[arg-type]

sdk/python/feast/infra/registry/registry.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,8 @@ def _update_metadata_fields(
541541
existing_proto.spec.tags.clear()
542542
existing_proto.spec.tags.update(updated_fv.tags)
543543
existing_proto.spec.owner = updated_fv.owner
544+
if hasattr(existing_proto.spec, "version") and hasattr(updated_fv, "version"):
545+
existing_proto.spec.version = getattr(updated_fv, "version")
544546

545547
# Configuration fields (FeatureView)
546548
if (
@@ -747,6 +749,8 @@ def apply_feature_view(
747749
self._update_metadata_fields(
748750
existing_feature_view_proto, feature_view
749751
)
752+
if commit:
753+
self.commit()
750754
return
751755
else:
752756
old_proto_bytes = existing_feature_view_proto.SerializeToString()
@@ -767,7 +771,6 @@ def apply_feature_view(
767771

768772
# Version history tracking
769773
if is_latest:
770-
new_proto_bytes = feature_view_proto.SerializeToString()
771774
if old_proto_bytes is not None:
772775
# FV changed: save old as a version if first time, then save new
773776
next_ver = self._next_version_number(feature_view.name, project)
@@ -776,20 +779,22 @@ def apply_feature_view(
776779
feature_view.name, project, 0, fv_type_str, old_proto_bytes
777780
)
778781
next_ver = 1
779-
self._save_version_record(
780-
feature_view.name, project, next_ver, fv_type_str, new_proto_bytes
781-
)
782782
feature_view.current_version_number = next_ver
783783
feature_view_proto = feature_view.to_proto()
784784
feature_view_proto.spec.project = project
785-
else:
786-
# New FV: save as v0
785+
new_proto_bytes = feature_view_proto.SerializeToString()
787786
self._save_version_record(
788-
feature_view.name, project, 0, fv_type_str, new_proto_bytes
787+
feature_view.name, project, next_ver, fv_type_str, new_proto_bytes
789788
)
789+
else:
790+
# New FV: save as v0
790791
feature_view.current_version_number = 0
791792
feature_view_proto = feature_view.to_proto()
792793
feature_view_proto.spec.project = project
794+
new_proto_bytes = feature_view_proto.SerializeToString()
795+
self._save_version_record(
796+
feature_view.name, project, 0, fv_type_str, new_proto_bytes
797+
)
793798

794799
existing_feature_views_of_same_type.append(feature_view_proto)
795800
if commit:

sdk/python/feast/infra/registry/sql.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -727,16 +727,18 @@ def apply_feature_view(
727727
)
728728
next_ver = 1
729729

730-
# Save new as next version
730+
# Update current_version_number before saving snapshot
731+
feature_view.current_version_number = next_ver
732+
snapshot_proto_bytes = feature_view.to_proto().SerializeToString()
733+
734+
# Save new as next version (with correct current_version_number)
731735
self._save_version_snapshot(
732736
feature_view.name,
733737
project,
734738
next_ver,
735739
fv_type_str,
736-
new_proto_bytes,
740+
snapshot_proto_bytes,
737741
)
738-
# Update current_version_number on the active FV
739-
feature_view.current_version_number = next_ver
740742
# Re-serialize with updated version number
741743
with self.write_engine.begin() as conn:
742744
update_stmt = (
@@ -746,20 +748,21 @@ def apply_feature_view(
746748
fv_table.c.project_id == project,
747749
)
748750
.values(
749-
feature_view_proto=feature_view.to_proto().SerializeToString(),
751+
feature_view_proto=snapshot_proto_bytes,
750752
)
751753
)
752754
conn.execute(update_stmt)
753755
else:
754756
# New FV: save as v0
757+
feature_view.current_version_number = 0
758+
snapshot_proto_bytes = feature_view.to_proto().SerializeToString()
755759
self._save_version_snapshot(
756760
feature_view.name,
757761
project,
758762
0,
759763
fv_type_str,
760-
new_proto_bytes,
764+
snapshot_proto_bytes,
761765
)
762-
feature_view.current_version_number = 0
763766
with self.write_engine.begin() as conn:
764767
update_stmt = (
765768
update(fv_table)
@@ -768,7 +771,7 @@ def apply_feature_view(
768771
fv_table.c.project_id == project,
769772
)
770773
.values(
771-
feature_view_proto=feature_view.to_proto().SerializeToString(),
774+
feature_view_proto=snapshot_proto_bytes,
772775
)
773776
)
774777
conn.execute(update_stmt)

sdk/python/feast/utils.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,7 @@ def _augment_response_with_on_demand_transforms(
743743

744744
odfv_feature_refs = defaultdict(list)
745745
for feature_ref in feature_refs:
746-
view_name, feature_name = feature_ref.split(":")
746+
view_name, _, feature_name = _parse_feature_ref(feature_ref)
747747
if view_name in requested_odfv_feature_names:
748748
odfv_feature_refs[view_name].append(
749749
f"{requested_odfv_map[view_name].projection.name_to_use()}__{feature_name}"
@@ -1390,13 +1390,14 @@ def _get_online_request_context(
13901390
requested_on_demand_feature_views,
13911391
)
13921392

1393-
requested_result_row_names = {
1394-
feat_ref.replace(":", "__") for feat_ref in _feature_refs
1395-
}
1396-
if not full_feature_names:
1397-
requested_result_row_names = {
1398-
name.rpartition("__")[-1] for name in requested_result_row_names
1399-
}
1393+
# Build expected result names using clean FV names (without @vN syntax)
1394+
requested_result_row_names = set()
1395+
for feat_ref in _feature_refs:
1396+
fv_name, _, feature_name = _parse_feature_ref(feat_ref)
1397+
if full_feature_names:
1398+
requested_result_row_names.add(f"{fv_name}__{feature_name}")
1399+
else:
1400+
requested_result_row_names.add(feature_name)
14001401

14011402
feature_views = list(view for view, _ in grouped_refs)
14021403

0 commit comments

Comments
 (0)