Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions protos/feast/types/Value.proto
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ message ValueType {
FLOAT_SET = 27;
BOOL_SET = 28;
UNIX_TIMESTAMP_SET = 29;
UUID = 30;
TIME_UUID = 31;
UUID_LIST = 32;
TIME_UUID_LIST = 33;
}
}

Expand Down Expand Up @@ -88,6 +92,10 @@ message Value {
FloatSet float_set_val = 27;
BoolSet bool_set_val = 28;
Int64Set unix_timestamp_set_val = 29;
string uuid_val = 30;
string time_uuid_val = 31;
StringList uuid_list_val = 32;
StringList time_uuid_list_val = 33;
}
}

Expand Down
11 changes: 8 additions & 3 deletions sdk/python/feast/feature_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -2597,7 +2597,10 @@ def _doc_feature(x):
online_features_response=online_features_response,
data=requested_features_data,
)
return OnlineResponse(online_features_response)
feature_types = {
f.name: f.dtype.to_value_type() for f in requested_feature_view.features
}
return OnlineResponse(online_features_response, feature_types=feature_types)

def retrieve_online_documents_v2(
self,
Expand Down Expand Up @@ -2881,7 +2884,8 @@ def _retrieve_from_online_store_v2(
online_features_response.metadata.feature_names.val.extend(
features_to_request
)
return OnlineResponse(online_features_response)
feature_types = {f.name: f.dtype.to_value_type() for f in table.features}
return OnlineResponse(online_features_response, feature_types=feature_types)

table_entity_values, idxs, output_len = utils._get_unique_entities_from_values(
entity_key_dict,
Expand All @@ -2908,7 +2912,8 @@ def _retrieve_from_online_store_v2(
data=entity_key_dict,
)

return OnlineResponse(online_features_response)
feature_types = {f.name: f.dtype.to_value_type() for f in table.features}
return OnlineResponse(online_features_response, feature_types=feature_types)

def serve(
self,
Expand Down
30 changes: 28 additions & 2 deletions sdk/python/feast/infra/online_stores/online_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from feast.protos.feast.types.Value_pb2 import Value as ValueProto
from feast.repo_config import RepoConfig
from feast.stream_feature_view import StreamFeatureView
from feast.value_type import ValueType


class OnlineStore(ABC):
Expand Down Expand Up @@ -218,18 +219,21 @@ def get_online_features(
output_len,
)

feature_types = self._build_feature_types(grouped_refs)

if requested_on_demand_feature_views:
utils._augment_response_with_on_demand_transforms(
online_features_response,
feature_refs,
requested_on_demand_feature_views,
full_feature_names,
feature_types=feature_types,
)

utils._drop_unneeded_columns(
online_features_response, requested_result_row_names
)
return OnlineResponse(online_features_response)
return OnlineResponse(online_features_response, feature_types=feature_types)

async def get_online_features_async(
self,
Expand Down Expand Up @@ -318,18 +322,40 @@ async def query_table(table, requested_features):
output_len,
)

feature_types = self._build_feature_types(grouped_refs)

if requested_on_demand_feature_views:
utils._augment_response_with_on_demand_transforms(
online_features_response,
feature_refs,
requested_on_demand_feature_views,
full_feature_names,
feature_types=feature_types,
)

utils._drop_unneeded_columns(
online_features_response, requested_result_row_names
)
return OnlineResponse(online_features_response)
return OnlineResponse(online_features_response, feature_types=feature_types)

@staticmethod
def _build_feature_types(
grouped_refs: List,
) -> Dict[str, ValueType]:
"""Build a mapping of feature names to ValueType from grouped feature view refs.

Includes both bare names and prefixed names (feature_view__feature) so that
lookups succeed regardless of the full_feature_names setting.
"""
feature_types: Dict[str, ValueType] = {}
for table, requested_features in grouped_refs:
table_name = table.projection.name_to_use()
for field in table.features:
if field.name in requested_features:
vtype = field.dtype.to_value_type()
feature_types[field.name] = vtype
feature_types[f"{table_name}__{field.name}"] = vtype
return feature_types

@abstractmethod
def update(
Expand Down
6 changes: 6 additions & 0 deletions sdk/python/feast/on_demand_feature_view.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import copy
import functools
import uuid
import warnings
from types import FunctionType
from typing import Any, List, Optional, Union, cast
Expand Down Expand Up @@ -1093,6 +1094,9 @@ def _get_sample_values_by_type(self) -> dict[ValueType, list[Any]]:
# Special binary types
ValueType.PDF_BYTES: [pdf_sample],
ValueType.IMAGE_BYTES: [image_sample],
# UUID types
ValueType.UUID: [uuid.uuid4()],
ValueType.TIME_UUID: [uuid.uuid1()],
# List types
ValueType.BYTES_LIST: [[b"hello world"]],
ValueType.STRING_LIST: [["hello world"]],
Expand All @@ -1102,6 +1106,8 @@ def _get_sample_values_by_type(self) -> dict[ValueType, list[Any]]:
ValueType.FLOAT_LIST: [[1.0]],
ValueType.BOOL_LIST: [[True]],
ValueType.UNIX_TIMESTAMP_LIST: [[_utc_now()]],
ValueType.UUID_LIST: [[uuid.uuid4(), uuid.uuid4()]],
ValueType.TIME_UUID_LIST: [[uuid.uuid1(), uuid.uuid1()]],
}

@staticmethod
Expand Down
15 changes: 12 additions & 3 deletions sdk/python/feast/online_response.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 OnlineResponse.to_arrow() crashes with ArrowInvalid when response contains UUID features

The to_dict() method in OnlineResponse now returns uuid.UUID objects for UUID/TIME_UUID features (via the updated feast_value_type_to_python_type at sdk/python/feast/type_map.py:132-133). However, to_arrow() at sdk/python/feast/online_response.py:107 passes this dict directly to pa.Table.from_pydict(), which internally calls pa.array(). PyArrow does not natively support uuid.UUID objects and will raise ArrowInvalid: Could not convert UUID('...') with type UUID.

Root Cause and Impact

Before this PR, UUID values were stored as string_val and deserialized as plain str by feast_value_type_to_python_type. After this PR, the dedicated uuid_val proto field causes deserialization to uuid.UUID objects. While to_dict() and to_df() (pandas handles uuid.UUID as object dtype) work correctly, to_arrow() breaks because PyArrow has no built-in conversion for uuid.UUID.

This also breaks the ODFV pandas/substrait transformation path in _augment_response_with_on_demand_transforms (sdk/python/feast/utils.py:694):

if initial_response_arrow is None:
    initial_response_arrow = initial_response.to_arrow()  # crashes here

Any OnDemandFeatureView with mode="pandas" or mode="substrait" whose source feature view contains UUID features will fail at serving time.

Actual: pa.Table.from_pydict({"uuid_col": [uuid.UUID("...")]}) raises ArrowInvalid.

Expected: UUID values should be converted to strings before creating the Arrow table, consistent with the mapping Uuid: pyarrow.string() defined in sdk/python/feast/types.py:270.

(Refers to lines 99-107)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TYPE_CHECKING, Any, Dict, List, TypeAlias, Union
from typing import TYPE_CHECKING, Any, Dict, List, Optional, TypeAlias, Union

import pandas as pd
import pyarrow as pa
Expand All @@ -21,6 +21,7 @@
from feast.protos.feast.serving.ServingService_pb2 import GetOnlineFeaturesResponse
from feast.torch_wrapper import get_torch
from feast.type_map import feast_value_type_to_python_type
from feast.value_type import ValueType

if TYPE_CHECKING:
import torch
Expand All @@ -37,14 +38,20 @@ class OnlineResponse:
Defines an online response in feast.
"""

def __init__(self, online_response_proto: GetOnlineFeaturesResponse):
def __init__(
self,
online_response_proto: GetOnlineFeaturesResponse,
feature_types: Optional[Dict[str, ValueType]] = None,
):
"""
Construct a native online response from its protobuf version.

Args:
online_response_proto: GetOnlineResponse proto object to construct from.
feature_types: Optional mapping of feature names to ValueType for type-aware deserialization.
"""
self.proto = online_response_proto
self._feature_types = feature_types or {}
# Delete DUMMY_ENTITY_ID from proto if it exists
for idx, val in enumerate(self.proto.metadata.feature_names.val):
if val == DUMMY_ENTITY_ID:
Expand All @@ -65,8 +72,10 @@ def to_dict(self, include_event_timestamps: bool = False) -> Dict[str, Any]:
for feature_ref, feature_vector in zip(
self.proto.metadata.feature_names.val, self.proto.results
):
feature_type = self._feature_types.get(feature_ref)
response[feature_ref] = [
feast_value_type_to_python_type(v) for v in feature_vector.values
feast_value_type_to_python_type(v, feature_type)
for v in feature_vector.values
]

if include_event_timestamps:
Expand Down
26 changes: 13 additions & 13 deletions sdk/python/feast/protos/feast/core/DatastoreTable_pb2.pyi
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
* Copyright 2021 The Feast Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and

* Copyright 2021 The Feast Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
"""
import builtins
Expand Down
26 changes: 13 additions & 13 deletions sdk/python/feast/protos/feast/core/Entity_pb2.pyi
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
* Copyright 2020 The Feast Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and

* Copyright 2020 The Feast Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
"""
import builtins
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ else:
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor

class FeatureViewProjection(google.protobuf.message.Message):
"""A projection to be applied on top of a FeatureView.
"""A projection to be applied on top of a FeatureView.
Contains the modifications to a FeatureView such as the features subset to use.
"""

Expand Down
26 changes: 13 additions & 13 deletions sdk/python/feast/protos/feast/core/Project_pb2.pyi
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
* Copyright 2020 The Feast Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and

* Copyright 2020 The Feast Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
"""
import builtins
Expand Down
26 changes: 13 additions & 13 deletions sdk/python/feast/protos/feast/core/Registry_pb2.pyi
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
* Copyright 2020 The Feast Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and

* Copyright 2020 The Feast Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
"""
import builtins
Expand Down
Loading
Loading