Skip to content

Commit 160d7b7

Browse files
authored
fix: Enforce kw args featureservice (#2575)
* Fix Signed-off-by: Kevin Zhang <[email protected]> * Fix lint Signed-off-by: Kevin Zhang <[email protected]> * Fix Signed-off-by: Kevin Zhang <[email protected]> * Fix lint Signed-off-by: Kevin Zhang <[email protected]> * Add tests Signed-off-by: Kevin Zhang <[email protected]> * Fix lint Signed-off-by: Kevin Zhang <[email protected]> * Fix Signed-off-by: Kevin Zhang <[email protected]> * Fix lint Signed-off-by: Kevin Zhang <[email protected]>
1 parent 0b7ec53 commit 160d7b7

File tree

2 files changed

+82
-4
lines changed

2 files changed

+82
-4
lines changed

sdk/python/feast/feature_service.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import warnings
12
from datetime import datetime
23
from typing import Dict, List, Optional, Union
34

@@ -47,8 +48,9 @@ class FeatureService:
4748
@log_exceptions
4849
def __init__(
4950
self,
50-
name: str,
51-
features: List[Union[FeatureView, OnDemandFeatureView]],
51+
*args,
52+
name: Optional[str] = None,
53+
features: Optional[List[Union[FeatureView, OnDemandFeatureView]]] = None,
5254
tags: Dict[str, str] = None,
5355
description: str = "",
5456
owner: str = "",
@@ -59,10 +61,38 @@ def __init__(
5961
Raises:
6062
ValueError: If one of the specified features is not a valid type.
6163
"""
62-
self.name = name
64+
positional_attributes = ["name", "features"]
65+
_name = name
66+
_features = features
67+
if args:
68+
warnings.warn(
69+
(
70+
"Feature service parameters should be specified as a keyword argument instead of a positional arg."
71+
"Feast 0.23+ will not support positional arguments to construct feature service"
72+
),
73+
DeprecationWarning,
74+
)
75+
if len(args) > len(positional_attributes):
76+
raise ValueError(
77+
f"Only {', '.join(positional_attributes)} are allowed as positional args when defining "
78+
f"feature service, for backwards compatibility."
79+
)
80+
if len(args) >= 1:
81+
_name = args[0]
82+
if len(args) >= 2:
83+
_features = args[1]
84+
85+
if not _name:
86+
raise ValueError("Feature service name needs to be specified")
87+
88+
if not _features:
89+
# Technically, legal to create feature service with no feature views before.
90+
_features = []
91+
92+
self.name = _name
6393
self.feature_view_projections = []
6494

65-
for feature_grouping in features:
95+
for feature_grouping in _features:
6696
if isinstance(feature_grouping, BaseFeatureView):
6797
self.feature_view_projections.append(feature_grouping.projection)
6898
else:

sdk/python/tests/unit/test_feature_service.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
from feast.feature_service import FeatureService
24
from feast.feature_view import FeatureView
35
from feast.field import Field
@@ -55,3 +57,49 @@ def test_hash():
5557

5658
s4 = {feature_service_1, feature_service_2, feature_service_3, feature_service_4}
5759
assert len(s4) == 3
60+
61+
62+
def test_feature_view_kw_args_warning():
63+
with pytest.warns(DeprecationWarning):
64+
service = FeatureService("name", [], tags={"tag_1": "tag"}, description="desc")
65+
assert service.name == "name"
66+
assert service.tags == {"tag_1": "tag"}
67+
assert service.description == "desc"
68+
69+
# More positional args than name and features
70+
with pytest.raises(ValueError):
71+
service = FeatureService("name", [], {"tag_1": "tag"}, "desc")
72+
73+
# No name defined.
74+
with pytest.raises(ValueError):
75+
service = FeatureService(features=[], tags={"tag_1": "tag"}, description="desc")
76+
77+
78+
def no_warnings(func):
79+
def wrapper_no_warnings(*args, **kwargs):
80+
with pytest.warns(None) as warnings:
81+
func(*args, **kwargs)
82+
83+
if len(warnings) > 0:
84+
raise AssertionError(
85+
"Warnings were raised: " + ", ".join([str(w) for w in warnings])
86+
)
87+
88+
return wrapper_no_warnings
89+
90+
91+
@no_warnings
92+
def test_feature_view_kw_args_normal():
93+
file_source = FileSource(name="my-file-source", path="test.parquet")
94+
feature_view = FeatureView(
95+
name="my-feature-view",
96+
entities=[],
97+
schema=[
98+
Field(name="feature1", dtype=Float32),
99+
Field(name="feature2", dtype=Float32),
100+
],
101+
source=file_source,
102+
)
103+
_ = FeatureService(
104+
name="my-feature-service", features=[feature_view[["feature1", "feature2"]]]
105+
)

0 commit comments

Comments
 (0)