Skip to content

Commit 432c74a

Browse files
committed
feat: Making feature view source optional (#6074)
1 parent 95b9af8 commit 432c74a

File tree

4 files changed

+30
-10
lines changed

4 files changed

+30
-10
lines changed

docs/getting-started/concepts/batch-feature-view.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class BatchFeatureView(FeatureView):
2727
def __init__(
2828
*,
2929
name: str,
30-
source: Union[DataSource, FeatureView, List[FeatureView]],
30+
source: Optional[Union[DataSource, FeatureView, List[FeatureView]]] = None,
3131
sink_source: Optional[DataSource] = None,
3232
schema: Optional[List[Field]] = None,
3333
entities: Optional[List[Entity]] = None,
@@ -142,6 +142,7 @@ See:
142142
## 🛑 Gotchas
143143

144144
- `sink_source` is **required** when chaining views (i.e., `source` is another FeatureView or list of them).
145+
- `source` is optional; if omitted (`None`), the feature view has no associated batch data source.
145146
- Schema fields must be consistent with `sink_source`, `batch_source.field_mapping` if field mappings exist.
146147
- Aggregation logic must reference columns present in the raw source or transformed inputs.
147148

protos/feast/core/FeatureView.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,15 @@ message FeatureViewSpec {
6161
google.protobuf.Duration ttl = 6;
6262

6363
// Batch/Offline DataSource where this view can retrieve offline feature data.
64+
// Optional: if not set, the feature view has no associated batch data source (e.g. purely derived views).
6465
DataSource batch_source = 7;
6566

6667
// Whether these features should be served online or not
6768
// This is also used to determine whether the features should be written to the online store
6869
bool online = 8;
6970

7071
// Streaming DataSource from where this view can consume "online" feature data.
72+
// Optional: only required for streaming feature views.
7173
DataSource stream_source = 9;
7274

7375
// Description of the feature view.

sdk/python/feast/feature_view.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,8 @@ class FeatureView(BaseFeatureView):
7373
ttl: The amount of time this group of features lives. A ttl of 0 indicates that
7474
this group of features lives forever. Note that large ttl's or a ttl of 0
7575
can result in extremely computationally intensive queries.
76-
batch_source: The batch source of data where this group of features
77-
is stored. This is optional ONLY if a push source is specified as the
78-
stream_source, since push sources contain their own batch sources.
76+
batch_source: Optional batch source of data where this group of features
77+
is stored. If no source is provided, this will be None.
7978
stream_source: The stream source of data where this group of features is stored.
8079
schema: The schema of the feature view, including feature, timestamp, and entity
8180
columns. If not specified, can be inferred from the underlying data source.
@@ -97,7 +96,7 @@ class FeatureView(BaseFeatureView):
9796
name: str
9897
entities: List[str]
9998
ttl: Optional[timedelta]
100-
batch_source: DataSource
99+
batch_source: Optional[DataSource]
101100
stream_source: Optional[DataSource]
102101
source_views: Optional[List["FeatureView"]]
103102
entity_columns: List[Field]
@@ -115,7 +114,7 @@ def __init__(
115114
self,
116115
*,
117116
name: str,
118-
source: Union[DataSource, "FeatureView", List["FeatureView"]],
117+
source: Optional[Union[DataSource, "FeatureView", List["FeatureView"]]] = None,
119118
sink_source: Optional[DataSource] = None,
120119
schema: Optional[List[Field]] = None,
121120
entities: Optional[List[Entity]] = None,
@@ -133,8 +132,9 @@ def __init__(
133132
134133
Args:
135134
name: The unique name of the feature view.
136-
source: The source of data for this group of features. May be a stream source, or a batch source.
137-
If a stream source, the source should contain a batch_source for backfills & batch materialization.
135+
source (optional): The source of data for this group of features. May be a stream source,
136+
a batch source, a FeatureView, or a list of FeatureViews. If None, the feature view
137+
has no associated data source.
138138
schema (optional): The schema of the feature view, including feature, timestamp,
139139
and entity columns.
140140
# TODO: clarify that schema is only useful here...
@@ -170,7 +170,9 @@ def __init__(
170170
self.data_source: Optional[DataSource] = None
171171
self.source_views: List[FeatureView] = []
172172

173-
if isinstance(source, DataSource):
173+
if source is None:
174+
pass # data_source remains None, source_views remains []
175+
elif isinstance(source, DataSource):
174176
self.data_source = source
175177
elif isinstance(source, FeatureView):
176178
self.source_views = [source]
@@ -199,11 +201,14 @@ def __init__(
199201
elif self.data_source:
200202
# Batch source definition
201203
self.batch_source = self.data_source
202-
else:
204+
elif self.source_views:
203205
# Derived view source definition
204206
if not sink_source:
205207
raise ValueError("Derived FeatureView must specify `sink_source`.")
206208
self.batch_source = sink_source
209+
else:
210+
# source=None - no batch source
211+
self.batch_source = None
207212

208213
# Initialize features and entity columns.
209214
features: List[Field] = []

sdk/python/tests/unit/test_feature_views.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@
2323
from feast.utils import _utc_now, make_tzaware
2424

2525

26+
def test_create_feature_view_without_source():
27+
fv = FeatureView(name="test_no_source", ttl=timedelta(days=1))
28+
assert fv.batch_source is None
29+
assert fv.stream_source is None
30+
31+
proto = fv.to_proto()
32+
assert not proto.spec.HasField("batch_source")
33+
34+
fv_roundtrip = FeatureView.from_proto(proto)
35+
assert fv_roundtrip.batch_source is None
36+
37+
2638
def test_create_feature_view_with_conflicting_entities():
2739
user1 = Entity(name="user1", join_keys=["user_id"])
2840
user2 = Entity(name="user2", join_keys=["user_id"])

0 commit comments

Comments
 (0)