Skip to content

Commit 1c59bba

Browse files
committed
fix: Fixed the entity to on-demand feature view relationship
Signed-off-by: ntkathole <[email protected]>
1 parent 7f10151 commit 1c59bba

File tree

5 files changed

+94
-10
lines changed

5 files changed

+94
-10
lines changed

sdk/python/feast/api/registry/rest/entities.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ def get_entity(
109109
seen_ds_names = set()
110110
for rel in relationships:
111111
if rel.get("target", {}).get("type") == "dataSource":
112-
ds_name = rel["target"]["name"]
113-
if ds_name not in seen_ds_names:
112+
ds_name = rel["target"].get("name")
113+
if ds_name and ds_name not in seen_ds_names:
114114
ds_obj = ds_map.get(ds_name)
115115
if ds_obj:
116116
data_source_objs.append(ds_obj)

sdk/python/feast/lineage/registry_lineage.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,31 @@ def _parse_direct_relationships(self, registry: Registry) -> List[EntityRelation
172172

173173
# OnDemand FeatureView: Feature -> OnDemandFeatureView relationships
174174
for odfv in registry.on_demand_feature_views:
175-
if hasattr(odfv, "spec") and odfv.spec and hasattr(odfv.spec, "features"):
176-
for feature in odfv.spec.features:
177-
relationships.append(
178-
EntityRelation(
179-
source=EntityReference(
180-
FeastObjectType.FEATURE, feature.name
181-
),
175+
if hasattr(odfv, "spec") and odfv.spec:
176+
# Entity relationships
177+
if hasattr(odfv.spec, "entities"):
178+
for entity_name in odfv.spec.entities:
179+
rel = EntityRelation(
180+
source=EntityReference(FeastObjectType.ENTITY, entity_name),
182181
target=EntityReference(
183182
FeastObjectType.FEATURE_VIEW, odfv.spec.name
184183
),
185184
)
186-
)
185+
relationships.append(rel)
186+
187+
# Feature -> OnDemandFeatureView relationships
188+
if hasattr(odfv.spec, "features"):
189+
for feature in odfv.spec.features:
190+
relationships.append(
191+
EntityRelation(
192+
source=EntityReference(
193+
FeastObjectType.FEATURE, feature.name
194+
),
195+
target=EntityReference(
196+
FeastObjectType.FEATURE_VIEW, odfv.spec.name
197+
),
198+
)
199+
)
187200

188201
# OnDemand FeatureView relationships
189202
for odfv in registry.on_demand_feature_views:

sdk/python/tests/unit/api/test_api_rest_registry.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,7 @@ def test_metrics_popular_tags_via_rest(fastapi_test_app):
16531653
assert "feature_views" in tag_info
16541654
assert "total_feature_views" in tag_info
16551655

1656+
16561657
def test_all_endpoints_return_404_for_invalid_objects(fastapi_test_app):
16571658
"""Test that all REST API endpoints return 404 errors when objects are not found."""
16581659

sdk/python/tests/unit/infra/test_registry_lineage.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,61 @@ def test_on_demand_feature_view_lineage(self):
231231
assert isinstance(relationships, list)
232232
assert isinstance(indirect_relationships, list)
233233

234+
def test_on_demand_feature_view_entity_relationships(self):
235+
"""Test entity relationships with on-demand feature views."""
236+
registry = Registry()
237+
238+
# Create entities
239+
entity1_spec = EntitySpecV2(name="user_id")
240+
entity1 = Entity(spec=entity1_spec)
241+
242+
entity2_spec = EntitySpecV2(name="dummy")
243+
entity2 = Entity(spec=entity2_spec)
244+
245+
registry.entities.extend([entity1, entity2])
246+
247+
# Create on-demand feature view with entities
248+
odfv_spec = OnDemandFeatureViewSpec(name="transformed_features")
249+
odfv_spec.entities.append("user_id")
250+
odfv_spec.entities.append("dummy")
251+
odfv = OnDemandFeatureView(spec=odfv_spec)
252+
registry.on_demand_feature_views.append(odfv)
253+
254+
# Generate lineage
255+
lineage_generator = RegistryLineageGenerator()
256+
direct_relationships, indirect_relationships = (
257+
lineage_generator.generate_lineage(registry)
258+
)
259+
260+
# Filter Entity -> OnDemandFeatureView relationships
261+
entity_to_odfv_relationships = [
262+
rel
263+
for rel in direct_relationships
264+
if rel.source.type == FeastObjectType.ENTITY
265+
and rel.target.type == FeastObjectType.FEATURE_VIEW
266+
and rel.target.name == "transformed_features"
267+
]
268+
269+
# Should have 2 relationships: user_id -> transformed_features, dummy -> transformed_features
270+
assert len(entity_to_odfv_relationships) == 2
271+
272+
# Check specific relationships
273+
relationship_pairs = {
274+
(rel.source.name, rel.target.name) for rel in entity_to_odfv_relationships
275+
}
276+
277+
expected_pairs = {
278+
("user_id", "transformed_features"),
279+
("dummy", "transformed_features"),
280+
}
281+
282+
assert relationship_pairs == expected_pairs
283+
284+
# Test relationship types
285+
for rel in entity_to_odfv_relationships:
286+
assert rel.source.type == FeastObjectType.ENTITY
287+
assert rel.target.type == FeastObjectType.FEATURE_VIEW
288+
234289
def test_stream_feature_view_lineage(self):
235290
"""Test lineage with stream feature views."""
236291
registry = Registry()

ui/src/parsers/parseEntityRelationships.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,21 @@ const parseEntityRelationships = (objects: feast.core.Registry) => {
5757
});
5858

5959
objects.onDemandFeatureViews?.forEach((fv) => {
60+
// Entity relationships
61+
fv.spec?.entities?.forEach((ent) => {
62+
links.push({
63+
source: {
64+
type: FEAST_FCO_TYPES["entity"],
65+
name: ent,
66+
},
67+
target: {
68+
type: FEAST_FCO_TYPES["featureView"],
69+
name: fv.spec?.name!,
70+
},
71+
});
72+
});
73+
74+
// Data source relationships
6075
Object.values(fv.spec?.sources!).forEach(
6176
(input: { [key: string]: any }) => {
6277
if (input.requestDataSource) {

0 commit comments

Comments
 (0)