Skip to content

Commit 897b3f3

Browse files
authored
feat: Added filtering support for featureView and featureServices api (feast-dev#5552)
* feat: Modified feature api to return timestamps and owner metadata Signed-off-by: ntkathole <[email protected]> * feat: Added filtering support for feature-views and feature-services Signed-off-by: ntkathole <[email protected]> --------- Signed-off-by: ntkathole <[email protected]>
1 parent b7ea5cc commit 897b3f3

File tree

11 files changed

+538
-140
lines changed

11 files changed

+538
-140
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ Most endpoints support these common query parameters:
210210
- `include_relationships` (optional): Include relationships for each feature view
211211
- `allow_cache` (optional): Whether to allow cached data
212212
- `tags` (optional): Filter by tags
213+
- `entity` (optional): Filter feature views by entity name
214+
- `feature` (optional): Filter feature views by feature name
215+
- `feature_service` (optional): Filter feature views by feature service name
216+
- `data_source` (optional): Filter feature views by data source name
213217
- `page` (optional): Page number for pagination
214218
- `limit` (optional): Number of items per page
215219
- `sort_by` (optional): Field to sort by
@@ -223,6 +227,26 @@ Most endpoints support these common query parameters:
223227
# With pagination and relationships
224228
curl -H "Authorization: Bearer <token>" \
225229
"http://localhost:6572/api/v1/feature_views?project=my_project&include_relationships=true&page=1&limit=5&sort_by=name"
230+
231+
# Filter by entity
232+
curl -H "Authorization: Bearer <token>" \
233+
"http://localhost:6572/api/v1/feature_views?project=my_project&entity=user"
234+
235+
# Filter by feature
236+
curl -H "Authorization: Bearer <token>" \
237+
"http://localhost:6572/api/v1/feature_views?project=my_project&feature=age"
238+
239+
# Filter by data source
240+
curl -H "Authorization: Bearer <token>" \
241+
"http://localhost:6572/api/v1/feature_views?project=my_project&data_source=user_profile_source"
242+
243+
# Filter by feature service
244+
curl -H "Authorization: Bearer <token>" \
245+
"http://localhost:6572/api/v1/feature_views?project=my_project&feature_service=user_service"
246+
247+
# Multiple filters combined
248+
curl -H "Authorization: Bearer <token>" \
249+
"http://localhost:6572/api/v1/feature_views?project=my_project&entity=user&feature=age"
226250
```
227251

228252
#### Get Feature View
@@ -415,6 +439,7 @@ Most endpoints support these common query parameters:
415439
- `include_relationships` (optional): Include relationships for each feature service
416440
- `allow_cache` (optional): Whether to allow cached data
417441
- `tags` (optional): Filter by tags
442+
- `feature_view` (optional): Filter feature services by feature view name
418443
- `page` (optional): Page number for pagination
419444
- `limit` (optional): Number of items per page
420445
- `sort_by` (optional): Field to sort by
@@ -428,6 +453,10 @@ Most endpoints support these common query parameters:
428453
# With pagination and relationships
429454
curl -H "Authorization: Bearer <token>" \
430455
"http://localhost:6572/api/v1/feature_services?project=my_project&include_relationships=true&page=1&limit=10"
456+
457+
# Filter by feature view
458+
curl -H "Authorization: Bearer <token>" \
459+
"http://localhost:6572/api/v1/feature_services?project=my_project&feature_view=user_profile"
431460
```
432461

433462
#### Get Feature Service

protos/feast/registry/RegistryServer.proto

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,12 @@ message ListAllFeatureViewsRequest {
272272
string project = 1;
273273
bool allow_cache = 2;
274274
map<string,string> tags = 3;
275-
PaginationParams pagination = 4;
276-
SortingParams sorting = 5;
275+
string entity = 4;
276+
string feature = 5;
277+
string feature_service = 6;
278+
string data_source = 7;
279+
PaginationParams pagination = 8;
280+
SortingParams sorting = 9;
277281
}
278282

279283
message ListAllFeatureViewsResponse {
@@ -342,8 +346,9 @@ message ListFeatureServicesRequest {
342346
string project = 1;
343347
bool allow_cache = 2;
344348
map<string,string> tags = 3;
345-
PaginationParams pagination = 4;
346-
SortingParams sorting = 5;
349+
string feature_view = 4;
350+
PaginationParams pagination = 5;
351+
SortingParams sorting = 6;
347352
}
348353

349354
message ListFeatureServicesResponse {
@@ -534,7 +539,10 @@ message Feature {
534539
string feature_view = 2;
535540
string type = 3;
536541
string description = 4;
537-
map<string, string> tags = 7;
542+
string owner = 5;
543+
google.protobuf.Timestamp created_timestamp = 6;
544+
google.protobuf.Timestamp last_updated_timestamp = 7;
545+
map<string, string> tags = 8;
538546
}
539547

540548
message ListFeaturesRequest {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ def list_feature_services(
2727
False, description="Include relationships for each feature service"
2828
),
2929
allow_cache: bool = Query(default=True),
30+
feature_view: str = Query(
31+
None, description="Filter feature services by feature view name"
32+
),
3033
tags: Dict[str, str] = Depends(parse_tags),
3134
pagination_params: dict = Depends(get_pagination_params),
3235
sorting_params: dict = Depends(get_sorting_params),
@@ -35,6 +38,7 @@ def list_feature_services(
3538
project=project,
3639
allow_cache=allow_cache,
3740
tags=tags,
41+
feature_view=feature_view,
3842
pagination=create_grpc_pagination_params(pagination_params),
3943
sorting=create_grpc_sorting_params(sorting_params),
4044
)

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,14 @@ def list_all_feature_views(
229229
include_relationships: bool = Query(
230230
False, description="Include relationships for each feature view"
231231
),
232+
entity: str = Query(None, description="Filter feature views by entity name"),
233+
feature: str = Query(None, description="Filter feature views by feature name"),
234+
feature_service: str = Query(
235+
None, description="Filter feature views by feature service name"
236+
),
237+
data_source: str = Query(
238+
None, description="Filter feature views by data source name"
239+
),
232240
tags: Dict[str, str] = Depends(parse_tags),
233241
pagination_params: dict = Depends(get_pagination_params),
234242
sorting_params: dict = Depends(get_sorting_params),
@@ -237,6 +245,10 @@ def list_all_feature_views(
237245
project=project,
238246
allow_cache=allow_cache,
239247
tags=tags,
248+
entity=entity,
249+
feature=feature,
250+
feature_service=feature_service,
251+
data_source=data_source,
240252
pagination=create_grpc_pagination_params(pagination_params),
241253
sorting=create_grpc_sorting_params(sorting_params),
242254
)

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

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from fastapi import APIRouter, Depends, Query
1+
from fastapi import APIRouter, Depends, HTTPException, Query
22

33
from feast.api.registry.rest.codegen_utils import render_feature_code
44
from feast.api.registry.rest.rest_utils import (
@@ -69,32 +69,53 @@ def get_feature(
6969
feature_view=feature_view,
7070
name=name,
7171
)
72-
response = grpc_call(grpc_handler.GetFeature, req)
73-
if include_relationships:
74-
response["relationships"] = get_object_relationships(
75-
grpc_handler, "feature", name, project
76-
)
77-
if response:
78-
dtype_str = response.get("type") or response.get("dtype")
79-
value_type_enum = (
80-
_convert_value_type_str_to_value_type(dtype_str.upper())
81-
if dtype_str
82-
else None
83-
)
84-
feast_type = from_value_type(value_type_enum) if value_type_enum else None
85-
dtype = (
86-
feast_type.__name__
87-
if feast_type and hasattr(feast_type, "__name__")
88-
else "String"
89-
)
90-
context = dict(
91-
name=response.get("name", name),
92-
dtype=dtype,
93-
description=response.get("description", ""),
94-
tags=response.get("tags", response.get("labels", {})) or {},
72+
73+
try:
74+
try:
75+
response = grpc_call(grpc_handler.GetFeature, req)
76+
except Exception as e:
77+
raise HTTPException(
78+
status_code=404,
79+
detail=f"Feature '{name}' not found in feature view '{feature_view}' in project '{project}'",
80+
) from e
81+
82+
if include_relationships:
83+
response["relationships"] = get_object_relationships(
84+
grpc_handler, "feature", name, project
85+
)
86+
87+
if response:
88+
dtype_str = response.get("type") or response.get("dtype")
89+
value_type_enum = (
90+
_convert_value_type_str_to_value_type(dtype_str.upper())
91+
if dtype_str
92+
else None
93+
)
94+
feast_type = (
95+
from_value_type(value_type_enum) if value_type_enum else None
96+
)
97+
dtype = (
98+
feast_type.__name__
99+
if feast_type and hasattr(feast_type, "__name__")
100+
else "String"
101+
)
102+
context = dict(
103+
name=response.get("name", name),
104+
dtype=dtype,
105+
description=response.get("description", ""),
106+
tags=response.get("tags", response.get("labels", {})) or {},
107+
)
108+
response["featureDefinition"] = render_feature_code(context)
109+
110+
return response
111+
112+
except HTTPException:
113+
raise
114+
except Exception as e:
115+
raise HTTPException(
116+
status_code=500,
117+
detail=f"Internal server error while retrieving feature '{name}' from feature view '{feature_view}' in project '{project}': {str(e)}",
95118
)
96-
response["featureDefinition"] = render_feature_code(context)
97-
return response
98119

99120
@router.get("/features/all")
100121
def list_features_all(

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ async def dispatch(self, request: Request, call_next):
9191
user = "anonymous"
9292
project = request.query_params.get("project") or self.project
9393
key = f"recently_visited_{user}"
94-
logger.info(f"[LoggingMiddleware] Project: {project}, Key: {key}")
9594
path = str(request.url.path)
9695
method = request.method
9796

sdk/python/feast/protos/feast/registry/RegistryServer_pb2.py

Lines changed: 88 additions & 88 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/python/feast/protos/feast/registry/RegistryServer_pb2.pyi

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -689,12 +689,20 @@ class ListAllFeatureViewsRequest(google.protobuf.message.Message):
689689
PROJECT_FIELD_NUMBER: builtins.int
690690
ALLOW_CACHE_FIELD_NUMBER: builtins.int
691691
TAGS_FIELD_NUMBER: builtins.int
692+
ENTITY_FIELD_NUMBER: builtins.int
693+
FEATURE_FIELD_NUMBER: builtins.int
694+
FEATURE_SERVICE_FIELD_NUMBER: builtins.int
695+
DATA_SOURCE_FIELD_NUMBER: builtins.int
692696
PAGINATION_FIELD_NUMBER: builtins.int
693697
SORTING_FIELD_NUMBER: builtins.int
694698
project: builtins.str
695699
allow_cache: builtins.bool
696700
@property
697701
def tags(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ...
702+
entity: builtins.str
703+
feature: builtins.str
704+
feature_service: builtins.str
705+
data_source: builtins.str
698706
@property
699707
def pagination(self) -> global___PaginationParams: ...
700708
@property
@@ -705,11 +713,15 @@ class ListAllFeatureViewsRequest(google.protobuf.message.Message):
705713
project: builtins.str = ...,
706714
allow_cache: builtins.bool = ...,
707715
tags: collections.abc.Mapping[builtins.str, builtins.str] | None = ...,
716+
entity: builtins.str = ...,
717+
feature: builtins.str = ...,
718+
feature_service: builtins.str = ...,
719+
data_source: builtins.str = ...,
708720
pagination: global___PaginationParams | None = ...,
709721
sorting: global___SortingParams | None = ...,
710722
) -> None: ...
711723
def HasField(self, field_name: typing_extensions.Literal["pagination", b"pagination", "sorting", b"sorting"]) -> builtins.bool: ...
712-
def ClearField(self, field_name: typing_extensions.Literal["allow_cache", b"allow_cache", "pagination", b"pagination", "project", b"project", "sorting", b"sorting", "tags", b"tags"]) -> None: ...
724+
def ClearField(self, field_name: typing_extensions.Literal["allow_cache", b"allow_cache", "data_source", b"data_source", "entity", b"entity", "feature", b"feature", "feature_service", b"feature_service", "pagination", b"pagination", "project", b"project", "sorting", b"sorting", "tags", b"tags"]) -> None: ...
713725

714726
global___ListAllFeatureViewsRequest = ListAllFeatureViewsRequest
715727

@@ -972,12 +984,14 @@ class ListFeatureServicesRequest(google.protobuf.message.Message):
972984
PROJECT_FIELD_NUMBER: builtins.int
973985
ALLOW_CACHE_FIELD_NUMBER: builtins.int
974986
TAGS_FIELD_NUMBER: builtins.int
987+
FEATURE_VIEW_FIELD_NUMBER: builtins.int
975988
PAGINATION_FIELD_NUMBER: builtins.int
976989
SORTING_FIELD_NUMBER: builtins.int
977990
project: builtins.str
978991
allow_cache: builtins.bool
979992
@property
980993
def tags(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ...
994+
feature_view: builtins.str
981995
@property
982996
def pagination(self) -> global___PaginationParams: ...
983997
@property
@@ -988,11 +1002,12 @@ class ListFeatureServicesRequest(google.protobuf.message.Message):
9881002
project: builtins.str = ...,
9891003
allow_cache: builtins.bool = ...,
9901004
tags: collections.abc.Mapping[builtins.str, builtins.str] | None = ...,
1005+
feature_view: builtins.str = ...,
9911006
pagination: global___PaginationParams | None = ...,
9921007
sorting: global___SortingParams | None = ...,
9931008
) -> None: ...
9941009
def HasField(self, field_name: typing_extensions.Literal["pagination", b"pagination", "sorting", b"sorting"]) -> builtins.bool: ...
995-
def ClearField(self, field_name: typing_extensions.Literal["allow_cache", b"allow_cache", "pagination", b"pagination", "project", b"project", "sorting", b"sorting", "tags", b"tags"]) -> None: ...
1010+
def ClearField(self, field_name: typing_extensions.Literal["allow_cache", b"allow_cache", "feature_view", b"feature_view", "pagination", b"pagination", "project", b"project", "sorting", b"sorting", "tags", b"tags"]) -> None: ...
9961011

9971012
global___ListFeatureServicesRequest = ListFeatureServicesRequest
9981013

@@ -1719,11 +1734,19 @@ class Feature(google.protobuf.message.Message):
17191734
FEATURE_VIEW_FIELD_NUMBER: builtins.int
17201735
TYPE_FIELD_NUMBER: builtins.int
17211736
DESCRIPTION_FIELD_NUMBER: builtins.int
1737+
OWNER_FIELD_NUMBER: builtins.int
1738+
CREATED_TIMESTAMP_FIELD_NUMBER: builtins.int
1739+
LAST_UPDATED_TIMESTAMP_FIELD_NUMBER: builtins.int
17221740
TAGS_FIELD_NUMBER: builtins.int
17231741
name: builtins.str
17241742
feature_view: builtins.str
17251743
type: builtins.str
17261744
description: builtins.str
1745+
owner: builtins.str
1746+
@property
1747+
def created_timestamp(self) -> google.protobuf.timestamp_pb2.Timestamp: ...
1748+
@property
1749+
def last_updated_timestamp(self) -> google.protobuf.timestamp_pb2.Timestamp: ...
17271750
@property
17281751
def tags(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ...
17291752
def __init__(
@@ -1733,9 +1756,13 @@ class Feature(google.protobuf.message.Message):
17331756
feature_view: builtins.str = ...,
17341757
type: builtins.str = ...,
17351758
description: builtins.str = ...,
1759+
owner: builtins.str = ...,
1760+
created_timestamp: google.protobuf.timestamp_pb2.Timestamp | None = ...,
1761+
last_updated_timestamp: google.protobuf.timestamp_pb2.Timestamp | None = ...,
17361762
tags: collections.abc.Mapping[builtins.str, builtins.str] | None = ...,
17371763
) -> None: ...
1738-
def ClearField(self, field_name: typing_extensions.Literal["description", b"description", "feature_view", b"feature_view", "name", b"name", "tags", b"tags", "type", b"type"]) -> None: ...
1764+
def HasField(self, field_name: typing_extensions.Literal["created_timestamp", b"created_timestamp", "last_updated_timestamp", b"last_updated_timestamp"]) -> builtins.bool: ...
1765+
def ClearField(self, field_name: typing_extensions.Literal["created_timestamp", b"created_timestamp", "description", b"description", "feature_view", b"feature_view", "last_updated_timestamp", b"last_updated_timestamp", "name", b"name", "owner", b"owner", "tags", b"tags", "type", b"type"]) -> None: ...
17391766

17401767
global___Feature = Feature
17411768

0 commit comments

Comments
 (0)