Pewarta Proxy

📋 Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

⚙️ Pengaturan Proxy Target

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
fix: handle set, list, and map Value types in _value_to_native
Previous implementation used "_list_" substring check which silently
returned protobuf objects (not JSON-serializable) for:
- *_set_val types (bytes_set_val, string_set_val, int64_set_val, etc.)
- list_val / set_val (RepeatedValue with nested Values)
- map_val / struct_val (Map<string, Value>)
- map_list_val / struct_list_val (MapList)

Add tests for all previously uncovered types and a float32 precision
consistency test against MessageToDict.

Signed-off-by: abhijeet-dhumal <abhijeetdhumal652@gmail.com>
  • Loading branch information
abhijeet-dhumal committed May 27, 2026
commit 5fff274a0f9181e8d01c386da9c05e0f135d7414
18 changes: 17 additions & 1 deletion sdk/python/feast/feature_server_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,23 @@ def _value_to_native(v: Value) -> Optional[Any]:
which = v.WhichOneof("val")
if which is None or which == "null_val":
return None
elif "_list_" in which:
# RepeatedValue — nested Values that must be recursively converted
elif which in ("list_val", "set_val"):
return [_value_to_native(nested) for nested in getattr(v, which).val]
# Map<string, Value> — recursively convert nested Values
elif which in ("map_val", "struct_val"):
return {k: _value_to_native(vv) for k, vv in getattr(v, which).val.items()}
# MapList — list of Map<string, Value>
elif which in ("map_list_val", "struct_list_val"):
return [
{k: _value_to_native(vv) for k, vv in m.val.items()}
for m in getattr(v, which).val
]
# scalar_map_val has non-string keys and is not JSON-serializable without extra work
elif which == "scalar_map_val":
return None
# All simple list/set types (.val is a repeated scalar field)
elif "_list_" in which or "_set_" in which:
return list(getattr(v, which).val)
else:
return getattr(v, which)
Comment on lines +77 to +109

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 bytes_val values cause ORJSONResponse to raise TypeError

_value_to_native at feature_server_utils.py:68 returns raw bytes for bytes_val fields. Even if orjson were installed, orjson.dumps() cannot serialize Python bytes objects and raises TypeError: Type is not JSON serializable: bytes. The old code path returned the dict to FastAPI which processed it through jsonable_encoder (capable of handling bytes). The switch to ORJSONResponse bypasses that encoder, causing a regression for any response containing bytes-typed feature values.

Prompt for agents
In _value_to_native (feature_server_utils.py), when the value type is bytes_val, the raw bytes object is returned. orjson cannot serialize bytes. You need to either: (1) base64-encode bytes values before returning them (e.g. using base64.b64encode(val).decode('ascii')), or (2) not use ORJSONResponse and instead use a serializer that handles bytes. Note that bytes_list_val (handled by the _list_ branch) would also produce a list of bytes objects with the same issue.
Open in Devin Review

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@abhijeet-dhumal bytes_val is still problem since you are using JSONResponse

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching this, Nikhil! Now bytes_val and bytes_list_val now base64-encode the output before returning, so JSONResponse / json.dumps can serialize them without TypeError.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Please review!

Expand Down
81 changes: 81 additions & 0 deletions sdk/python/tests/unit/test_feature_server_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,16 @@
)
from feast.protos.feast.types.Value_pb2 import (
BoolList,
BytesList,
DoubleList,
FloatList,
Int32List,
Int64List,
Int64Set,
Map,
RepeatedValue,
StringList,
StringSet,
Value,
)

Expand Down Expand Up @@ -135,6 +140,65 @@ def test_unix_timestamp_val(self):
result = _value_to_native(v)
assert result == 1609459200

def test_bytes_list_val(self):
v = Value(bytes_list_val=BytesList(val=[b"\x00\x01", b"\x02\x03"]))
result = _value_to_native(v)
assert result == [b"\x00\x01", b"\x02\x03"]

def test_unix_timestamp_list_val(self):
v = Value(int64_list_val=Int64List(val=[1609459200, 1609545600]))
result = _value_to_native(v)
assert result == [1609459200, 1609545600]

def test_string_set_val(self):
v = Value(string_set_val=StringSet(val=["x", "y", "z"]))
result = _value_to_native(v)
assert set(result) == {"x", "y", "z"}

def test_int64_set_val(self):
v = Value(int64_set_val=Int64Set(val=[10, 20, 30]))
result = _value_to_native(v)
assert set(result) == {10, 20, 30}

def test_list_val_nested_values(self):
inner = RepeatedValue()
inner.val.append(Value(int64_val=1))
inner.val.append(Value(string_val="a"))
inner.val.append(Value())
v = Value(list_val=inner)
result = _value_to_native(v)
assert result == [1, "a", None]

def test_set_val_nested_values(self):
inner = RepeatedValue()
inner.val.append(Value(bool_val=True))
inner.val.append(Value(double_val=3.14))
v = Value(set_val=inner)
result = _value_to_native(v)
assert result == [True, 3.14]

def test_map_val(self):
m = Map()
m.val["key1"].CopyFrom(Value(int64_val=42))
m.val["key2"].CopyFrom(Value(string_val="hello"))
v = Value(map_val=m)
result = _value_to_native(v)
assert result == {"key1": 42, "key2": "hello"}

def test_map_val_nested_null(self):
m = Map()
m.val["present"].CopyFrom(Value(int32_val=7))
m.val["missing"].CopyFrom(Value())
v = Value(map_val=m)
result = _value_to_native(v)
assert result["present"] == 7
assert result["missing"] is None

def test_json_val(self):
v = Value(json_val='{"foo": 1}')
result = _value_to_native(v)
assert result == '{"foo": 1}'


class TestTimestampToStr:
"""Tests for _timestamp_to_str function."""
Expand Down Expand Up @@ -366,6 +430,23 @@ def test_metadata_matches_patched_format(self):
== standard_result["metadata"]["feature_names"]
)

def test_float_val_precision_matches_message_to_dict(self):
"""float32 storage causes truncation; both paths must return identical values."""
response = GetOnlineFeaturesResponse()
fv = response.results.add()
# 3.14 is not exactly representable as float32; both implementations
# should return the same truncated float64 representation
fv.values.append(Value(float_val=3.14))
fv.statuses.append(FieldStatus.PRESENT)

fast_result = convert_response_to_dict(response)
standard_result = MessageToDict(response, preserving_proto_field_name=True)

assert (
fast_result["results"][0]["values"]
== standard_result["results"][0]["values"]
)

def test_bytes_values_match_patched_message_to_dict(self):
"""Ensure bytes serialization matches proto_json.patch() format (raw bytes)."""
response = GetOnlineFeaturesResponse()
Expand Down