Skip to content

Commit 0a75696

Browse files
authored
feat: Added grpc and rest endpoint for features (#5519)
Signed-off-by: ntkathole <[email protected]>
1 parent 1250f8e commit 0a75696

File tree

14 files changed

+786
-36
lines changed

14 files changed

+786
-36
lines changed

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

Lines changed: 176 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,137 @@ Most endpoints support these common query parameters:
274274
}
275275
```
276276

277+
### Features
278+
279+
#### List Features
280+
- **Endpoint**: `GET /api/v1/features`
281+
- **Description**: Retrieve all features in a project
282+
- **Parameters**:
283+
- `project` (required): Project name
284+
- `feature_view` (optional): Filter by feature view name
285+
- `name` (optional): Filter by feature name
286+
- `include_relationships` (optional): Include relationships for each feature (relationships are keyed by feature name)
287+
- `allow_cache` (optional): Whether to allow cached data
288+
- `page` (optional): Page number for pagination
289+
- `limit` (optional): Number of items per page
290+
- `sort_by` (optional): Field to sort by
291+
- `sort_order` (optional): Sort order ("asc" or "desc")
292+
- **Examples**:
293+
```bash
294+
# Basic list
295+
curl -H "Authorization: Bearer <token>" \
296+
"http://localhost:6572/api/v1/features?project=my_project"
297+
298+
# With pagination and relationships
299+
curl -H "Authorization: Bearer <token>" \
300+
"http://localhost:6572/api/v1/features?project=my_project&include_relationships=true&page=1&limit=5"
301+
```
302+
- **Response Example**:
303+
```json
304+
{
305+
"features": [
306+
{ "name": "conv_rate", "featureView": "driver_hourly_stats_fresh", "type": "Float32" },
307+
{ "name": "acc_rate", "featureView": "driver_hourly_stats_fresh", "type": "Float32" },
308+
{ "name": "avg_daily_trips", "featureView": "driver_hourly_stats_fresh", "type": "Int64" },
309+
{ "name": "conv_rate", "featureView": "driver_hourly_stats", "type": "Float32" },
310+
{ "name": "acc_rate", "featureView": "driver_hourly_stats", "type": "Float32" }
311+
],
312+
"pagination": {
313+
"page": 1,
314+
"limit": 5,
315+
"totalCount": 10,
316+
"totalPages": 2,
317+
"hasNext": true
318+
},
319+
"relationships": {
320+
"conv_rate": [
321+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureView", "name": "driver_hourly_stats_fresh" } },
322+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureView", "name": "driver_hourly_stats" } },
323+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureService", "name": "driver_activity_v1" } }
324+
]
325+
}
326+
}
327+
```
328+
329+
#### Get Feature
330+
- **Endpoint**: `GET /api/v1/features/{feature_view}/{name}`
331+
- **Description**: Retrieve a specific feature by feature view and name
332+
- **Parameters**:
333+
- `feature_view` (path): Feature view name
334+
- `name` (path): Feature name
335+
- `project` (required): Project name
336+
- `include_relationships` (optional): Include relationships for this feature
337+
- `allow_cache` (optional): Whether to allow cached data
338+
- **Examples**:
339+
```bash
340+
# Basic get
341+
curl -H "Authorization: Bearer <token>" \
342+
"http://localhost:6572/api/v1/features/driver_hourly_stats/conv_rate?project=my_project"
343+
344+
# With relationships
345+
curl -H "Authorization: Bearer <token>" \
346+
"http://localhost:6572/api/v1/features/driver_hourly_stats/conv_rate?project=my_project&include_relationships=true"
347+
```
348+
- **Response Example**:
349+
```json
350+
{
351+
"name": "conv_rate",
352+
"featureView": "driver_hourly_stats",
353+
"type": "Float32",
354+
"relationships": [
355+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureView", "name": "driver_hourly_stats_fresh" } },
356+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureView", "name": "driver_hourly_stats" } },
357+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureService", "name": "driver_activity_v1" } }
358+
]
359+
}
360+
```
361+
362+
#### List All Features (All Projects)
363+
- **Endpoint**: `GET /api/v1/features/all`
364+
- **Description**: Retrieve all features across all projects. Each feature includes a `project` field.
365+
- **Parameters**:
366+
- `page` (optional): Page number for pagination
367+
- `limit` (optional): Number of items per page
368+
- `sort_by` (optional): Field to sort by
369+
- `sort_order` (optional): Sort order ("asc" or "desc")
370+
- `include_relationships` (optional): Include relationships for each feature
371+
- `allow_cache` (optional): Whether to allow cached data
372+
- **Examples**:
373+
```bash
374+
curl -H "Authorization: Bearer <token>" \
375+
"http://localhost:6572/api/v1/features/all?page=1&limit=5&sort_by=name"
376+
# With relationships
377+
curl -H "Authorization: Bearer <token>" \
378+
"http://localhost:6572/api/v1/features/all?include_relationships=true"
379+
```
380+
- **Response Example**:
381+
```json
382+
{
383+
"features": [
384+
{ "name": "conv_rate", "featureView": "driver_hourly_stats_fresh", "type": "Float32", "project": "multiproject" },
385+
{ "name": "acc_rate", "featureView": "driver_hourly_stats_fresh", "type": "Float32", "project": "multiproject" },
386+
{ "name": "avg_daily_trips", "featureView": "driver_hourly_stats_fresh", "type": "Int64", "project": "multiproject" },
387+
{ "name": "conv_rate", "featureView": "driver_hourly_stats", "type": "Float32", "project": "multiproject" },
388+
{ "name": "acc_rate", "featureView": "driver_hourly_stats", "type": "Float32", "project": "multiproject" }
389+
],
390+
"pagination": {
391+
"page": 1,
392+
"limit": 5,
393+
"total_count": 20,
394+
"total_pages": 4,
395+
"has_next": true,
396+
"has_previous": false
397+
},
398+
"relationships": {
399+
"conv_rate": [
400+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureView", "name": "driver_hourly_stats" } },
401+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureView", "name": "driver_hourly_stats_fresh" } },
402+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureService", "name": "driver_activity_v3" } }
403+
]
404+
}
405+
}
406+
```
407+
277408
### Feature Services
278409

279410
#### List Feature Services
@@ -356,7 +487,7 @@ Most endpoints support these common query parameters:
356487
- **Parameters**:
357488
- `project` (required): Project name
358489
- `allow_cache` (optional): Whether to allow cached data
359-
- `filter_object_type` (optional): Filter by object type (`dataSource`, `entity`, `featureView`, `featureService`)
490+
- `filter_object_type` (optional): Filter by object type (`dataSource`, `entity`, `featureView`, `featureService`, `feature`)
360491
- `filter_object_name` (optional): Filter by object name
361492
- **Example**:
362493
```bash
@@ -368,15 +499,25 @@ Most endpoints support these common query parameters:
368499
- **Endpoint**: `GET /api/v1/lineage/objects/{object_type}/{object_name}`
369500
- **Description**: Retrieve relationships for a specific object
370501
- **Parameters**:
371-
- `object_type` (path): Type of object (`dataSource`, `entity`, `featureView`, `featureService`)
502+
- `object_type` (path): Type of object (`dataSource`, `entity`, `featureView`, `featureService`, `feature`)
372503
- `object_name` (path): Name of the object
373504
- `project` (required): Project name
374505
- `include_indirect` (optional): Whether to include indirect relationships
375506
- `allow_cache` (optional): Whether to allow cached data
376507
- **Example**:
377508
```bash
378509
curl -H "Authorization: Bearer <token>" \
379-
"http://localhost:6572/api/v1/lineage/objects/featureView/user_features?project=my_project&include_indirect=true"
510+
"http://localhost:6572/api/v1/lineage/objects/feature/conv_rate?project=my_project&include_indirect=true"
511+
```
512+
- **Response Example**:
513+
```json
514+
{
515+
"relationships": [
516+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureView", "name": "driver_hourly_stats_fresh" } },
517+
{ "source": { "type": "feature", "name": "conv_rate" }, "target": { "type": "featureView", "name": "driver_hourly_stats" } }
518+
],
519+
"pagination": { "totalCount": 2, "totalPages": 1 }
520+
}
380521
```
381522

382523
#### Get Complete Registry Data
@@ -390,6 +531,35 @@ Most endpoints support these common query parameters:
390531
curl -H "Authorization: Bearer <token>" \
391532
"http://localhost:6572/api/v1/lineage/complete?project=my_project"
392533
```
534+
- **Response Example**:
535+
```json
536+
{
537+
"project": "multiproject",
538+
"objects": {
539+
"entities": [ ... ],
540+
"dataSources": [ ... ],
541+
"featureViews": [ ... ],
542+
"featureServices": [ ... ],
543+
"features": [
544+
{ "name": "conv_rate", "featureView": "driver_hourly_stats_fresh", "type": "Float32" },
545+
{ "name": "acc_rate", "featureView": "driver_hourly_stats_fresh", "type": "Float32" },
546+
{ "name": "avg_daily_trips", "featureView": "driver_hourly_stats_fresh", "type": "Int64" },
547+
{ "name": "conv_rate", "featureView": "driver_hourly_stats", "type": "Float32" },
548+
{ "name": "acc_rate", "featureView": "driver_hourly_stats", "type": "Float32" },
549+
{ "name": "conv_rate_plus_val1", "featureView": "transformed_conv_rate_fresh", "type": "Float64" },
550+
{ "name": "conv_rate_plus_val2", "featureView": "transformed_conv_rate_fresh", "type": "Float64" },
551+
{ "name": "conv_rate_plus_val1", "featureView": "transformed_conv_rate", "type": "Float64" },
552+
{ "name": "conv_rate_plus_val2", "featureView": "transformed_conv_rate", "type": "Float64" }
553+
]
554+
},
555+
"relationships": [ ... ],
556+
"indirectRelationships": [ ... ],
557+
"pagination": {
558+
"features": { "totalCount": 10, "totalPages": 1 },
559+
...
560+
}
561+
}
562+
```
393563

394564
#### Get Registry Lineage (All Projects)
395565
- **Endpoint**: `GET /api/v1/lineage/registry/all`
@@ -716,6 +886,7 @@ Relationships show how different Feast objects connect to each other, providing
716886
- `entity` - Feast entities
717887
- `dataSource` - Data sources
718888
- `featureView` - Feature views (including regular, on-demand, and stream)
889+
- `feature` - Features (including features from on-demand feature views)
719890
- `featureService` - Feature services
720891
- `permission` - Permissions
721892
- `savedDataset` - Saved datasets
@@ -724,6 +895,8 @@ Relationships show how different Feast objects connect to each other, providing
724895
- Feature Views → Data Sources (feature views depend on data sources)
725896
- Feature Views → Entities (feature views use entities as join keys)
726897
- Feature Services → Feature Views (feature services consume feature views)
898+
- Features → Feature Views (features belong to feature views, including on-demand feature views)
899+
- Features → Feature Services (features are consumed by feature services)
727900
- Entities → Data Sources (entities connect to data sources through feature views)
728901
- Entities → Feature Services (entities connect to feature services through feature views)
729902

protos/feast/registry/RegistryServer.proto

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ service RegistryServer{
9292
rpc GetRegistryLineage (GetRegistryLineageRequest) returns (GetRegistryLineageResponse) {}
9393
rpc GetObjectRelationships (GetObjectRelationshipsRequest) returns (GetObjectRelationshipsResponse) {}
9494

95+
// Feature RPCs
96+
rpc ListFeatures (ListFeaturesRequest) returns (ListFeaturesResponse) {}
97+
rpc GetFeature (GetFeatureRequest) returns (Feature) {}
9598
}
9699

97100
// Common pagination and sorting messages
@@ -524,3 +527,32 @@ message GetObjectRelationshipsResponse {
524527
repeated EntityRelation relationships = 1;
525528
PaginationMetadata pagination = 2;
526529
}
530+
531+
// Feature messages
532+
message Feature {
533+
string name = 1;
534+
string feature_view = 2;
535+
string type = 3;
536+
string description = 4;
537+
map<string, string> tags = 7;
538+
}
539+
540+
message ListFeaturesRequest {
541+
string project = 1;
542+
string feature_view = 2;
543+
string name = 3;
544+
bool allow_cache = 6;
545+
PaginationParams pagination = 4;
546+
SortingParams sorting = 5;
547+
}
548+
549+
message ListFeaturesResponse {
550+
repeated Feature features = 1;
551+
PaginationMetadata pagination = 2;
552+
}
553+
554+
message GetFeatureRequest {
555+
string project = 1;
556+
string feature_view = 2;
557+
string name = 3;
558+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from feast.api.registry.rest.entities import get_entity_router
55
from feast.api.registry.rest.feature_services import get_feature_service_router
66
from feast.api.registry.rest.feature_views import get_feature_view_router
7+
from feast.api.registry.rest.features import get_feature_router
78
from feast.api.registry.rest.lineage import get_lineage_router
89
from feast.api.registry.rest.permissions import get_permission_router
910
from feast.api.registry.rest.projects import get_project_router
@@ -15,6 +16,7 @@ def register_all_routes(app: FastAPI, grpc_handler):
1516
app.include_router(get_data_source_router(grpc_handler))
1617
app.include_router(get_feature_service_router(grpc_handler))
1718
app.include_router(get_feature_view_router(grpc_handler))
19+
app.include_router(get_feature_router(grpc_handler))
1820
app.include_router(get_lineage_router(grpc_handler))
1921
app.include_router(get_permission_router(grpc_handler))
2022
app.include_router(get_project_router(grpc_handler))
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from fastapi import APIRouter, Depends, Query
2+
3+
from feast.api.registry.rest.rest_utils import (
4+
aggregate_across_projects,
5+
create_grpc_pagination_params,
6+
create_grpc_sorting_params,
7+
get_object_relationships,
8+
get_pagination_params,
9+
get_relationships_for_objects,
10+
get_sorting_params,
11+
grpc_call,
12+
)
13+
from feast.registry_server import RegistryServer_pb2
14+
15+
16+
def get_feature_router(grpc_handler) -> APIRouter:
17+
router = APIRouter()
18+
19+
@router.get("/features")
20+
def list_features(
21+
project: str = Query(...),
22+
feature_view: str = Query(None),
23+
name: str = Query(None),
24+
include_relationships: bool = Query(
25+
False, description="Include relationships for each feature"
26+
),
27+
allow_cache: bool = Query(
28+
True, description="Allow using cached registry data (default: true)"
29+
),
30+
pagination_params: dict = Depends(get_pagination_params),
31+
sorting_params: dict = Depends(get_sorting_params),
32+
):
33+
req = RegistryServer_pb2.ListFeaturesRequest(
34+
project=project,
35+
feature_view=feature_view or "",
36+
name=name or "",
37+
allow_cache=allow_cache,
38+
pagination=create_grpc_pagination_params(pagination_params),
39+
sorting=create_grpc_sorting_params(sorting_params),
40+
)
41+
response = grpc_call(grpc_handler.ListFeatures, req)
42+
if include_relationships:
43+
relationships = get_relationships_for_objects(
44+
grpc_handler, response["features"], "feature", project, allow_cache
45+
)
46+
response["relationships"] = relationships
47+
return response
48+
49+
@router.get("/features/{feature_view}/{name}")
50+
def get_feature(
51+
feature_view: str,
52+
name: str,
53+
project: str = Query(...),
54+
include_relationships: bool = Query(
55+
False, description="Include relationships for this feature"
56+
),
57+
):
58+
req = RegistryServer_pb2.GetFeatureRequest(
59+
project=project,
60+
feature_view=feature_view,
61+
name=name,
62+
)
63+
response = grpc_call(grpc_handler.GetFeature, req)
64+
if include_relationships:
65+
response["relationships"] = get_object_relationships(
66+
grpc_handler, "feature", name, project
67+
)
68+
return response
69+
70+
@router.get("/features/all")
71+
def list_features_all(
72+
page: int = Query(1, ge=1),
73+
limit: int = Query(50, ge=1, le=100),
74+
sort_by: str = Query(None),
75+
sort_order: str = Query("asc"),
76+
include_relationships: bool = Query(
77+
False, description="Include relationships for each feature"
78+
),
79+
allow_cache: bool = Query(
80+
True, description="Allow using cached registry data (default: true)"
81+
),
82+
):
83+
return aggregate_across_projects(
84+
grpc_handler=grpc_handler,
85+
list_method=grpc_handler.ListFeatures,
86+
request_cls=RegistryServer_pb2.ListFeaturesRequest,
87+
response_key="features",
88+
object_type="feature",
89+
include_relationships=include_relationships,
90+
allow_cache=allow_cache,
91+
page=page,
92+
limit=limit,
93+
sort_by=sort_by,
94+
sort_order=sort_order,
95+
)
96+
97+
return router

0 commit comments

Comments
 (0)