Skip to content
Prev Previous commit
Next Next commit
fix linting
Signed-off-by: HaoXuAI <sduxuhao@gmail.com>
  • Loading branch information
HaoXuAI committed Jul 13, 2025
commit 20182b789ec4b469c817d5e0a56854f1ae65d70a
4 changes: 3 additions & 1 deletion sdk/python/feast/feature_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,9 @@ def _from_proto_internal(
else None
)
source_views = [
FeatureView.from_proto(FeatureViewProto(spec=view_spec, meta=None))
FeatureView._from_proto_internal(
FeatureViewProto(spec=view_spec, meta=None), seen
)
for view_spec in feature_view_proto.spec.source_views
]

Expand Down
106 changes: 106 additions & 0 deletions sdk/python/tests/unit/test_feature_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
from feast.feature_view import FeatureView
from feast.field import Field
from feast.infra.offline_stores.file_source import FileSource
from feast.protos.feast.core.FeatureView_pb2 import FeatureView as FeatureViewProto
from feast.protos.feast.core.FeatureView_pb2 import (
FeatureViewMeta as FeatureViewMetaProto,
)
from feast.protos.feast.core.FeatureView_pb2 import (
FeatureViewSpec as FeatureViewSpecProto,
)
from feast.protos.feast.types.Value_pb2 import ValueType
from feast.types import Float32
from feast.utils import _utc_now, make_tzaware
Expand Down Expand Up @@ -192,3 +199,102 @@ def test_update_materialization_intervals():
second_updated_feature_view.materialization_intervals[0][1]
== updated_feature_view.materialization_intervals[0][1]
)


def test_create_feature_view_with_chained_views():
file_source = FileSource(name="my-file-source", path="test.parquet")
sink_source = FileSource(name="my-sink-source", path="sink.parquet")
feature_view_1 = FeatureView(
name="my-feature-view-1",
entities=[],
schema=[Field(name="feature1", dtype=Float32)],
source=file_source,
)
feature_view_2 = FeatureView(
name="my-feature-view-2",
entities=[],
schema=[Field(name="feature2", dtype=Float32)],
source=feature_view_1,
sink_source=sink_source,
)
feature_view_3 = FeatureView(
name="my-feature-view-3",
entities=[],
schema=[Field(name="feature3", dtype=Float32)],
source=[feature_view_1, feature_view_2],
sink_source=sink_source,
)

assert feature_view_2.name == "my-feature-view-2"
assert feature_view_2.schema == [Field(name="feature2", dtype=Float32)]
assert feature_view_2.batch_source == sink_source
assert feature_view_2.source_views == [feature_view_1]

assert feature_view_3.name == "my-feature-view-3"
assert feature_view_3.schema == [Field(name="feature3", dtype=Float32)]
assert feature_view_3.batch_source == sink_source
assert feature_view_3.source_views == [feature_view_1, feature_view_2]


def test_feature_view_to_proto_with_cycle():
fv_a = FeatureView(
name="fv_a",
schema=[Field(name="feature1", dtype=Float32)],
source=FileSource(name="source_a", path="source_a.parquet"),
ttl=timedelta(days=1),
entities=[],
)

fv_b = FeatureView(
name="fv_b",
schema=[Field(name="feature1", dtype=Float32)],
source=[fv_a],
ttl=timedelta(days=1),
entities=[],
sink_source=FileSource(name="sink_source_b", path="sink_b.parquet"),
)

fv_a = FeatureView(
name="fv_a",
schema=[Field(name="feature1", dtype=Float32)],
source=[fv_b],
ttl=timedelta(days=1),
entities=[],
sink_source=FileSource(name="sink_source_a", path="sink_a.parquet"),
)
with pytest.raises(
ValueError, match="Cycle detected during serialization of FeatureView: fv_a"
):
fv_a.to_proto()


def test_feature_view_from_proto_with_cycle():
# Create spec_a
spec_a = FeatureViewSpecProto()
spec_a.name = "fv_a"
spec_a.entities.append("entity_id")
spec_a.features.append(Field(name="a", dtype=Float32).to_proto())
spec_a.batch_source.CopyFrom(
FileSource(name="source_a", path="source_a.parquet").to_proto()
)

# Create spec_b
spec_b = FeatureViewSpecProto()
spec_b.name = "fv_b"
spec_b.entities.append("entity_id")
spec_b.features.append(Field(name="b", dtype=Float32).to_proto())
spec_b.batch_source.CopyFrom(
FileSource(name="source_b", path="source_b.parquet").to_proto()
)

# Create the cycle: A → B → A
spec_b.source_views.append(spec_a)
spec_a.source_views.append(spec_b)

# Trigger deserialization
proto_a = FeatureViewProto(spec=spec_a, meta=FeatureViewMetaProto())

with pytest.raises(
ValueError, match="Cycle detected while deserializing FeatureView: fv_a"
):
FeatureView.from_proto(proto_a)
Loading