Skip to content

Commit bfae6ac

Browse files
authored
fix: Fix on demand feature view output in feast plan + Web UI crash (feast-dev#3057)
* fix: Fix on demand feature view output in feast plan + Web UI crash with ODFV Signed-off-by: Danny Chiao <[email protected]> * lint Signed-off-by: Danny Chiao <[email protected]> * fix tests Signed-off-by: Danny Chiao <[email protected]> Signed-off-by: Danny Chiao <[email protected]>
1 parent e169729 commit bfae6ac

File tree

6 files changed

+53
-14
lines changed

6 files changed

+53
-14
lines changed

protos/feast/core/OnDemandFeatureView.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,7 @@ message UserDefinedFunction {
8383

8484
// The python-syntax function body (serialized by dill)
8585
bytes body = 2;
86+
87+
// The string representation of the udf
88+
string body_text = 3;
8689
}

sdk/python/feast/diff/registry_diff.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from feast.protos.feast.core.OnDemandFeatureView_pb2 import (
1818
OnDemandFeatureView as OnDemandFeatureViewProto,
1919
)
20+
from feast.protos.feast.core.OnDemandFeatureView_pb2 import OnDemandFeatureViewSpec
2021
from feast.protos.feast.core.RequestFeatureView_pb2 import (
2122
RequestFeatureView as RequestFeatureViewProto,
2223
)
@@ -137,19 +138,39 @@ def diff_registry_objects(
137138
else:
138139
current_spec = current_proto.spec
139140
new_spec = new_proto.spec
140-
if current_spec != new_spec:
141+
if current != new:
141142
for _field in current_spec.DESCRIPTOR.fields:
142143
if _field.name in FIELDS_TO_IGNORE:
143144
continue
144-
if getattr(current_spec, _field.name) != getattr(new_spec, _field.name):
145-
transition = TransitionType.UPDATE
146-
property_diffs.append(
147-
PropertyDiff(
148-
_field.name,
149-
getattr(current_spec, _field.name),
150-
getattr(new_spec, _field.name),
145+
elif getattr(current_spec, _field.name) != getattr(new_spec, _field.name):
146+
if _field.name == "user_defined_function":
147+
current_spec = cast(OnDemandFeatureViewSpec, current_proto)
148+
new_spec = cast(OnDemandFeatureViewSpec, new_proto)
149+
current_udf = current_spec.user_defined_function
150+
new_udf = new_spec.user_defined_function
151+
for _udf_field in current_udf.DESCRIPTOR.fields:
152+
if _udf_field.name == "body":
153+
continue
154+
if getattr(current_udf, _udf_field.name) != getattr(
155+
new_udf, _udf_field.name
156+
):
157+
transition = TransitionType.UPDATE
158+
property_diffs.append(
159+
PropertyDiff(
160+
_field.name + "." + _udf_field.name,
161+
getattr(current_udf, _udf_field.name),
162+
getattr(new_udf, _udf_field.name),
163+
)
164+
)
165+
else:
166+
transition = TransitionType.UPDATE
167+
property_diffs.append(
168+
PropertyDiff(
169+
_field.name,
170+
getattr(current_spec, _field.name),
171+
getattr(new_spec, _field.name),
172+
)
151173
)
152-
)
153174
return FeastObjectDiff(
154175
name=new_spec.name,
155176
feast_object_type=object_type,

sdk/python/feast/on_demand_feature_view.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ class OnDemandFeatureView(BaseFeatureView):
5959
maintainer.
6060
"""
6161

62-
# TODO(adchia): remove inputs from proto and declaration
6362
name: str
6463
features: List[Field]
6564
source_feature_view_projections: Dict[str, FeatureViewProjection]
6665
source_request_sources: Dict[str, RequestSource]
6766
udf: FunctionType
67+
udf_string: str
6868
description: str
6969
tags: Dict[str, str]
7070
owner: str
@@ -83,6 +83,7 @@ def __init__( # noqa: C901
8383
]
8484
],
8585
udf: FunctionType,
86+
udf_string: str = "",
8687
description: str = "",
8788
tags: Optional[Dict[str, str]] = None,
8889
owner: str = "",
@@ -99,6 +100,7 @@ def __init__( # noqa: C901
99100
which will refer to them by name.
100101
udf: The user defined transformation function, which must take pandas
101102
dataframes as inputs.
103+
udf_string: The source code version of the udf (for diffing and displaying in Web UI)
102104
description (optional): A human-readable description.
103105
tags (optional): A dictionary of key-value pairs to store arbitrary metadata.
104106
owner (optional): The owner of the on demand feature view, typically the email
@@ -125,6 +127,7 @@ def __init__( # noqa: C901
125127
] = odfv_source.projection
126128

127129
self.udf = udf # type: ignore
130+
self.udf_string = udf_string
128131

129132
@property
130133
def proto_class(self) -> Type[OnDemandFeatureViewProto]:
@@ -137,6 +140,7 @@ def __copy__(self):
137140
sources=list(self.source_feature_view_projections.values())
138141
+ list(self.source_request_sources.values()),
139142
udf=self.udf,
143+
udf_string=self.udf_string,
140144
description=self.description,
141145
tags=self.tags,
142146
owner=self.owner,
@@ -157,6 +161,7 @@ def __eq__(self, other):
157161
self.source_feature_view_projections
158162
!= other.source_feature_view_projections
159163
or self.source_request_sources != other.source_request_sources
164+
or self.udf_string != other.udf_string
160165
or self.udf.__code__.co_code != other.udf.__code__.co_code
161166
):
162167
return False
@@ -198,6 +203,7 @@ def to_proto(self) -> OnDemandFeatureViewProto:
198203
user_defined_function=UserDefinedFunctionProto(
199204
name=self.udf.__name__,
200205
body=dill.dumps(self.udf, recurse=True),
206+
body_text=self.udf_string,
201207
),
202208
description=self.description,
203209
tags=self.tags,
@@ -250,6 +256,7 @@ def from_proto(cls, on_demand_feature_view_proto: OnDemandFeatureViewProto):
250256
udf=dill.loads(
251257
on_demand_feature_view_proto.spec.user_defined_function.body
252258
),
259+
udf_string=on_demand_feature_view_proto.spec.user_defined_function.body_text,
253260
description=on_demand_feature_view_proto.spec.description,
254261
tags=dict(on_demand_feature_view_proto.spec.tags),
255262
owner=on_demand_feature_view_proto.spec.owner,
@@ -438,6 +445,7 @@ def mainify(obj):
438445
obj.__module__ = "__main__"
439446

440447
def decorator(user_function):
448+
udf_string = dill.source.getsource(user_function)
441449
mainify(user_function)
442450
on_demand_feature_view_obj = OnDemandFeatureView(
443451
name=user_function.__name__,
@@ -447,6 +455,7 @@ def decorator(user_function):
447455
description=description,
448456
tags=tags,
449457
owner=owner,
458+
udf_string=udf_string,
450459
)
451460
functools.update_wrapper(
452461
wrapper=on_demand_feature_view_obj, wrapped=user_function

sdk/python/feast/registry.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from typing import Any, Dict, List, Optional
2525
from urllib.parse import urlparse
2626

27-
import dill
2827
from google.protobuf.internal.containers import RepeatedCompositeFieldContainer
2928
from google.protobuf.json_format import MessageToJson
3029
from proto import Message
@@ -732,9 +731,10 @@ def to_dict(self, project: str) -> Dict[str, List[Any]]:
732731
key=lambda on_demand_feature_view: on_demand_feature_view.name,
733732
):
734733
odfv_dict = self._message_to_sorted_dict(on_demand_feature_view.to_proto())
735-
odfv_dict["spec"]["userDefinedFunction"]["body"] = dill.source.getsource(
736-
on_demand_feature_view.udf
737-
)
734+
735+
odfv_dict["spec"]["userDefinedFunction"][
736+
"body"
737+
] = on_demand_feature_view.udf_string
738738
registry_dict["onDemandFeatureViews"].append(odfv_dict)
739739
for request_feature_view in sorted(
740740
self.list_request_feature_views(project=project),

sdk/python/tests/integration/feature_repos/universal/feature_views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def conv_rate_plus_100_feature_view(
6969
schema=[] if infer_features else _features,
7070
sources=sources,
7171
udf=conv_rate_plus_100,
72+
udf_string="raw udf source",
7273
)
7374

7475

@@ -106,6 +107,7 @@ def similarity_feature_view(
106107
sources=sources,
107108
schema=[] if infer_features else _fields,
108109
udf=similarity,
110+
udf_string="similarity raw udf",
109111
)
110112

111113

sdk/python/tests/unit/test_on_demand_feature_view.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def test_hash():
5555
Field(name="output2", dtype=Float32),
5656
],
5757
udf=udf1,
58+
udf_string="udf1 source code",
5859
)
5960
on_demand_feature_view_2 = OnDemandFeatureView(
6061
name="my-on-demand-feature-view",
@@ -64,6 +65,7 @@ def test_hash():
6465
Field(name="output2", dtype=Float32),
6566
],
6667
udf=udf1,
68+
udf_string="udf1 source code",
6769
)
6870
on_demand_feature_view_3 = OnDemandFeatureView(
6971
name="my-on-demand-feature-view",
@@ -73,6 +75,7 @@ def test_hash():
7375
Field(name="output2", dtype=Float32),
7476
],
7577
udf=udf2,
78+
udf_string="udf2 source code",
7679
)
7780
on_demand_feature_view_4 = OnDemandFeatureView(
7881
name="my-on-demand-feature-view",
@@ -82,6 +85,7 @@ def test_hash():
8285
Field(name="output2", dtype=Float32),
8386
],
8487
udf=udf2,
88+
udf_string="udf2 source code",
8589
description="test",
8690
)
8791

0 commit comments

Comments
 (0)