Skip to content

Commit aade119

Browse files
authored
docs: Add end-to-end registry deletion lifecycle example and unit test (#5360) (#6504)
* docs: add end-to-end registry deletion lifecycle snippet (#5360) The existing registry deletion docs cover the CLI and individual Python SDK delete methods, but lack a single copy-pasteable example showing the full create -> verify -> delete -> confirm flow. - Add an 'End-to-end example' snippet to registry.md that lists a feature view, deletes it with delete_feature_view(), and lists again to confirm. - Add a unit test for FeatureView deletion via apply(objects_to_delete=..., partial=False) to guard the programmatic deletion path. Signed-off-by: Linda Oraegbunam <obielinda@gmail.com> * test: cover delete_feature_view, the API the docs example uses Addresses review feedback on #6504: the new docs snippet demonstrates store.delete_feature_view(name), but the only test added went through apply(objects_to_delete=..., partial=False), so the documented API was still untested. - Add test_delete_feature_view, mirroring the registry.md snippet step for step: list, delete by name, list again, then assert get_feature_view raises FeatureViewNotFoundException. - Add test_delete_feature_view_raises_when_missing, covering the FeatureViewNotFoundException that delete_feature_view's own docstring promises for an unregistered name. - Keep the apply(objects_to_delete=...) test for the `feast apply` path documented in the hint block, and note in its docstring that it is deliberately distinct from delete_feature_view. - Lift the shared source frame and registration into two helpers so the two lifecycle tests do not duplicate ~30 lines of setup. Signed-off-by: Linda Oraegbunam <obielinda@gmail.com> --------- Signed-off-by: Linda Oraegbunam <obielinda@gmail.com>
1 parent 6a4690a commit aade119

2 files changed

Lines changed: 140 additions & 1 deletion

File tree

‎docs/getting-started/components/registry.md‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,30 @@ store._registry.delete_validation_reference("my_validation_reference", project=s
6969
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.
7070
{% endhint %}
7171

72+
### End-to-end example
73+
74+
The following snippet shows the full lifecycle of deleting a feature view from the registry:
75+
76+
```python
77+
from feast import FeatureStore
78+
79+
store = FeatureStore(repo_path=".")
80+
81+
# 1. Verify the object exists before deletion
82+
print(store.list_batch_feature_views()) # shows my_feature_view
83+
84+
# 2. Delete the feature view
85+
store.delete_feature_view("my_feature_view")
86+
87+
# 3. Confirm it's gone
88+
print(store.list_batch_feature_views()) # my_feature_view no longer listed
89+
90+
# Trying to fetch it now raises FeatureViewNotFoundException
91+
# store.get_feature_view("my_feature_view")
92+
```
93+
94+
The same pattern works for other registry objects: list/verify the object, call the corresponding `delete_*` method, then list again to confirm the deletion.
95+
7296
## Accessing the registry from clients
7397

7498
Users can specify the registry through a `feature_store.yaml` config file, or programmatically. We often see teams

‎sdk/python/tests/unit/local_feast_tests/test_local_feature_store.py‎

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from feast.data_format import AvroFormat, ParquetFormat
1212
from feast.data_source import KafkaSource
1313
from feast.entity import Entity
14-
from feast.errors import ConflictingFeatureViewNames
14+
from feast.errors import ConflictingFeatureViewNames, FeatureViewNotFoundException
1515
from feast.feast_object import ALL_RESOURCE_TYPES
1616
from feast.feature_store import FeatureStore
1717
from feast.feature_view import DUMMY_ENTITY_ID, DUMMY_ENTITY_NAME, FeatureView
@@ -443,6 +443,121 @@ def test_apply_permissions(test_feature_store):
443443
test_feature_store.teardown()
444444

445445

446+
def _apply_feature_view_to_delete(test_feature_store, file_source):
447+
"""Register an entity and a feature view, and return the feature view."""
448+
entity = Entity(
449+
name="driver_entity", join_keys=["test_key"], value_type=ValueType.INT64
450+
)
451+
driver_fv = FeatureView(
452+
name="driver_fv_to_delete",
453+
entities=[entity],
454+
schema=[Field(name="test_key", dtype=Int64)],
455+
source=file_source,
456+
)
457+
test_feature_store.apply([entity, driver_fv])
458+
459+
fvs = test_feature_store.list_batch_feature_views()
460+
assert len(fvs) == 1
461+
assert fvs[0].name == driver_fv.name
462+
463+
return driver_fv
464+
465+
466+
def _deletion_source_dataframe():
467+
"""Build the small source frame both deletion tests register against."""
468+
now = pd.Timestamp.utcnow().round("ms")
469+
return pd.DataFrame(
470+
{
471+
"test_key": [1, 2, 1, 3, 3],
472+
"feature_value": [0.1, 0.2, 0.3, 4.0, 5.0],
473+
"ts_1": [
474+
now,
475+
now - pd.Timedelta(hours=4),
476+
now - pd.Timedelta(hours=3),
477+
now - pd.Timedelta(hours=2),
478+
now - pd.Timedelta(hours=1),
479+
],
480+
}
481+
)
482+
483+
484+
@pytest.mark.parametrize(
485+
"test_feature_store",
486+
[lazy_fixture("feature_store_with_local_registry")],
487+
)
488+
def test_delete_feature_view(test_feature_store):
489+
"""Test the delete_feature_view lifecycle documented in registry.md.
490+
491+
Mirrors the end-to-end snippet in docs/getting-started/components/registry.md:
492+
list the object, delete it by name, list again to confirm it is gone, and
493+
check that fetching it afterwards raises FeatureViewNotFoundException.
494+
"""
495+
assert isinstance(test_feature_store, FeatureStore)
496+
497+
with prep_file_source(
498+
df=_deletion_source_dataframe(), timestamp_field="ts_1"
499+
) as file_source:
500+
driver_fv = _apply_feature_view_to_delete(test_feature_store, file_source)
501+
502+
# Delete the feature view by name
503+
test_feature_store.delete_feature_view(driver_fv.name)
504+
505+
# Verify feature view is deleted
506+
assert len(test_feature_store.list_batch_feature_views()) == 0
507+
508+
# Verify get_feature_view raises FeatureViewNotFoundException
509+
with pytest.raises(FeatureViewNotFoundException):
510+
test_feature_store.get_feature_view(driver_fv.name)
511+
512+
test_feature_store.teardown()
513+
514+
515+
@pytest.mark.parametrize(
516+
"test_feature_store",
517+
[lazy_fixture("feature_store_with_local_registry")],
518+
)
519+
def test_delete_feature_view_raises_when_missing(test_feature_store):
520+
"""Deleting a feature view that was never registered raises, as documented."""
521+
assert isinstance(test_feature_store, FeatureStore)
522+
523+
with pytest.raises(FeatureViewNotFoundException):
524+
test_feature_store.delete_feature_view("feature_view_that_does_not_exist")
525+
526+
test_feature_store.teardown()
527+
528+
529+
@pytest.mark.parametrize(
530+
"test_feature_store",
531+
[lazy_fixture("feature_store_with_local_registry")],
532+
)
533+
def test_apply_delete_feature_view(test_feature_store):
534+
"""Test that a feature view can be deleted using objects_to_delete with partial=False.
535+
536+
This is the `feast apply` path called out in the hint block in registry.md,
537+
and is distinct from the delete_feature_view path covered above.
538+
"""
539+
assert isinstance(test_feature_store, FeatureStore)
540+
541+
with prep_file_source(
542+
df=_deletion_source_dataframe(), timestamp_field="ts_1"
543+
) as file_source:
544+
driver_fv = _apply_feature_view_to_delete(test_feature_store, file_source)
545+
546+
# Delete the feature view using objects_to_delete
547+
test_feature_store.apply(
548+
objects=[], objects_to_delete=[driver_fv], partial=False
549+
)
550+
551+
# Verify feature view is deleted
552+
assert len(test_feature_store.list_batch_feature_views()) == 0
553+
554+
# Verify get_feature_view raises FeatureViewNotFoundException
555+
with pytest.raises(FeatureViewNotFoundException):
556+
test_feature_store.get_feature_view(driver_fv.name)
557+
558+
test_feature_store.teardown()
559+
560+
446561
@pytest.mark.parametrize(
447562
"test_feature_store",
448563
[lazy_fixture("feature_store_with_local_registry")],

0 commit comments

Comments
 (0)