Skip to content

Commit f630056

Browse files
authored
feat: Make udf optional if agg defined (#5689) (#6328)
1 parent f08b4e8 commit f630056

14 files changed

Lines changed: 176 additions & 50 deletions

File tree

sdk/python/feast/entity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def to_proto(self) -> EntityProto:
203203

204204
spec = EntitySpecProto(
205205
name=self.name,
206-
value_type=self.value_type.value,
206+
value_type=self.value_type.value, # type: ignore[arg-type]
207207
join_key=self.join_key,
208208
description=self.description,
209209
tags=self.tags,

sdk/python/feast/field.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def to_proto(self) -> FieldProto:
136136
tags[NESTED_COLLECTION_INNER_TYPE_TAG] = _feast_type_to_str(self.dtype)
137137
return FieldProto(
138138
name=self.name,
139-
value_type=value_type.value,
139+
value_type=value_type.value, # type: ignore[arg-type]
140140
description=self.description,
141141
tags=tags,
142142
vector_index=self.vector_index,

sdk/python/feast/infra/online_stores/remote.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ def _construct_online_read_api_json_request(
501501
for row in entity_keys:
502502
entity_key = row.join_keys[0]
503503
entity_values.append(
504-
getattr(row.entity_values[0], row.entity_values[0].WhichOneof("val"))
504+
getattr(row.entity_values[0], row.entity_values[0].WhichOneof("val")) # type: ignore[arg-type]
505505
)
506506

507507
return {

sdk/python/feast/on_demand_feature_view.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,10 @@ def _validate_sources_config(self) -> None:
527527

528528
def _validate_transformation_config(self) -> None:
529529
"""Validate transformation configuration."""
530+
# Aggregations provide their own transformation; no udf/feature_transformation required.
531+
if self.aggregations:
532+
return
533+
530534
if not self.feature_transformation:
531535
raise ValueError(ODFVErrorMessages.no_transformation_provided())
532536

@@ -783,6 +787,8 @@ def _parse_transformation_from_proto(
783787
feature_transformation.substrait_transformation
784788
)
785789
elif transformation_type is None:
790+
if proto.spec.aggregations:
791+
return None
786792
# Handle backward compatibility case where feature_transformation is cleared
787793
return cls._handle_backward_compatible_udf(proto)
788794
else:
@@ -1113,6 +1119,13 @@ def _preprocess_feature_dict(
11131119
return preprocessed_dict, columns_to_cleanup
11141120

11151121
def infer_features(self) -> None:
1122+
if self.aggregations and not self.feature_transformation:
1123+
if not self.features:
1124+
raise RegistryInferenceFailure(
1125+
"OnDemandFeatureView",
1126+
f"Could not infer Features for the feature view '{self.name}'.",
1127+
)
1128+
return
11161129
assert self.feature_transformation is not None
11171130
random_input = self._construct_random_input(singleton=self.singleton)
11181131
inferred_features = self.feature_transformation.infer_features(

sdk/python/feast/protos/feast/core/DataSource_pb2.pyi

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import builtins
2020
import collections.abc
2121
import feast.core.DataFormat_pb2
2222
import feast.core.Feature_pb2
23-
import feast.types.Value_pb2
23+
from feast.protos.feast.types import Value_pb2 as _feast_types_Value_pb2
2424
import google.protobuf.descriptor
2525
import google.protobuf.duration_pb2
2626
import google.protobuf.internal.containers
@@ -424,26 +424,26 @@ class DataSource(google.protobuf.message.Message):
424424
KEY_FIELD_NUMBER: builtins.int
425425
VALUE_FIELD_NUMBER: builtins.int
426426
key: builtins.str
427-
value: feast.types.Value_pb2.ValueType.Enum.ValueType
427+
value: _feast_types_Value_pb2.ValueType.Enum.ValueType
428428
def __init__(
429429
self,
430430
*,
431431
key: builtins.str = ...,
432-
value: feast.types.Value_pb2.ValueType.Enum.ValueType = ...,
432+
value: _feast_types_Value_pb2.ValueType.Enum.ValueType = ...,
433433
) -> None: ...
434434
def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ...
435435

436436
DEPRECATED_SCHEMA_FIELD_NUMBER: builtins.int
437437
SCHEMA_FIELD_NUMBER: builtins.int
438438
@property
439-
def deprecated_schema(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, feast.types.Value_pb2.ValueType.Enum.ValueType]:
439+
def deprecated_schema(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, _feast_types_Value_pb2.ValueType.Enum.ValueType]:
440440
"""Mapping of feature name to type"""
441441
@property
442442
def schema(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[feast.core.Feature_pb2.FeatureSpecV2]: ...
443443
def __init__(
444444
self,
445445
*,
446-
deprecated_schema: collections.abc.Mapping[builtins.str, feast.types.Value_pb2.ValueType.Enum.ValueType] | None = ...,
446+
deprecated_schema: collections.abc.Mapping[builtins.str, _feast_types_Value_pb2.ValueType.Enum.ValueType] | None = ...,
447447
schema: collections.abc.Iterable[feast.core.Feature_pb2.FeatureSpecV2] | None = ...,
448448
) -> None: ...
449449
def ClearField(self, field_name: typing_extensions.Literal["deprecated_schema", b"deprecated_schema", "schema", b"schema"]) -> None: ...

sdk/python/feast/protos/feast/core/Entity_pb2.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ isort:skip_file
1818
"""
1919
import builtins
2020
import collections.abc
21-
import feast.types.Value_pb2
21+
from feast.protos.feast.types import Value_pb2 as _feast_types_Value_pb2
2222
import google.protobuf.descriptor
2323
import google.protobuf.internal.containers
2424
import google.protobuf.message
@@ -83,7 +83,7 @@ class EntitySpecV2(google.protobuf.message.Message):
8383
"""Name of the entity."""
8484
project: builtins.str
8585
"""Name of Feast project that this feature table belongs to."""
86-
value_type: feast.types.Value_pb2.ValueType.Enum.ValueType
86+
value_type: _feast_types_Value_pb2.ValueType.Enum.ValueType
8787
"""Type of the entity."""
8888
description: builtins.str
8989
"""Description of the entity."""
@@ -99,7 +99,7 @@ class EntitySpecV2(google.protobuf.message.Message):
9999
*,
100100
name: builtins.str = ...,
101101
project: builtins.str = ...,
102-
value_type: feast.types.Value_pb2.ValueType.Enum.ValueType = ...,
102+
value_type: _feast_types_Value_pb2.ValueType.Enum.ValueType = ...,
103103
description: builtins.str = ...,
104104
join_key: builtins.str = ...,
105105
tags: collections.abc.Mapping[builtins.str, builtins.str] | None = ...,

sdk/python/feast/protos/feast/core/Feature_pb2.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ limitations under the License.
1818
"""
1919
import builtins
2020
import collections.abc
21-
import feast.types.Value_pb2
21+
from feast.protos.feast.types import Value_pb2 as _feast_types_Value_pb2
2222
import google.protobuf.descriptor
2323
import google.protobuf.internal.containers
2424
import google.protobuf.message
@@ -58,7 +58,7 @@ class FeatureSpecV2(google.protobuf.message.Message):
5858
VECTOR_LENGTH_FIELD_NUMBER: builtins.int
5959
name: builtins.str
6060
"""Name of the feature. Not updatable."""
61-
value_type: feast.types.Value_pb2.ValueType.Enum.ValueType
61+
value_type: _feast_types_Value_pb2.ValueType.Enum.ValueType
6262
"""Value type of the feature. Not updatable."""
6363
@property
6464
def tags(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]:
@@ -75,7 +75,7 @@ class FeatureSpecV2(google.protobuf.message.Message):
7575
self,
7676
*,
7777
name: builtins.str = ...,
78-
value_type: feast.types.Value_pb2.ValueType.Enum.ValueType = ...,
78+
value_type: _feast_types_Value_pb2.ValueType.Enum.ValueType = ...,
7979
tags: collections.abc.Mapping[builtins.str, builtins.str] | None = ...,
8080
description: builtins.str = ...,
8181
vector_index: builtins.bool = ...,

sdk/python/feast/protos/feast/serving/Connector_pb2.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import builtins
66
import collections.abc
77
import feast.serving.ServingService_pb2
88
import feast.types.EntityKey_pb2
9-
import feast.types.Value_pb2
9+
from feast.protos.feast.types import Value_pb2 as _feast_types_Value_pb2
1010
import google.protobuf.descriptor
1111
import google.protobuf.internal.containers
1212
import google.protobuf.message
@@ -31,13 +31,13 @@ class ConnectorFeature(google.protobuf.message.Message):
3131
@property
3232
def timestamp(self) -> google.protobuf.timestamp_pb2.Timestamp: ...
3333
@property
34-
def value(self) -> feast.types.Value_pb2.Value: ...
34+
def value(self) -> _feast_types_Value_pb2.Value: ...
3535
def __init__(
3636
self,
3737
*,
3838
reference: feast.serving.ServingService_pb2.FeatureReferenceV2 | None = ...,
3939
timestamp: google.protobuf.timestamp_pb2.Timestamp | None = ...,
40-
value: feast.types.Value_pb2.Value | None = ...,
40+
value: _feast_types_Value_pb2.Value | None = ...,
4141
) -> None: ...
4242
def HasField(self, field_name: typing_extensions.Literal["reference", b"reference", "timestamp", b"timestamp", "value", b"value"]) -> builtins.bool: ...
4343
def ClearField(self, field_name: typing_extensions.Literal["reference", b"reference", "timestamp", b"timestamp", "value", b"value"]) -> None: ...

sdk/python/feast/protos/feast/serving/GrpcServer_pb2.pyi

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ isort:skip_file
44
"""
55
import builtins
66
import collections.abc
7-
import feast.types.Value_pb2
7+
from feast.protos.feast.types import Value_pb2 as _feast_types_Value_pb2
88
import google.protobuf.descriptor
99
import google.protobuf.internal.containers
1010
import google.protobuf.message
@@ -42,12 +42,12 @@ class PushRequest(google.protobuf.message.Message):
4242
VALUE_FIELD_NUMBER: builtins.int
4343
key: builtins.str
4444
@property
45-
def value(self) -> feast.types.Value_pb2.Value: ...
45+
def value(self) -> _feast_types_Value_pb2.Value: ...
4646
def __init__(
4747
self,
4848
*,
4949
key: builtins.str = ...,
50-
value: feast.types.Value_pb2.Value | None = ...,
50+
value: _feast_types_Value_pb2.Value | None = ...,
5151
) -> None: ...
5252
def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ...
5353
def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ...
@@ -63,15 +63,15 @@ class PushRequest(google.protobuf.message.Message):
6363
allow_registry_cache: builtins.bool
6464
to: builtins.str
6565
@property
66-
def typed_features(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, feast.types.Value_pb2.Value]: ...
66+
def typed_features(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, _feast_types_Value_pb2.Value]: ...
6767
def __init__(
6868
self,
6969
*,
7070
features: collections.abc.Mapping[builtins.str, builtins.str] | None = ...,
7171
stream_feature_view: builtins.str = ...,
7272
allow_registry_cache: builtins.bool = ...,
7373
to: builtins.str = ...,
74-
typed_features: collections.abc.Mapping[builtins.str, feast.types.Value_pb2.Value] | None = ...,
74+
typed_features: collections.abc.Mapping[builtins.str, _feast_types_Value_pb2.Value] | None = ...,
7575
) -> None: ...
7676
def ClearField(self, field_name: typing_extensions.Literal["allow_registry_cache", b"allow_registry_cache", "features", b"features", "stream_feature_view", b"stream_feature_view", "to", b"to", "typed_features", b"typed_features"]) -> None: ...
7777

@@ -116,12 +116,12 @@ class WriteToOnlineStoreRequest(google.protobuf.message.Message):
116116
VALUE_FIELD_NUMBER: builtins.int
117117
key: builtins.str
118118
@property
119-
def value(self) -> feast.types.Value_pb2.Value: ...
119+
def value(self) -> _feast_types_Value_pb2.Value: ...
120120
def __init__(
121121
self,
122122
*,
123123
key: builtins.str = ...,
124-
value: feast.types.Value_pb2.Value | None = ...,
124+
value: _feast_types_Value_pb2.Value | None = ...,
125125
) -> None: ...
126126
def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ...
127127
def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ...
@@ -135,14 +135,14 @@ class WriteToOnlineStoreRequest(google.protobuf.message.Message):
135135
feature_view_name: builtins.str
136136
allow_registry_cache: builtins.bool
137137
@property
138-
def typed_features(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, feast.types.Value_pb2.Value]: ...
138+
def typed_features(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, _feast_types_Value_pb2.Value]: ...
139139
def __init__(
140140
self,
141141
*,
142142
features: collections.abc.Mapping[builtins.str, builtins.str] | None = ...,
143143
feature_view_name: builtins.str = ...,
144144
allow_registry_cache: builtins.bool = ...,
145-
typed_features: collections.abc.Mapping[builtins.str, feast.types.Value_pb2.Value] | None = ...,
145+
typed_features: collections.abc.Mapping[builtins.str, _feast_types_Value_pb2.Value] | None = ...,
146146
) -> None: ...
147147
def ClearField(self, field_name: typing_extensions.Literal["allow_registry_cache", b"allow_registry_cache", "feature_view_name", b"feature_view_name", "features", b"features", "typed_features", b"typed_features"]) -> None: ...
148148

sdk/python/feast/protos/feast/serving/ServingService_pb2.pyi

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ limitations under the License.
1818
"""
1919
import builtins
2020
import collections.abc
21-
import feast.types.Value_pb2
21+
from feast.protos.feast.types import Value_pb2 as _feast_types_Value_pb2
2222
import google.protobuf.descriptor
2323
import google.protobuf.internal.containers
2424
import google.protobuf.internal.enum_type_wrapper
@@ -137,12 +137,12 @@ class GetOnlineFeaturesRequestV2(google.protobuf.message.Message):
137137
VALUE_FIELD_NUMBER: builtins.int
138138
key: builtins.str
139139
@property
140-
def value(self) -> feast.types.Value_pb2.Value: ...
140+
def value(self) -> _feast_types_Value_pb2.Value: ...
141141
def __init__(
142142
self,
143143
*,
144144
key: builtins.str = ...,
145-
value: feast.types.Value_pb2.Value | None = ...,
145+
value: _feast_types_Value_pb2.Value | None = ...,
146146
) -> None: ...
147147
def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ...
148148
def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ...
@@ -155,13 +155,13 @@ class GetOnlineFeaturesRequestV2(google.protobuf.message.Message):
155155
together with maxAge, to determine feature staleness.
156156
"""
157157
@property
158-
def fields(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, feast.types.Value_pb2.Value]:
158+
def fields(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, _feast_types_Value_pb2.Value]:
159159
"""Map containing mapping of entity name to entity value."""
160160
def __init__(
161161
self,
162162
*,
163163
timestamp: google.protobuf.timestamp_pb2.Timestamp | None = ...,
164-
fields: collections.abc.Mapping[builtins.str, feast.types.Value_pb2.Value] | None = ...,
164+
fields: collections.abc.Mapping[builtins.str, _feast_types_Value_pb2.Value] | None = ...,
165165
) -> None: ...
166166
def HasField(self, field_name: typing_extensions.Literal["timestamp", b"timestamp"]) -> builtins.bool: ...
167167
def ClearField(self, field_name: typing_extensions.Literal["fields", b"fields", "timestamp", b"timestamp"]) -> None: ...
@@ -221,12 +221,12 @@ class GetOnlineFeaturesRequest(google.protobuf.message.Message):
221221
VALUE_FIELD_NUMBER: builtins.int
222222
key: builtins.str
223223
@property
224-
def value(self) -> feast.types.Value_pb2.RepeatedValue: ...
224+
def value(self) -> _feast_types_Value_pb2.RepeatedValue: ...
225225
def __init__(
226226
self,
227227
*,
228228
key: builtins.str = ...,
229-
value: feast.types.Value_pb2.RepeatedValue | None = ...,
229+
value: _feast_types_Value_pb2.RepeatedValue | None = ...,
230230
) -> None: ...
231231
def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ...
232232
def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ...
@@ -238,12 +238,12 @@ class GetOnlineFeaturesRequest(google.protobuf.message.Message):
238238
VALUE_FIELD_NUMBER: builtins.int
239239
key: builtins.str
240240
@property
241-
def value(self) -> feast.types.Value_pb2.RepeatedValue: ...
241+
def value(self) -> _feast_types_Value_pb2.RepeatedValue: ...
242242
def __init__(
243243
self,
244244
*,
245245
key: builtins.str = ...,
246-
value: feast.types.Value_pb2.RepeatedValue | None = ...,
246+
value: _feast_types_Value_pb2.RepeatedValue | None = ...,
247247
) -> None: ...
248248
def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ...
249249
def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ...
@@ -258,13 +258,13 @@ class GetOnlineFeaturesRequest(google.protobuf.message.Message):
258258
@property
259259
def features(self) -> global___FeatureList: ...
260260
@property
261-
def entities(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, feast.types.Value_pb2.RepeatedValue]:
261+
def entities(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, _feast_types_Value_pb2.RepeatedValue]:
262262
"""The entity data is specified in a columnar format
263263
A map of entity name -> list of values
264264
"""
265265
full_feature_names: builtins.bool
266266
@property
267-
def request_context(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, feast.types.Value_pb2.RepeatedValue]:
267+
def request_context(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, _feast_types_Value_pb2.RepeatedValue]:
268268
"""Context for OnDemand Feature Transformation
269269
(was moved to dedicated parameter to avoid unnecessary separation logic on serving side)
270270
A map of variable name -> list of values
@@ -276,9 +276,9 @@ class GetOnlineFeaturesRequest(google.protobuf.message.Message):
276276
*,
277277
feature_service: builtins.str = ...,
278278
features: global___FeatureList | None = ...,
279-
entities: collections.abc.Mapping[builtins.str, feast.types.Value_pb2.RepeatedValue] | None = ...,
279+
entities: collections.abc.Mapping[builtins.str, _feast_types_Value_pb2.RepeatedValue] | None = ...,
280280
full_feature_names: builtins.bool = ...,
281-
request_context: collections.abc.Mapping[builtins.str, feast.types.Value_pb2.RepeatedValue] | None = ...,
281+
request_context: collections.abc.Mapping[builtins.str, _feast_types_Value_pb2.RepeatedValue] | None = ...,
282282
include_feature_view_version_metadata: builtins.bool = ...,
283283
) -> None: ...
284284
def HasField(self, field_name: typing_extensions.Literal["feature_service", b"feature_service", "features", b"features", "kind", b"kind"]) -> builtins.bool: ...
@@ -297,15 +297,15 @@ class GetOnlineFeaturesResponse(google.protobuf.message.Message):
297297
STATUSES_FIELD_NUMBER: builtins.int
298298
EVENT_TIMESTAMPS_FIELD_NUMBER: builtins.int
299299
@property
300-
def values(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[feast.types.Value_pb2.Value]: ...
300+
def values(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[_feast_types_Value_pb2.Value]: ...
301301
@property
302302
def statuses(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[global___FieldStatus.ValueType]: ...
303303
@property
304304
def event_timestamps(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[google.protobuf.timestamp_pb2.Timestamp]: ...
305305
def __init__(
306306
self,
307307
*,
308-
values: collections.abc.Iterable[feast.types.Value_pb2.Value] | None = ...,
308+
values: collections.abc.Iterable[_feast_types_Value_pb2.Value] | None = ...,
309309
statuses: collections.abc.Iterable[global___FieldStatus.ValueType] | None = ...,
310310
event_timestamps: collections.abc.Iterable[google.protobuf.timestamp_pb2.Timestamp] | None = ...,
311311
) -> None: ...

0 commit comments

Comments
 (0)