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: Fix GrpcServer_pb2 typed_features stub and import paths
Signed-off-by: Nick Quinn <nicholas_quinn@apple.com>
  • Loading branch information
nickquinn408 committed Mar 16, 2026
commit d093bc62e6da71d62cc5aec0052c9e6440504688
3 changes: 2 additions & 1 deletion sdk/python/feast/infra/contrib/grpc_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import grpc
import pandas as pd
from grpc_health.v1 import health, health_pb2_grpc

from feast.data_source import PushMode
from feast.errors import FeatureServiceNotFoundException, PushSourceNotFoundException
Expand Down Expand Up @@ -156,6 +155,8 @@ def get_grpc_server(
max_workers: int,
registry_ttl_sec: int,
):
from grpc_health.v1 import health, health_pb2_grpc

logger.info(f"Initializing gRPC server on {address}")
server = grpc.server(futures.ThreadPoolExecutor(max_workers=max_workers))
add_GrpcFeatureServerServicer_to_server(
Expand Down
39 changes: 24 additions & 15 deletions sdk/python/feast/protos/feast/serving/GrpcServer_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 45 additions & 2 deletions sdk/python/feast/protos/feast/serving/GrpcServer_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ isort:skip_file
"""
import builtins
import collections.abc
import feast.protos.feast.types.Value_pb2
import google.protobuf.descriptor
import google.protobuf.internal.containers
import google.protobuf.message
Expand Down Expand Up @@ -34,24 +35,45 @@ class PushRequest(google.protobuf.message.Message):
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ...

class TypedFeaturesEntry(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor

KEY_FIELD_NUMBER: builtins.int
VALUE_FIELD_NUMBER: builtins.int
key: builtins.str
@property
def value(self) -> feast.protos.feast.types.Value_pb2.Value: ...
def __init__(
self,
*,
key: builtins.str = ...,
value: feast.protos.feast.types.Value_pb2.Value | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ...

FEATURES_FIELD_NUMBER: builtins.int
STREAM_FEATURE_VIEW_FIELD_NUMBER: builtins.int
ALLOW_REGISTRY_CACHE_FIELD_NUMBER: builtins.int
TO_FIELD_NUMBER: builtins.int
TYPED_FEATURES_FIELD_NUMBER: builtins.int
@property
def features(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ...
stream_feature_view: builtins.str
allow_registry_cache: builtins.bool
to: builtins.str
@property
def typed_features(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, feast.protos.feast.types.Value_pb2.Value]: ...
def __init__(
self,
*,
features: collections.abc.Mapping[builtins.str, builtins.str] | None = ...,
stream_feature_view: builtins.str = ...,
allow_registry_cache: builtins.bool = ...,
to: builtins.str = ...,
typed_features: collections.abc.Mapping[builtins.str, feast.protos.feast.types.Value_pb2.Value] | None = ...,
) -> None: ...
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"]) -> None: ...
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: ...

global___PushRequest = PushRequest

Expand Down Expand Up @@ -87,21 +109,42 @@ class WriteToOnlineStoreRequest(google.protobuf.message.Message):
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ...

class TypedFeaturesEntry(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor

KEY_FIELD_NUMBER: builtins.int
VALUE_FIELD_NUMBER: builtins.int
key: builtins.str
@property
def value(self) -> feast.protos.feast.types.Value_pb2.Value: ...
def __init__(
self,
*,
key: builtins.str = ...,
value: feast.protos.feast.types.Value_pb2.Value | None = ...,
) -> None: ...
def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ...
def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ...

FEATURES_FIELD_NUMBER: builtins.int
FEATURE_VIEW_NAME_FIELD_NUMBER: builtins.int
ALLOW_REGISTRY_CACHE_FIELD_NUMBER: builtins.int
TYPED_FEATURES_FIELD_NUMBER: builtins.int
@property
def features(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ...
feature_view_name: builtins.str
allow_registry_cache: builtins.bool
@property
def typed_features(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, feast.protos.feast.types.Value_pb2.Value]: ...
def __init__(
self,
*,
features: collections.abc.Mapping[builtins.str, builtins.str] | None = ...,
feature_view_name: builtins.str = ...,
allow_registry_cache: builtins.bool = ...,
typed_features: collections.abc.Mapping[builtins.str, feast.protos.feast.types.Value_pb2.Value] | None = ...,
) -> None: ...
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"]) -> None: ...
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: ...

global___WriteToOnlineStoreRequest = WriteToOnlineStoreRequest

Expand Down
28 changes: 20 additions & 8 deletions sdk/python/tests/unit/test_grpc_server_write_protos.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,37 @@ def test_write_to_online_store_request_typed_features():

def test_push_request_string_and_typed_features_are_independent():
"""Setting features does not affect typed_features and vice versa."""
r1 = PushRequest(features={"driver_id": "1001"}, stream_feature_view="s", to="online")
r2 = PushRequest(typed_features={"driver_id": Value(int64_val=1001)}, stream_feature_view="s", to="online")
r1 = PushRequest(
features={"driver_id": "1001"}, stream_feature_view="s", to="online"
)
r2 = PushRequest(
typed_features={"driver_id": Value(int64_val=1001)},
stream_feature_view="s",
to="online",
)
assert len(r1.typed_features) == 0
assert len(r2.features) == 0


def test_write_to_online_store_string_and_typed_features_are_independent():
r1 = WriteToOnlineStoreRequest(features={"driver_id": "1001"}, feature_view_name="fv")
r2 = WriteToOnlineStoreRequest(typed_features={"driver_id": Value(int64_val=1001)}, feature_view_name="fv")
r1 = WriteToOnlineStoreRequest(
features={"driver_id": "1001"}, feature_view_name="fv"
)
r2 = WriteToOnlineStoreRequest(
typed_features={"driver_id": Value(int64_val=1001)}, feature_view_name="fv"
)
assert len(r1.typed_features) == 0
assert len(r2.features) == 0


def test_parse_typed_null_val_becomes_none():
"""Value(null_val=NULL) must produce None in the DataFrame, not the integer 0."""
df = parse_typed({
"present": Value(int64_val=42),
"missing": Value(null_val=Null.NULL),
})
df = parse_typed(
{
"present": Value(int64_val=42),
"missing": Value(null_val=Null.NULL),
}
)
assert df["present"].iloc[0] == 42
assert df["missing"].iloc[0] is None

Expand Down
Loading