Skip to content

sqlite.py's OnlineStore.plan() raises typeguard.TypeCheckError for stream feature views #6658

Description

@casaar97

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions