From 2beaf6cc778f08faa2fef0c290c5cb4918ae7b29 Mon Sep 17 00:00:00 2001 From: Blake Date: Tue, 25 Feb 2025 12:13:38 -0500 Subject: [PATCH 1/4] allow setting name w/ udf Signed-off-by: Blake --- sdk/python/feast/on_demand_feature_view.py | 4 +- .../tests/unit/test_on_demand_feature_view.py | 66 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/sdk/python/feast/on_demand_feature_view.py b/sdk/python/feast/on_demand_feature_view.py index 0ae87b5e35a..7eb4bab26b7 100644 --- a/sdk/python/feast/on_demand_feature_view.py +++ b/sdk/python/feast/on_demand_feature_view.py @@ -722,6 +722,7 @@ def get_requested_odfvs( def on_demand_feature_view( *, + name: Optional[str] = None, entities: Optional[List[Entity]] = None, schema: list[Field], sources: list[ @@ -742,6 +743,7 @@ def on_demand_feature_view( Creates an OnDemandFeatureView object with the given user function as udf. Args: + name (optional): The name of the on demand feature view. If not provided, the name will be the name of the user function. entities (Optional): The list of names of entities that this feature view is associated with. schema: The list of features in the output of the on demand feature view, after the transformation has been applied. @@ -791,7 +793,7 @@ def decorator(user_function): transformation = SubstraitTransformation.from_ibis(user_function, sources) on_demand_feature_view_obj = OnDemandFeatureView( - name=user_function.__name__, + name=name if name is not None else user_function.__name__, sources=sources, schema=schema, feature_transformation=transformation, diff --git a/sdk/python/tests/unit/test_on_demand_feature_view.py b/sdk/python/tests/unit/test_on_demand_feature_view.py index 4b30bd6be99..8a360d59dcd 100644 --- a/sdk/python/tests/unit/test_on_demand_feature_view.py +++ b/sdk/python/tests/unit/test_on_demand_feature_view.py @@ -24,6 +24,7 @@ OnDemandFeatureView, PandasTransformation, PythonTransformation, + on_demand_feature_view, ) from feast.types import Float32 @@ -356,3 +357,68 @@ def test_on_demand_feature_view_stored_writes(): assert transformed_output["output3"] is not None and isinstance( transformed_output["output3"], datetime.datetime ) + + +def test_function_call_syntax(): + file_source = FileSource(name="my-file-source", path="test.parquet") + feature_view = FeatureView( + name="my-feature-view", + entities=[], + schema=[ + Field(name="feature1", dtype=Float32), + Field(name="feature2", dtype=Float32), + ], + source=file_source, + ) + sources = [feature_view] + + # Test with default name (function name) + def transform_features(features_df: pd.DataFrame) -> pd.DataFrame: + df = pd.DataFrame() + df["output1"] = features_df["feature1"] + df["output2"] = features_df["feature2"] + return df + + odfv = on_demand_feature_view( + sources=sources, + schema=[ + Field(name="output1", dtype=Float32), + Field(name="output2", dtype=Float32), + ], + )(transform_features) + + # Verify default name behavior + assert odfv.name == "transform_features" + assert isinstance(odfv, OnDemandFeatureView) + + proto = odfv.to_proto() + assert proto.spec.name == "transform_features" + + deserialized = OnDemandFeatureView.from_proto(proto) + assert deserialized.name == "transform_features" + + # Test with custom name + def another_transform(features_df: pd.DataFrame) -> pd.DataFrame: + df = pd.DataFrame() + df["output1"] = features_df["feature1"] + df["output2"] = features_df["feature2"] + return df + + odfv_custom = on_demand_feature_view( + name="custom-function-name", + sources=sources, + schema=[ + Field(name="output1", dtype=Float32), + Field(name="output2", dtype=Float32), + ], + )(another_transform) + + # Verify custom name behavior + assert odfv_custom.name == "custom-function-name" + assert isinstance(odfv_custom, OnDemandFeatureView) + + proto = odfv_custom.to_proto() + assert proto.spec.name == "custom-function-name" + + deserialized = OnDemandFeatureView.from_proto(proto) + assert deserialized.name == "custom-function-name" From 92a8a58184587e899eb573ee10b89ffb396ab51f Mon Sep 17 00:00:00 2001 From: Blake Date: Tue, 25 Feb 2025 12:45:58 -0500 Subject: [PATCH 2/4] refactor based on PR review Signed-off-by: Blake --- README.md | 4 ++-- .../tests/unit/test_on_demand_feature_view.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 306b135076e..1052a93c11e 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ The list below contains the functionality that contributors are planning to deve * [x] Python Client * [x] [Python feature server](https://docs.feast.dev/reference/feature-servers/python-feature-server) * [x] [Java feature server (alpha)](https://github.com/feast-dev/feast/blob/master/infra/charts/feast/README.md) - * [x] [Go feature server (alpha)](https://github.com/feast-dev/feast/blob/master/go/README.md) + * [x] [Go feature server (alpha)](https://docs.feast.dev/reference/feature-servers/go-feature-server) * **Data Quality Management (See [RFC](https://docs.google.com/document/d/110F72d4NTv80p35wDSONxhhPBqWRwbZXG4f9mNEMd98/edit))** * [x] Data profiling and validation (Great Expectations) * **Feature Discovery and Governance** @@ -249,4 +249,4 @@ Thanks goes to these incredible people: - + \ No newline at end of file diff --git a/sdk/python/tests/unit/test_on_demand_feature_view.py b/sdk/python/tests/unit/test_on_demand_feature_view.py index 8a360d59dcd..94003bca7f6 100644 --- a/sdk/python/tests/unit/test_on_demand_feature_view.py +++ b/sdk/python/tests/unit/test_on_demand_feature_view.py @@ -28,6 +28,8 @@ ) from feast.types import Float32 +CUSTOM_FUNCTION_NAME = "custom-function-name" + def udf1(features_df: pd.DataFrame) -> pd.DataFrame: df = pd.DataFrame() @@ -388,14 +390,14 @@ def transform_features(features_df: pd.DataFrame) -> pd.DataFrame: )(transform_features) # Verify default name behavior - assert odfv.name == "transform_features" + assert odfv.name == transform_features.__name__ assert isinstance(odfv, OnDemandFeatureView) proto = odfv.to_proto() - assert proto.spec.name == "transform_features" + assert proto.spec.name == transform_features.__name__ deserialized = OnDemandFeatureView.from_proto(proto) - assert deserialized.name == "transform_features" + assert deserialized.name == transform_features.__name__ # Test with custom name def another_transform(features_df: pd.DataFrame) -> pd.DataFrame: @@ -405,7 +407,7 @@ def another_transform(features_df: pd.DataFrame) -> pd.DataFrame: return df odfv_custom = on_demand_feature_view( - name="custom-function-name", + name=CUSTOM_FUNCTION_NAME, sources=sources, schema=[ Field(name="output1", dtype=Float32), @@ -414,11 +416,11 @@ def another_transform(features_df: pd.DataFrame) -> pd.DataFrame: )(another_transform) # Verify custom name behavior - assert odfv_custom.name == "custom-function-name" + assert odfv_custom.name == CUSTOM_FUNCTION_NAME assert isinstance(odfv_custom, OnDemandFeatureView) proto = odfv_custom.to_proto() - assert proto.spec.name == "custom-function-name" + assert proto.spec.name == CUSTOM_FUNCTION_NAME deserialized = OnDemandFeatureView.from_proto(proto) - assert deserialized.name == "custom-function-name" + assert deserialized.name == CUSTOM_FUNCTION_NAME From 61d3b2a3fc53fb0559e458f5807570746b3117ff Mon Sep 17 00:00:00 2001 From: Blake Date: Tue, 25 Feb 2025 12:48:00 -0500 Subject: [PATCH 3/4] removed comments Signed-off-by: Blake --- sdk/python/tests/unit/test_on_demand_feature_view.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sdk/python/tests/unit/test_on_demand_feature_view.py b/sdk/python/tests/unit/test_on_demand_feature_view.py index 94003bca7f6..04de0fa6075 100644 --- a/sdk/python/tests/unit/test_on_demand_feature_view.py +++ b/sdk/python/tests/unit/test_on_demand_feature_view.py @@ -374,7 +374,6 @@ def test_function_call_syntax(): ) sources = [feature_view] - # Test with default name (function name) def transform_features(features_df: pd.DataFrame) -> pd.DataFrame: df = pd.DataFrame() df["output1"] = features_df["feature1"] @@ -389,7 +388,6 @@ def transform_features(features_df: pd.DataFrame) -> pd.DataFrame: ], )(transform_features) - # Verify default name behavior assert odfv.name == transform_features.__name__ assert isinstance(odfv, OnDemandFeatureView) @@ -399,7 +397,6 @@ def transform_features(features_df: pd.DataFrame) -> pd.DataFrame: deserialized = OnDemandFeatureView.from_proto(proto) assert deserialized.name == transform_features.__name__ - # Test with custom name def another_transform(features_df: pd.DataFrame) -> pd.DataFrame: df = pd.DataFrame() df["output1"] = features_df["feature1"] @@ -415,7 +412,6 @@ def another_transform(features_df: pd.DataFrame) -> pd.DataFrame: ], )(another_transform) - # Verify custom name behavior assert odfv_custom.name == CUSTOM_FUNCTION_NAME assert isinstance(odfv_custom, OnDemandFeatureView) From 90fa9e0ef49c1d71ddfe43f32e918c56c128a3df Mon Sep 17 00:00:00 2001 From: Blake Date: Tue, 25 Feb 2025 12:59:13 -0500 Subject: [PATCH 4/4] pr comments Signed-off-by: Blake --- sdk/python/tests/unit/test_on_demand_feature_view.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdk/python/tests/unit/test_on_demand_feature_view.py b/sdk/python/tests/unit/test_on_demand_feature_view.py index 04de0fa6075..69724779cdd 100644 --- a/sdk/python/tests/unit/test_on_demand_feature_view.py +++ b/sdk/python/tests/unit/test_on_demand_feature_view.py @@ -28,8 +28,6 @@ ) from feast.types import Float32 -CUSTOM_FUNCTION_NAME = "custom-function-name" - def udf1(features_df: pd.DataFrame) -> pd.DataFrame: df = pd.DataFrame() @@ -362,6 +360,7 @@ def test_on_demand_feature_view_stored_writes(): def test_function_call_syntax(): + CUSTOM_FUNCTION_NAME = "custom-function-name" file_source = FileSource(name="my-file-source", path="test.parquet") feature_view = FeatureView( name="my-feature-view",