Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
85a18dc
feat: Add enabled/disabled toggle for feature views
rpathade May 13, 2026
5e98c7f
feat: Add demo noteboooks for users
ntkathole May 8, 2026
f735cd2
feat: Add CLI enable/disable commands and registry metadata support
rpathade May 15, 2026
426ae09
Added features
rpathade May 16, 2026
365ecb4
fix(compute-engine/local): Honor field_mapping on join keys in dedup …
1fanwang May 13, 2026
5b94d4d
feat: Add Prometheus gauges for FeatureStore installation telemetry (…
ntkathole May 13, 2026
b5297b8
docs: Rename Atlas Vector Search to MongoDB Vector Search and fix cod…
jvincent-mongodb May 13, 2026
04d500c
feat(dynamodb): Use ProjectionExpression when requested_features is set
Jwrede May 3, 2026
c9e368b
fix(dynamodb): Fix mypy type for _build_projection_expression return
Jwrede May 3, 2026
de050ab
fix(bigquery): Enable list inference for parquet loads in offline_wri…
Jwrede May 3, 2026
78fcd72
fix(trino): Clean up temporary entity tables after retrieval (#6381)
Jwrede May 14, 2026
2014acd
feat(bigquery): Support DATE-type event timestamp columns (#6362)
Jwrede May 14, 2026
5e69e5f
fix: Fixes for ray source
ntkathole May 14, 2026
53e4a92
feat: Expose registry endpoints on feature server for MCP access
patelchaitany Apr 21, 2026
e9791b0
fix: Revert state propagation to always update in _update_metadata_fi…
rpathade May 16, 2026
f7fd0f8
fix: Recompile protos for protobuf 4.x compatibility and fix state ma…
rpathade May 18, 2026
a4f7ab8
feat: Add unit tests for state machine and clean up lazy imports in r…
rpathade May 18, 2026
3aa8778
fix: Address review comments for feature view state management
rpathade May 20, 2026
e626433
fix: Resolve integration test failures in apply loop
rpathade May 20, 2026
8130b63
fix: Resolve integration test failures in apply loop
rpathade May 20, 2026
2b54bd0
Apply suggestion from @ntkathole
rpathade May 21, 2026
cb33b6d
fix: Resolve review comments for feature_store
rpathade May 21, 2026
f3b94eb
fix: Resolve review comments for feature_views.py
rpathade May 21, 2026
a4c85dc
Merge branch 'master' into feat/feature-view-enabled-disabled-v2
rpathade May 21, 2026
abb6a41
Merge remote-tracking branch 'origin/master' into feat/feature-view-e…
rpathade May 21, 2026
f4dd0a4
feat: Add FeatureStore methods and update describe for enabled/state
rpathade May 22, 2026
a3f65b0
Merge branch 'master' into feat/feature-view-enabled-disabled-v2
rpathade May 22, 2026
efd41a6
fix: Add type: ignore comments for mypy on BaseFeatureView attr access
rpathade May 22, 2026
436a712
fix: Remove REST API endpoints for enable/disable/set-state (deferred…
rpathade May 22, 2026
7110346
Merge remote-tracking branch 'origin/master' into feat/feature-view-e…
rpathade May 22, 2026
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
28 changes: 28 additions & 0 deletions protos/feast/core/FeatureView.proto
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ import "feast/core/DataSource.proto";
import "feast/core/Feature.proto";
import "feast/core/Transformation.proto";

// Lifecycle state of a feature view.
enum FeatureViewState {
// Default value for backward compatibility. Treated as AVAILABLE_ONLINE
// for existing feature views that predate the state machine.
STATE_UNSPECIFIED = 0;

// Feature view has been registered via feast apply but no data is available.
CREATED = 1;

// Feature engineering / offline data generation is complete.
// The feature view is ready to be materialized.
GENERATED = 2;

// Materialization is currently in progress.
MATERIALIZING = 3;

// Materialization completed. Features are available in the online store.
AVAILABLE_ONLINE = 4;
}

message FeatureView {
// User-specified specifications of this feature view.
FeatureViewSpec spec = 1;
Expand Down Expand Up @@ -100,6 +120,11 @@ message FeatureViewSpec {

// Organizational unit that owns this feature view (e.g. "ads", "search").
string org = 19;

// Whether this feature view is disabled for serving and materialization.
// When true, the feature view will not serve online features or be materialized.
// Defaults to false (enabled) for backward compatibility.
bool disabled = 20;
}

message FeatureViewMeta {
Expand All @@ -117,6 +142,9 @@ message FeatureViewMeta {

// Auto-generated UUID identifying this specific version.
string version_id = 5;

// Lifecycle state of this feature view.
FeatureViewState state = 6;
}

message MaterializationInterval {
Expand Down
8 changes: 8 additions & 0 deletions protos/feast/core/OnDemandFeatureView.proto
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ message OnDemandFeatureViewSpec {

// Organizational unit that owns this feature view (e.g. "ads", "search").
string org = 18;

// Whether this feature view is disabled for serving.
// When true, the feature view will not serve features.
// Defaults to false (enabled) for backward compatibility.
bool disabled = 19;
}

message OnDemandFeatureViewMeta {
Expand All @@ -94,6 +99,9 @@ message OnDemandFeatureViewMeta {

// Auto-generated UUID identifying this specific version.
string version_id = 4;

// Lifecycle state of this feature view.
FeatureViewState state = 5;
}

message OnDemandSource {
Expand Down
5 changes: 5 additions & 0 deletions protos/feast/core/StreamFeatureView.proto
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,10 @@ message StreamFeatureViewSpec {

// Organizational unit that owns this stream feature view (e.g. "ads", "search").
string org = 22;

// Whether this feature view is disabled for serving and materialization.
// When true, the feature view will not serve online features or be materialized.
// Defaults to false (enabled) for backward compatibility.
bool disabled = 23;
}

3 changes: 2 additions & 1 deletion sdk/python/feast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from .feature import Feature
from .feature_service import FeatureService
from .feature_store import FeatureStore
from .feature_view import FeatureView
from .feature_view import FeatureView, FeatureViewState
from .field import Field
from .on_demand_feature_view import OnDemandFeatureView
from .project import Project
Expand Down Expand Up @@ -53,6 +53,7 @@
"FeatureService",
"FeatureStore",
"FeatureView",
"FeatureViewState",
"OnDemandFeatureView",
"RepoConfig",
"StreamFeatureView",
Expand Down
134 changes: 127 additions & 7 deletions sdk/python/feast/cli/feature_views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import sys

import click
import yaml

from feast import utils
from feast.cli.cli_options import tagsOption
from feast.errors import FeastObjectNotFoundException
from feast.feature_view import FeatureView
from feast.feature_view import (
_VALID_STATE_TRANSITIONS,
FeatureView,
FeatureViewState,
)
from feast.on_demand_feature_view import OnDemandFeatureView
from feast.repo_operations import create_feature_store

Expand Down Expand Up @@ -32,11 +38,13 @@ def feature_view_describe(ctx: click.Context, name: str):
print(e)
exit(1)

print(
yaml.dump(
yaml.safe_load(str(feature_view)), default_flow_style=False, sort_keys=False
)
)
data = yaml.safe_load(str(feature_view))
# Always show enabled and state even when they are at default values.
if hasattr(feature_view, "enabled"):
data["enabled"] = feature_view.enabled
if hasattr(feature_view, "state"):
data["state"] = feature_view.state.name
print(yaml.dump(data, default_flow_style=False, sort_keys=False))


@feature_views_cmd.command(name="list")
Expand All @@ -59,17 +67,129 @@ def feature_view_list(ctx: click.Context, tags: list[str]):
elif isinstance(feature_view, OnDemandFeatureView):
for backing_fv in feature_view.source_feature_view_projections.values():
entities.update(store.get_feature_view(backing_fv.name).entities)
enabled = getattr(feature_view, "enabled", True)
state = getattr(feature_view, "state", FeatureViewState.STATE_UNSPECIFIED)
state_display = (
state.name if isinstance(state, FeatureViewState) else str(state)
)
table.append(
[
feature_view.name,
entities if len(entities) > 0 else "n/a",
type(feature_view).__name__,
"Yes" if enabled else "No",
state_display,
]
)

from tabulate import tabulate

print(tabulate(table, headers=["NAME", "ENTITIES", "TYPE"], tablefmt="plain"))
print(
tabulate(
table,
headers=["NAME", "ENTITIES", "TYPE", "ENABLED", "STATE"],
tablefmt="plain",
)
)


@feature_views_cmd.command("enable")
@click.argument("name", type=click.STRING)
@click.pass_context
def feature_view_enable(ctx: click.Context, name: str):
"""
Enable a feature view for serving and materialization.
"""
store = create_feature_store(ctx)
try:
fv = store.registry.get_any_feature_view(name, store.project)
except FeastObjectNotFoundException as e:
print(e)
sys.exit(1)

if not isinstance(fv, (FeatureView, OnDemandFeatureView)):
print(f"Feature view '{name}' does not support enable/disable.")
return

if fv.enabled:
print(f"Feature view '{name}' is already enabled.")
return

fv.enabled = True
store.registry.apply_feature_view(fv, store.project)
print(f"Feature view '{name}' has been enabled.")


@feature_views_cmd.command("disable")
@click.argument("name", type=click.STRING)
@click.pass_context
def feature_view_disable(ctx: click.Context, name: str):
"""
Disable a feature view to prevent serving and materialization.
"""
store = create_feature_store(ctx)
try:
fv = store.registry.get_any_feature_view(name, store.project)
except FeastObjectNotFoundException as e:
print(e)
sys.exit(1)

if not isinstance(fv, (FeatureView, OnDemandFeatureView)):
print(f"Feature view '{name}' does not support enable/disable.")
return

if not fv.enabled:
print(f"Feature view '{name}' is already disabled.")
return

fv.enabled = False
store.registry.apply_feature_view(fv, store.project)
print(f"Feature view '{name}' has been disabled.")


@feature_views_cmd.command("set-state")
@click.argument("name", type=click.STRING)
@click.argument(
"state",
type=click.Choice(
["CREATED", "GENERATED", "MATERIALIZING", "AVAILABLE_ONLINE"],
case_sensitive=False,
),
)
@click.pass_context
def feature_view_set_state(ctx: click.Context, name: str, state: str):
"""
Set the lifecycle state of a feature view.
"""
store = create_feature_store(ctx)
try:
fv = store.registry.get_any_feature_view(name, store.project)
except FeastObjectNotFoundException as e:
print(e)
sys.exit(1)

if not isinstance(fv, (FeatureView, OnDemandFeatureView)):
print(f"Feature view '{name}' does not support state management.")
return

new_state = FeatureViewState[state.upper()]
if fv.state == new_state:
print(f"Feature view '{name}' is already in state {new_state.name}.")
return

if not fv.state.can_transition_to(new_state):
current = fv.state.name
allowed = _VALID_STATE_TRANSITIONS.get(fv.state, set())
allowed_names = ", ".join(sorted(s.name for s in allowed)) or "none"
print(
f"Invalid state transition: {current} -> {new_state.name}. "
f"Allowed transitions from {current}: {allowed_names}."
)
return

fv.state = new_state
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs validation, It's better to add Valid transitions maps so that user is not able to bypass or skip some state

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added _VALID_STATE_TRANSITIONS map and can_transition_to() validation in the set-state command. Invalid transitions are now rejected with a message showing allowed transitions.

store.registry.apply_feature_view(fv, store.project)
print(f"Feature view '{name}' state set to {new_state.name}.")


@feature_views_cmd.command("list-versions")
Expand Down
Loading
Loading