Expected Behavior
feast plan (or any OnlineStore.plan() implementation that follows the same pattern as SqliteOnlineStore.plan()) should work the same way whether the registry contains regular feature views, streaming feature views, or both.
Current Behavior
Feast has two kinds of feature views: regular ones (FeatureView) and streaming ones (StreamFeatureView, for Kafka-style sources). Under the hood, each is a distinct protobuf message type — FeatureViewProto vs StreamFeatureViewProto. They're similar in shape, but not interchangeable.
SqliteOnlineStore.plan() loops over every feature view in the registry — both kinds together — and reconstructs each one with the same call (sdk/python/feast/infra/online_stores/sqlite.py):
def plan(
self, config: RepoConfig, desired_registry_proto: RegistryProto
) -> List[InfraObject]:
...
infra_objects: List[InfraObject] = [
SqliteTable(
path=self._get_db_path(config),
name=_table_id(project, FeatureView.from_proto(view), versioning),
)
for view in [
*desired_registry_proto.feature_views,
*desired_registry_proto.stream_feature_views,
]
]
FeatureView.from_proto() is guarded by a runtime type checker (typeguard, via @typechecked on the class — sdk/python/feast/feature_view.py#L110-L111) that only accepts a FeatureViewProto. It works fine for the feature_views list. But when the loop reaches an entry from stream_feature_views, it hands over a StreamFeatureViewProto — the wrong type for that function — and typeguard raises:
typeguard.TypeCheckError: argument "feature_view_proto" (feast.core.StreamFeatureView_pb2.StreamFeatureView) is not an instance of feast.core.FeatureView_pb2.FeatureView
In short: plan() calls the wrong reconstruction function on the wrong kind of feature view, so it crashes for any repo that has at least one StreamFeatureView registered — a fairly common setup, not an edge case.
We ran into this adding plan()/InfraObject support to a custom DynamoDB online store (the stock DynamoDBOnlineStore.plan() is just the no-op default and never triggers this) and initially copied sqlite.py's pattern as the reference implementation, since it's the only in-tree store that implements plan().
Steps to reproduce
from feast import FileSource
from feast.data_format import JsonFormat
from feast.data_source import KafkaSource
from feast.stream_feature_view import StreamFeatureView
from feast.infra.online_stores.sqlite import SqliteOnlineStore
from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto
from feast.repo_config import RepoConfig
config = RepoConfig(project="p", registry="/tmp/x.db", provider="local", online_store="sqlite")
stream_fv = StreamFeatureView(
name="driver_dropoffs_stream",
entities=[],
schema=[],
source=KafkaSource(
name="k",
timestamp_field="event_timestamp",
message_format=JsonFormat(schema_json=""),
kafka_bootstrap_servers="localhost:9092",
topic="t",
batch_source=FileSource(path="dummy.parquet", timestamp_field="event_timestamp"),
),
)
registry_proto = RegistryProto()
registry_proto.stream_feature_views.append(stream_fv.to_proto())
SqliteOnlineStore().plan(config, registry_proto) # raises typeguard.TypeCheckError
Full traceback:
Traceback (most recent call last):
File "repro.py", line 28, in <module>
SqliteOnlineStore().plan(config, registry_proto)
File ".../feast/infra/online_stores/sqlite.py", line 321, in plan
infra_objects: List[InfraObject] = [
^
File ".../feast/infra/online_stores/sqlite.py", line 326, in <listcomp>
FeatureView.from_proto(view),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File ".../feast/feature_view.py", line 523, in from_proto
def from_proto(cls, feature_view_proto: FeatureViewProto) -> "FeatureView":
File ".../typeguard/_functions.py", line 180, in check_argument_types_internal
check_type_internal(value, annotation, memo)
File ".../typeguard/_checkers.py", line 994, in check_type_internal
raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
typeguard.TypeCheckError: argument "feature_view_proto" (feast.core.StreamFeatureView_pb2.StreamFeatureView) is not an instance of feast.core.FeatureView_pb2.FeatureView
Specifications
- Version: reproduced on 0.62.0 (installed via
pip install feast==0.62.0); confirmed the same code pattern is still present on v0.65.0 (latest tag) as of this report.
- Platform: macOS, Python 3.11
- Subsystem: online store /
feast plan / registry
Possible Solution
Use the matching class for each list instead of FeatureView.from_proto() for both:
views = [
FeatureView.from_proto(view) for view in desired_registry_proto.feature_views
] + [
StreamFeatureView.from_proto(view) for view in desired_registry_proto.stream_feature_views
]
Opened a PR with this fix: #6659
Expected Behavior
feast plan(or anyOnlineStore.plan()implementation that follows the same pattern asSqliteOnlineStore.plan()) should work the same way whether the registry contains regular feature views, streaming feature views, or both.Current Behavior
Feast has two kinds of feature views: regular ones (
FeatureView) and streaming ones (StreamFeatureView, for Kafka-style sources). Under the hood, each is a distinct protobuf message type —FeatureViewProtovsStreamFeatureViewProto. They're similar in shape, but not interchangeable.SqliteOnlineStore.plan()loops over every feature view in the registry — both kinds together — and reconstructs each one with the same call (sdk/python/feast/infra/online_stores/sqlite.py):FeatureView.from_proto()is guarded by a runtime type checker (typeguard, via@typecheckedon the class — sdk/python/feast/feature_view.py#L110-L111) that only accepts aFeatureViewProto. It works fine for thefeature_viewslist. But when the loop reaches an entry fromstream_feature_views, it hands over aStreamFeatureViewProto— the wrong type for that function — and typeguard raises:In short:
plan()calls the wrong reconstruction function on the wrong kind of feature view, so it crashes for any repo that has at least oneStreamFeatureViewregistered — a fairly common setup, not an edge case.We ran into this adding
plan()/InfraObjectsupport to a custom DynamoDB online store (the stockDynamoDBOnlineStore.plan()is just the no-op default and never triggers this) and initially copiedsqlite.py's pattern as the reference implementation, since it's the only in-tree store that implementsplan().Steps to reproduce
Full traceback:
Specifications
pip install feast==0.62.0); confirmed the same code pattern is still present onv0.65.0(latest tag) as of this report.feast plan/ registryPossible Solution
Use the matching class for each list instead of
FeatureView.from_proto()for both:Opened a PR with this fix: #6659