Skip to content

Commit c559ac5

Browse files
docs: document float_precision=18 trade-off; add double_val round-trip test
Signed-off-by: abhijeet-dhumal <abhijeetdhumal652@gmail.com>
1 parent b2f2909 commit c559ac5

2 files changed

Lines changed: 65 additions & 2 deletions

File tree

sdk/python/feast/feature_server_utils.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,36 @@
66

77
import logging
88
from datetime import datetime, timezone
9-
from typing import Any, Dict, Optional
9+
from typing import Any, Callable, Dict, Optional
10+
11+
from starlette.responses import Response
1012

1113
from feast.protos.feast.serving.ServingService_pb2 import GetOnlineFeaturesResponse
1214
from feast.protos.feast.types.Value_pb2 import Value
1315

1416
logger = logging.getLogger(__name__)
1517

18+
try:
19+
import orjson
20+
21+
def _make_json_response(data: Dict[str, Any]) -> Response:
22+
return Response(
23+
content=orjson.dumps(data),
24+
media_type="application/json",
25+
)
26+
27+
except ImportError:
28+
import json
29+
30+
def _make_json_response(data: Dict[str, Any]) -> Response: # type: ignore[misc]
31+
return Response(
32+
content=json.dumps(data).encode(),
33+
media_type="application/json",
34+
)
35+
36+
37+
_make_json_response: Callable[[Dict[str, Any]], Response]
38+
1639
# FieldStatus enum mapping (protos/feast/serving/ServingService.proto)
1740
_STATUS_NAMES: Dict[int, str] = {
1841
0: "INVALID",
@@ -24,7 +47,17 @@
2447

2548

2649
def convert_response_to_dict(response: GetOnlineFeaturesResponse) -> Dict[str, Any]:
27-
"""Convert GetOnlineFeaturesResponse to dict (matches proto_json.patch() format)."""
50+
"""Convert GetOnlineFeaturesResponse to a JSON-serializable dict.
51+
52+
Matches the structure produced by MessageToDict(proto, preserving_proto_field_name=True)
53+
with proto_json.patch() applied, with one intentional difference:
54+
55+
- double_val fields are returned as Python float objects (json.dumps uses Python 3.1+
56+
shortest round-trip form, ~15-17 sig digits) rather than 18 fixed significant digits
57+
(float_precision=18). Values are numerically identical; only the JSON string length
58+
may differ. This is safe for all ML feature types and avoids unnecessary precision
59+
overhead.
60+
"""
2861
result: Dict[str, Any] = {
2962
"results": [
3063
{

sdk/python/tests/unit/test_feature_server_utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,36 @@ def test_float_val_precision_matches_message_to_dict(self):
448448
== standard_result["results"][0]["values"]
449449
)
450450

451+
def test_double_val_precision(self):
452+
"""double_val is returned as a Python float (shortest round-trip form).
453+
454+
The upstream code passed float_precision=18 to MessageToDict, which forced 18
455+
significant digits for doubles. Our implementation returns native Python floats
456+
serialized by json.dumps using Python 3.1+ shortest-round-trip representation
457+
(~15–17 sig digits). The value is identical when round-tripped through float64;
458+
the only difference is how many trailing digits appear in the JSON string.
459+
This is an intentional trade-off for speed and is safe for all ML feature values.
460+
"""
461+
import json
462+
import struct
463+
464+
# Use a value with many significant digits
465+
pi = 3.141592653589793
466+
response = GetOnlineFeaturesResponse()
467+
fv = response.results.add()
468+
fv.values.append(Value(double_val=pi))
469+
fv.statuses.append(FieldStatus.PRESENT)
470+
471+
result = convert_response_to_dict(response)
472+
value = result["results"][0]["values"][0]
473+
474+
# Value must round-trip correctly through json.dumps
475+
assert value == pi
476+
round_tripped = json.loads(json.dumps(value))
477+
assert round_tripped == pi
478+
# Verify it encodes as the same float64 bit pattern
479+
assert struct.pack("d", value) == struct.pack("d", pi)
480+
451481
def test_set_types_return_flat_list(self):
452482
"""set types (string_set_val, int64_set_val, etc.) return flat lists.
453483

0 commit comments

Comments
 (0)