Skip to content

Commit 87c194c

Browse files
authored
fix: Infer features for feature services when they depend on feature views without schemas (#2653)
* fix: Infer features for feature services when they depend on feature views that have no schema Signed-off-by: Danny Chiao <[email protected]> * fix Signed-off-by: Danny Chiao <[email protected]> * lint Signed-off-by: Danny Chiao <[email protected]>
1 parent 30e0bf3 commit 87c194c

File tree

3 files changed

+58
-12
lines changed

3 files changed

+58
-12
lines changed

sdk/python/feast/feature_service.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class FeatureService:
3939
"""
4040

4141
name: str
42+
_features: List[Union[FeatureView, OnDemandFeatureView]]
4243
feature_view_projections: List[FeatureViewProjection]
4344
description: str
4445
tags: Dict[str, str]
@@ -93,24 +94,36 @@ def __init__(
9394
_features = []
9495

9596
self.name = _name
97+
self._features = _features
9698
self.feature_view_projections = []
99+
self.description = description
100+
self.tags = tags or {}
101+
self.owner = owner
102+
self.created_timestamp = None
103+
self.last_updated_timestamp = None
104+
self.logging_config = logging_config
105+
self.infer_features()
97106

98-
for feature_grouping in _features:
107+
def infer_features(self, fvs_to_update: Optional[Dict[str, FeatureView]] = None):
108+
self.feature_view_projections = []
109+
for feature_grouping in self._features:
99110
if isinstance(feature_grouping, BaseFeatureView):
111+
# For feature services that depend on an unspecified feature view, apply inferred schema
112+
if (
113+
fvs_to_update is not None
114+
and len(feature_grouping.projection.features) == 0
115+
and feature_grouping.name in fvs_to_update
116+
):
117+
feature_grouping.projection.features = fvs_to_update[
118+
feature_grouping.name
119+
].features
100120
self.feature_view_projections.append(feature_grouping.projection)
101121
else:
102122
raise ValueError(
103-
f"The feature service {name} has been provided with an invalid type "
123+
f"The feature service {self.name} has been provided with an invalid type "
104124
f'{type(feature_grouping)} as part of the "features" argument.)'
105125
)
106126

107-
self.description = description
108-
self.tags = tags or {}
109-
self.owner = owner
110-
self.created_timestamp = None
111-
self.last_updated_timestamp = None
112-
self.logging_config = logging_config
113-
114127
def __repr__(self):
115128
items = (f"{k} = {v}" for k, v in self.__dict__.items())
116129
return f"<{self.__class__.__name__}({', '.join(items)})>"
@@ -119,7 +132,7 @@ def __str__(self):
119132
return str(MessageToJson(self.to_proto()))
120133

121134
def __hash__(self):
122-
return hash((self.name))
135+
return hash(self.name)
123136

124137
def __eq__(self, other):
125138
if not isinstance(other, FeatureService):

sdk/python/feast/feature_store.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,9 @@ def _make_inferences(
475475
entities_to_update: List[Entity],
476476
views_to_update: List[FeatureView],
477477
odfvs_to_update: List[OnDemandFeatureView],
478+
feature_services_to_update: List[FeatureService],
478479
):
479-
"""Makes inferences for entities, feature views, and odfvs."""
480+
"""Makes inferences for entities, feature views, odfvs, and feature services."""
480481
update_entities_with_inferred_types_from_feature_views(
481482
entities_to_update, views_to_update, self.config
482483
)
@@ -498,6 +499,10 @@ def _make_inferences(
498499
for odfv in odfvs_to_update:
499500
odfv.infer_features()
500501

502+
fvs_to_update_map = {view.name: view for view in views_to_update}
503+
for feature_service in feature_services_to_update:
504+
feature_service.infer_features(fvs_to_update=fvs_to_update_map)
505+
501506
@log_exceptions_and_usage
502507
def _plan(
503508
self, desired_repo_contents: RepoContents
@@ -553,6 +558,7 @@ def _plan(
553558
desired_repo_contents.entities,
554559
desired_repo_contents.feature_views,
555560
desired_repo_contents.on_demand_feature_views,
561+
desired_repo_contents.feature_services,
556562
)
557563

558564
# Compute the desired difference between the current objects in the registry and
@@ -692,7 +698,11 @@ def apply(
692698
views_to_update, odfvs_to_update, request_views_to_update
693699
)
694700
self._make_inferences(
695-
data_sources_to_update, entities_to_update, views_to_update, odfvs_to_update
701+
data_sources_to_update,
702+
entities_to_update,
703+
views_to_update,
704+
odfvs_to_update,
705+
services_to_update,
696706
)
697707

698708
# Handle all entityless feature views by using DUMMY_ENTITY as a placeholder entity.

sdk/python/tests/integration/registration/test_inference.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
BigQuerySource,
88
Entity,
99
Feature,
10+
FeatureService,
1011
FileSource,
1112
RedshiftSource,
1213
RepoConfig,
@@ -332,3 +333,25 @@ def test_update_feature_views_with_inferred_features():
332333
)
333334
assert len(feature_view_2.schema) == 1
334335
assert len(feature_view_2.features) == 1
336+
337+
338+
def test_update_feature_services_with_inferred_features(simple_dataset_1):
339+
with prep_file_source(df=simple_dataset_1, timestamp_field="ts_1") as file_source:
340+
entity1 = Entity(name="test1", join_keys=["id_join_key"])
341+
feature_view_1 = FeatureView(
342+
name="test1", entities=[entity1], source=file_source,
343+
)
344+
feature_service = FeatureService(name="fs_1", features=[feature_view_1])
345+
assert len(feature_service.feature_view_projections) == 1
346+
assert len(feature_service.feature_view_projections[0].features) == 0
347+
348+
update_feature_views_with_inferred_features(
349+
[feature_view_1], [entity1], RepoConfig(provider="local", project="test")
350+
)
351+
feature_service.infer_features(
352+
fvs_to_update={feature_view_1.name: feature_view_1}
353+
)
354+
355+
assert len(feature_view_1.schema) == 3
356+
assert len(feature_view_1.features) == 3
357+
assert len(feature_service.feature_view_projections[0].features) == 3

0 commit comments

Comments
 (0)