Skip to content

Commit bea83e7

Browse files
authored
feat: Added relationship support to all API endpoints (#5496)
Signed-off-by: ntkathole <[email protected]>
1 parent b9ac90b commit bea83e7

File tree

12 files changed

+1015
-54
lines changed

12 files changed

+1015
-54
lines changed

docs/reference/feature-servers/registry-server.md

Lines changed: 266 additions & 13 deletions
Large diffs are not rendered by default.

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from feast.api.registry.rest.rest_utils import (
77
create_grpc_pagination_params,
88
create_grpc_sorting_params,
9+
get_object_relationships,
910
get_pagination_params,
11+
get_relationships_for_objects,
1012
get_sorting_params,
1113
grpc_call,
1214
parse_tags,
@@ -22,6 +24,9 @@ def get_data_source_router(grpc_handler) -> APIRouter:
2224
@router.get("/data_sources")
2325
def list_data_sources(
2426
project: str = Query(...),
27+
include_relationships: bool = Query(
28+
False, description="Include relationships for each data source"
29+
),
2530
allow_cache: bool = Query(default=True),
2631
tags: Dict[str, str] = Depends(parse_tags),
2732
pagination_params: dict = Depends(get_pagination_params),
@@ -34,24 +39,46 @@ def list_data_sources(
3439
pagination=create_grpc_pagination_params(pagination_params),
3540
sorting=create_grpc_sorting_params(sorting_params),
3641
)
37-
3842
response = grpc_call(grpc_handler.ListDataSources, req)
39-
return {
40-
"data_sources": response.get("dataSources", []),
43+
data_sources = response.get("dataSources", [])
44+
45+
result = {
46+
"data_sources": data_sources,
4147
"pagination": response.get("pagination", {}),
4248
}
4349

50+
if include_relationships:
51+
relationships = get_relationships_for_objects(
52+
grpc_handler, data_sources, "dataSource", project, allow_cache
53+
)
54+
result["relationships"] = relationships
55+
56+
return result
57+
4458
@router.get("/data_sources/{name}")
4559
def get_data_source(
4660
name: str,
4761
project: str = Query(...),
62+
include_relationships: bool = Query(
63+
False, description="Include relationships for this data source"
64+
),
4865
allow_cache: bool = Query(default=True),
4966
):
5067
req = RegistryServer_pb2.GetDataSourceRequest(
5168
name=name,
5269
project=project,
5370
allow_cache=allow_cache,
5471
)
55-
return grpc_call(grpc_handler.GetDataSource, req)
72+
data_source = grpc_call(grpc_handler.GetDataSource, req)
73+
74+
result = data_source
75+
76+
if include_relationships:
77+
relationships = get_object_relationships(
78+
grpc_handler, "dataSource", name, project, allow_cache
79+
)
80+
result["relationships"] = relationships
81+
82+
return result
5683

5784
return router

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
from feast.api.registry.rest.rest_utils import (
66
create_grpc_pagination_params,
77
create_grpc_sorting_params,
8+
get_object_relationships,
89
get_pagination_params,
10+
get_relationships_for_objects,
911
get_sorting_params,
1012
grpc_call,
1113
)
@@ -21,6 +23,9 @@ def get_entity_router(grpc_handler) -> APIRouter:
2123
def list_entities(
2224
project: str = Query(...),
2325
allow_cache: bool = Query(default=True),
26+
include_relationships: bool = Query(
27+
False, description="Include relationships for each entity"
28+
),
2429
pagination_params: dict = Depends(get_pagination_params),
2530
sorting_params: dict = Depends(get_sorting_params),
2631
):
@@ -30,20 +35,46 @@ def list_entities(
3035
pagination=create_grpc_pagination_params(pagination_params),
3136
sorting=create_grpc_sorting_params(sorting_params),
3237
)
38+
response = grpc_call(grpc_handler.ListEntities, req)
39+
entities = response.get("entities", [])
3340

34-
return grpc_call(grpc_handler.ListEntities, req)
41+
result = {
42+
"entities": entities,
43+
"pagination": response.get("pagination", {}),
44+
}
45+
46+
if include_relationships:
47+
relationships = get_relationships_for_objects(
48+
grpc_handler, entities, "entity", project, allow_cache
49+
)
50+
result["relationships"] = relationships
51+
52+
return result
3553

3654
@router.get("/entities/{name}")
3755
def get_entity(
3856
name: str,
3957
project: str = Query(...),
58+
include_relationships: bool = Query(
59+
False, description="Include relationships for this entity"
60+
),
4061
allow_cache: bool = Query(default=True),
4162
):
4263
req = RegistryServer_pb2.GetEntityRequest(
4364
name=name,
4465
project=project,
4566
allow_cache=allow_cache,
4667
)
47-
return grpc_call(grpc_handler.GetEntity, req)
68+
entity = grpc_call(grpc_handler.GetEntity, req)
69+
70+
result = entity
71+
72+
if include_relationships:
73+
relationships = get_object_relationships(
74+
grpc_handler, "entity", name, project, allow_cache
75+
)
76+
result["relationships"] = relationships
77+
78+
return result
4879

4980
return router

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
from feast.api.registry.rest.rest_utils import (
66
create_grpc_pagination_params,
77
create_grpc_sorting_params,
8+
get_object_relationships,
89
get_pagination_params,
10+
get_relationships_for_objects,
911
get_sorting_params,
1012
grpc_call,
1113
parse_tags,
@@ -19,6 +21,9 @@ def get_feature_service_router(grpc_handler) -> APIRouter:
1921
@router.get("/feature_services")
2022
def list_feature_services(
2123
project: str = Query(...),
24+
include_relationships: bool = Query(
25+
False, description="Include relationships for each feature service"
26+
),
2227
allow_cache: bool = Query(default=True),
2328
tags: Dict[str, str] = Depends(parse_tags),
2429
pagination_params: dict = Depends(get_pagination_params),
@@ -31,19 +36,46 @@ def list_feature_services(
3136
pagination=create_grpc_pagination_params(pagination_params),
3237
sorting=create_grpc_sorting_params(sorting_params),
3338
)
34-
return grpc_call(grpc_handler.ListFeatureServices, req)
39+
response = grpc_call(grpc_handler.ListFeatureServices, req)
40+
feature_services = response.get("featureServices", [])
41+
42+
result = {
43+
"featureServices": feature_services,
44+
"pagination": response.get("pagination", {}),
45+
}
46+
47+
if include_relationships:
48+
relationships = get_relationships_for_objects(
49+
grpc_handler, feature_services, "featureService", project, allow_cache
50+
)
51+
result["relationships"] = relationships
52+
53+
return result
3554

3655
@router.get("/feature_services/{name}")
3756
def get_feature_service(
3857
name: str,
3958
project: str = Query(...),
59+
include_relationships: bool = Query(
60+
False, description="Include relationships for this feature service"
61+
),
4062
allow_cache: bool = Query(default=True),
4163
):
4264
req = RegistryServer_pb2.GetFeatureServiceRequest(
4365
name=name,
4466
project=project,
4567
allow_cache=allow_cache,
4668
)
47-
return grpc_call(grpc_handler.GetFeatureService, req)
69+
feature_service = grpc_call(grpc_handler.GetFeatureService, req)
70+
71+
result = feature_service
72+
73+
if include_relationships:
74+
relationships = get_object_relationships(
75+
grpc_handler, "featureService", name, project, allow_cache
76+
)
77+
result["relationships"] = relationships
78+
79+
return result
4880

4981
return router

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

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,42 @@
55
from feast.api.registry.rest.rest_utils import (
66
create_grpc_pagination_params,
77
create_grpc_sorting_params,
8+
get_object_relationships,
89
get_pagination_params,
10+
get_relationships_for_objects,
911
get_sorting_params,
1012
grpc_call,
1113
parse_tags,
1214
)
1315
from feast.registry_server import RegistryServer_pb2
1416

1517

18+
def _extract_feature_view_from_any(any_feature_view: dict) -> dict:
19+
"""Extract the specific feature view type and data from an AnyFeatureView object.
20+
21+
Args:
22+
any_feature_view: Dictionary containing the AnyFeatureView data
23+
24+
Returns:
25+
Dictionary with 'type' and feature view data, or empty dict if no valid type found
26+
"""
27+
for key, value in any_feature_view.items():
28+
if value:
29+
return {"type": key, **value}
30+
31+
return {}
32+
33+
1634
def get_feature_view_router(grpc_handler) -> APIRouter:
1735
router = APIRouter()
1836

1937
@router.get("/feature_views/{name}")
2038
def get_any_feature_view(
2139
name: str,
2240
project: str = Query(...),
41+
include_relationships: bool = Query(
42+
False, description="Include relationships for this feature view"
43+
),
2344
allow_cache: bool = Query(True),
2445
):
2546
req = RegistryServer_pb2.GetAnyFeatureViewRequest(
@@ -29,18 +50,24 @@ def get_any_feature_view(
2950
)
3051
response = grpc_call(grpc_handler.GetAnyFeatureView, req)
3152
any_feature_view = response.get("anyFeatureView", {})
32-
feature_view = (
33-
any_feature_view.get("featureView")
34-
or any_feature_view.get("onDemandFeatureView")
35-
or any_feature_view.get("streamFeatureView")
36-
or {}
37-
)
38-
return {"featureView": feature_view}
53+
54+
result = _extract_feature_view_from_any(any_feature_view)
55+
56+
if include_relationships:
57+
relationships = get_object_relationships(
58+
grpc_handler, "featureView", name, project, allow_cache
59+
)
60+
result["relationships"] = relationships
61+
62+
return result
3963

4064
@router.get("/feature_views")
4165
def list_all_feature_views(
4266
project: str = Query(...),
4367
allow_cache: bool = Query(default=True),
68+
include_relationships: bool = Query(
69+
False, description="Include relationships for each feature view"
70+
),
4471
tags: Dict[str, str] = Depends(parse_tags),
4572
pagination_params: dict = Depends(get_pagination_params),
4673
sorting_params: dict = Depends(get_sorting_params),
@@ -52,6 +79,27 @@ def list_all_feature_views(
5279
pagination=create_grpc_pagination_params(pagination_params),
5380
sorting=create_grpc_sorting_params(sorting_params),
5481
)
55-
return grpc_call(grpc_handler.ListAllFeatureViews, req)
82+
response = grpc_call(grpc_handler.ListAllFeatureViews, req)
83+
any_feature_views = response.get("featureViews", [])
84+
85+
# Extract the specific type of feature view from each AnyFeatureView
86+
feature_views = []
87+
for any_feature_view in any_feature_views:
88+
feature_view = _extract_feature_view_from_any(any_feature_view)
89+
if feature_view:
90+
feature_views.append(feature_view)
91+
92+
result = {
93+
"featureViews": feature_views,
94+
"pagination": response.get("pagination", {}),
95+
}
96+
97+
if include_relationships:
98+
relationships = get_relationships_for_objects(
99+
grpc_handler, feature_views, "featureView", project, allow_cache
100+
)
101+
result["relationships"] = relationships
102+
103+
return result
56104

57105
return router

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
from feast.api.registry.rest.rest_utils import (
44
create_grpc_pagination_params,
55
create_grpc_sorting_params,
6+
get_object_relationships,
67
get_pagination_params,
8+
get_relationships_for_objects,
79
get_sorting_params,
810
grpc_call,
911
)
@@ -17,19 +19,37 @@ def get_permission_router(grpc_handler) -> APIRouter:
1719
def get_permission(
1820
name: str,
1921
project: str = Query(...),
22+
include_relationships: bool = Query(
23+
False, description="Include relationships for this permission"
24+
),
2025
allow_cache: bool = Query(True),
2126
):
2227
req = RegistryServer_pb2.GetPermissionRequest(
2328
name=name,
2429
project=project,
2530
allow_cache=allow_cache,
2631
)
27-
return {"permission": grpc_call(grpc_handler.GetPermission, req)}
32+
permission = grpc_call(grpc_handler.GetPermission, req)
33+
34+
result = permission
35+
36+
# Note: permissions may not have relationships in the traditional sense
37+
# but we include the functionality for consistency
38+
if include_relationships:
39+
relationships = get_object_relationships(
40+
grpc_handler, "permission", name, project, allow_cache
41+
)
42+
result["relationships"] = relationships
43+
44+
return result
2845

2946
@router.get("/permissions")
3047
def list_permissions(
3148
project: str = Query(...),
3249
allow_cache: bool = Query(default=True),
50+
include_relationships: bool = Query(
51+
False, description="Include relationships for each permission"
52+
),
3353
pagination_params: dict = Depends(get_pagination_params),
3454
sorting_params: dict = Depends(get_sorting_params),
3555
):
@@ -39,6 +59,20 @@ def list_permissions(
3959
pagination=create_grpc_pagination_params(pagination_params),
4060
sorting=create_grpc_sorting_params(sorting_params),
4161
)
42-
return grpc_call(grpc_handler.ListPermissions, req)
62+
response = grpc_call(grpc_handler.ListPermissions, req)
63+
permissions = response.get("permissions", [])
64+
65+
result = {
66+
"permissions": permissions,
67+
"pagination": response.get("pagination", {}),
68+
}
69+
70+
if include_relationships:
71+
relationships = get_relationships_for_objects(
72+
grpc_handler, permissions, "permission", project, allow_cache
73+
)
74+
result["relationships"] = relationships
75+
76+
return result
4377

4478
return router

0 commit comments

Comments
 (0)