Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions docs/getting-started/components/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,30 @@ store._registry.delete_validation_reference("my_validation_reference", project=s
When using `feast apply` via the CLI, you can also use the `objects_to_delete` parameter with `partial=False` to delete objects as part of the apply operation. However, this is less common and typically used in automated deployment scenarios.
{% endhint %}

### End-to-end example

The following snippet shows the full lifecycle of deleting a feature view from the registry:

```python
from feast import FeatureStore

store = FeatureStore(repo_path=".")

# 1. Verify the object exists before deletion
print(store.list_batch_feature_views()) # shows my_feature_view

# 2. Delete the feature view
store.delete_feature_view("my_feature_view")

# 3. Confirm it's gone
print(store.list_batch_feature_views()) # my_feature_view no longer listed

# Trying to fetch it now raises FeatureViewNotFoundException
# store.get_feature_view("my_feature_view")
```

The same pattern works for other registry objects: list/verify the object, call the corresponding `delete_*` method, then list again to confirm the deletion.

## Accessing the registry from clients

Users can specify the registry through a `feature_store.yaml` config file, or programmatically. We often see teams
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from feast.data_format import AvroFormat, ParquetFormat
from feast.data_source import KafkaSource
from feast.entity import Entity
from feast.errors import ConflictingFeatureViewNames
from feast.errors import ConflictingFeatureViewNames, FeatureViewNotFoundException
from feast.feast_object import ALL_RESOURCE_TYPES
from feast.feature_store import FeatureStore
from feast.feature_view import DUMMY_ENTITY_ID, DUMMY_ENTITY_NAME, FeatureView
Expand Down Expand Up @@ -443,6 +443,121 @@ def test_apply_permissions(test_feature_store):
test_feature_store.teardown()


def _apply_feature_view_to_delete(test_feature_store, file_source):
"""Register an entity and a feature view, and return the feature view."""
entity = Entity(
name="driver_entity", join_keys=["test_key"], value_type=ValueType.INT64
)
driver_fv = FeatureView(
name="driver_fv_to_delete",
entities=[entity],
schema=[Field(name="test_key", dtype=Int64)],
source=file_source,
)
test_feature_store.apply([entity, driver_fv])

fvs = test_feature_store.list_batch_feature_views()
assert len(fvs) == 1
assert fvs[0].name == driver_fv.name

return driver_fv


def _deletion_source_dataframe():
"""Build the small source frame both deletion tests register against."""
now = pd.Timestamp.utcnow().round("ms")
return pd.DataFrame(
{
"test_key": [1, 2, 1, 3, 3],
"feature_value": [0.1, 0.2, 0.3, 4.0, 5.0],
"ts_1": [
now,
now - pd.Timedelta(hours=4),
now - pd.Timedelta(hours=3),
now - pd.Timedelta(hours=2),
now - pd.Timedelta(hours=1),
],
}
)


@pytest.mark.parametrize(
"test_feature_store",
[lazy_fixture("feature_store_with_local_registry")],
)
def test_delete_feature_view(test_feature_store):
"""Test the delete_feature_view lifecycle documented in registry.md.

Mirrors the end-to-end snippet in docs/getting-started/components/registry.md:
list the object, delete it by name, list again to confirm it is gone, and
check that fetching it afterwards raises FeatureViewNotFoundException.
"""
assert isinstance(test_feature_store, FeatureStore)

with prep_file_source(
df=_deletion_source_dataframe(), timestamp_field="ts_1"
) as file_source:
driver_fv = _apply_feature_view_to_delete(test_feature_store, file_source)

# Delete the feature view by name
test_feature_store.delete_feature_view(driver_fv.name)

# Verify feature view is deleted
assert len(test_feature_store.list_batch_feature_views()) == 0

# Verify get_feature_view raises FeatureViewNotFoundException
with pytest.raises(FeatureViewNotFoundException):
test_feature_store.get_feature_view(driver_fv.name)

test_feature_store.teardown()


@pytest.mark.parametrize(
"test_feature_store",
[lazy_fixture("feature_store_with_local_registry")],
)
def test_delete_feature_view_raises_when_missing(test_feature_store):
"""Deleting a feature view that was never registered raises, as documented."""
assert isinstance(test_feature_store, FeatureStore)

with pytest.raises(FeatureViewNotFoundException):
test_feature_store.delete_feature_view("feature_view_that_does_not_exist")

test_feature_store.teardown()


@pytest.mark.parametrize(
"test_feature_store",
[lazy_fixture("feature_store_with_local_registry")],
)
def test_apply_delete_feature_view(test_feature_store):
"""Test that a feature view can be deleted using objects_to_delete with partial=False.

This is the `feast apply` path called out in the hint block in registry.md,
and is distinct from the delete_feature_view path covered above.
"""
assert isinstance(test_feature_store, FeatureStore)

with prep_file_source(
df=_deletion_source_dataframe(), timestamp_field="ts_1"
) as file_source:
driver_fv = _apply_feature_view_to_delete(test_feature_store, file_source)

# Delete the feature view using objects_to_delete
test_feature_store.apply(

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.

test is using feast apply to delete while docs and docstrings says objects_to_delete or delete_feature_view.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch, you're right — thanks. Fixed in 92523ec38.

The docs snippet demonstrates store.delete_feature_view(name), but the only test I added went through apply(objects_to_delete=[...], partial=False), so the API the example actually teaches had no coverage at all.

Rather than swap one for the other, I split them, since registry.md legitimately documents both routes:

  • test_delete_feature_view — mirrors the end-to-end snippet step for step: list, delete by name, list again, then assert get_feature_view raises FeatureViewNotFoundException.
  • test_delete_feature_view_raises_when_missing — covers the FeatureViewNotFoundException that delete_feature_view's own docstring promises for a name that was never registered.
  • test_apply_delete_feature_view — keeps the objects_to_delete / partial=False path from the hint block above the example, with a docstring noting it is deliberately the feast apply route and distinct from the one above.

I also lifted the shared source frame and registration into two small helpers, so the two lifecycle tests don't duplicate ~30 lines of setup.

Verified locally: the 3 deletion tests pass, and the file is at 24 passed. The 2 failures in test_apply_stream_feature_view / test_apply_stream_feature_view_udf are pre-existing on master — identical with my changes stashed. ruff check and ruff format --check are both clean.

The branch was 151 commits behind, so I rebased onto current master in the same push; that clears the previous out-of-date state.

objects=[], objects_to_delete=[driver_fv], partial=False
)

# Verify feature view is deleted
assert len(test_feature_store.list_batch_feature_views()) == 0

# Verify get_feature_view raises FeatureViewNotFoundException
with pytest.raises(FeatureViewNotFoundException):
test_feature_store.get_feature_view(driver_fv.name)

test_feature_store.teardown()


@pytest.mark.parametrize(
"test_feature_store",
[lazy_fixture("feature_store_with_local_registry")],
Expand Down