Visitar URL original
feat: Added UI for Labels and LabelView · feast-dev/feast@a7c8748 · GitHub
Skip to content

Commit a7c8748

Browse files
committed
feat: Added UI for Labels and LabelView
Signed-off-by: ntkathole <nikhilkathole2683@gmail.com>
1 parent e614dcd commit a7c8748

66 files changed

Lines changed: 6988 additions & 2075 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docs/adr/ADR-0012-label-view.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0012: LabelView — First-Class Mutable Labels for Feast
22

3-
Status: Proposed
3+
Status: Accepted
44
Authors: Nikhil Kathole
55
Pull Request: https://github.com/feast-dev/feast/pull/6292
66
Date: 2026-04-16

‎docs/adr/rfc-feature-view-versioning.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# RFC: Feature View Versioning
22

3-
**Status:** In Review
3+
**Status:** Accepted
44
**Authors:** @farceo
55
**Branch:** `featureview-versioning`
66
**Date:** 2026-03-17
Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,42 @@
1-
syntax = "proto3";
2-
package feast.core;
3-
4-
option go_package = "github.com/feast-dev/feast/go/protos/feast/core";
5-
option java_outer_classname = "FeatureReferenceProto";
6-
option java_package = "feast.proto.core";
7-
8-
import "feast/core/Feature.proto";
9-
import "feast/core/DataSource.proto";
10-
11-
12-
// A projection to be applied on top of a FeatureView.
13-
// Contains the modifications to a FeatureView such as the features subset to use.
14-
message FeatureViewProjection {
15-
// The feature view name
16-
string feature_view_name = 1;
17-
18-
// Alias for feature view name
19-
string feature_view_name_alias = 3;
20-
21-
// The features of the feature view that are a part of the feature reference.
22-
repeated FeatureSpecV2 feature_columns = 2;
23-
24-
// Map for entity join_key overrides of feature data entity join_key to entity data join_key
25-
map<string,string> join_key_map = 4;
26-
27-
string timestamp_field = 5;
28-
string date_partition_column = 6;
29-
string created_timestamp_column = 7;
30-
// Batch/Offline DataSource where this view can retrieve offline feature data.
31-
DataSource batch_source = 8;
32-
// Streaming DataSource from where this view can consume "online" feature data.
33-
DataSource stream_source = 9;
34-
35-
// Optional version tag for version-qualified feature references (e.g., @v2).
36-
optional int32 version_tag = 10;
37-
38-
}
1+
syntax = "proto3";
2+
package feast.core;
3+
4+
option go_package = "github.com/feast-dev/feast/go/protos/feast/core";
5+
option java_outer_classname = "FeatureReferenceProto";
6+
option java_package = "feast.proto.core";
7+
8+
import "feast/core/Feature.proto";
9+
import "feast/core/DataSource.proto";
10+
11+
12+
// A projection to be applied on top of a FeatureView.
13+
// Contains the modifications to a FeatureView such as the features subset to use.
14+
message FeatureViewProjection {
15+
// The feature view name
16+
string feature_view_name = 1;
17+
18+
// Alias for feature view name
19+
string feature_view_name_alias = 3;
20+
21+
// The features of the feature view that are a part of the feature reference.
22+
repeated FeatureSpecV2 feature_columns = 2;
23+
24+
// Map for entity join_key overrides of feature data entity join_key to entity data join_key
25+
map<string,string> join_key_map = 4;
26+
27+
string timestamp_field = 5;
28+
string date_partition_column = 6;
29+
string created_timestamp_column = 7;
30+
// Batch/Offline DataSource where this view can retrieve offline feature data.
31+
DataSource batch_source = 8;
32+
// Streaming DataSource from where this view can consume "online" feature data.
33+
DataSource stream_source = 9;
34+
35+
// Optional version tag for version-qualified feature references (e.g., @v2).
36+
optional int32 version_tag = 10;
37+
38+
// Distinguishes the source view type (e.g., "featureView", "labelView").
39+
// Empty string or unset means "featureView" for backward compatibility.
40+
string view_type = 11;
41+
42+
}

‎protos/feast/registry/RegistryServer.proto‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,8 @@ message Feature {
571571
google.protobuf.Timestamp created_timestamp = 6;
572572
google.protobuf.Timestamp last_updated_timestamp = 7;
573573
map<string, string> tags = 8;
574+
// Semantic kind: "feature" (input/predictor) or "label" (target/annotation).
575+
string kind = 9;
574576
}
575577

576578
message ListFeaturesRequest {
@@ -580,6 +582,8 @@ message ListFeaturesRequest {
580582
bool allow_cache = 6;
581583
PaginationParams pagination = 4;
582584
SortingParams sorting = 5;
585+
// Filter by kind: "feature" or "label". Empty means no filter.
586+
string kind = 7;
583587
}
584588

585589
message ListFeaturesResponse {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
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
77
from feast.api.registry.rest.features import get_feature_router
8+
from feast.api.registry.rest.label_views import get_label_view_router
89
from feast.api.registry.rest.lineage import get_lineage_router
910
from feast.api.registry.rest.metrics import get_metrics_router
1011
from feast.api.registry.rest.permissions import get_permission_router
@@ -19,6 +20,7 @@ def register_all_routes(app: FastAPI, grpc_handler, server=None):
1920
app.include_router(get_feature_service_router(grpc_handler))
2021
app.include_router(get_feature_view_router(grpc_handler))
2122
app.include_router(get_feature_router(grpc_handler))
23+
app.include_router(get_label_view_router(grpc_handler))
2224
app.include_router(get_lineage_router(grpc_handler))
2325
app.include_router(get_permission_router(grpc_handler))
2426
app.include_router(get_project_router(grpc_handler))

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def list_features(
3838
feature_view=feature_view or "",
3939
name=name or "",
4040
allow_cache=allow_cache,
41+
kind="feature",
4142
pagination=create_grpc_pagination_params(pagination_params),
4243
sorting=create_grpc_sorting_params(sorting_params),
4344
)
@@ -127,6 +128,76 @@ def list_features_all(
127128
limit=limit,
128129
sort_by=sort_by,
129130
sort_order=sort_order,
131+
extra_request_params={"kind": "feature"},
130132
)
131133

134+
# --- Labels endpoints (items from LabelViews only) ---
135+
136+
@router.get("/labels")
137+
def list_labels(
138+
project: str = Query(...),
139+
feature_view: str = Query(None, description="Filter by label view name"),
140+
name: str = Query(None, description="Filter by label name"),
141+
include_relationships: bool = Query(
142+
False, description="Include relationships for each label"
143+
),
144+
allow_cache: bool = Query(True),
145+
pagination_params: dict = Depends(get_pagination_params),
146+
sorting_params: dict = Depends(get_sorting_params),
147+
):
148+
req = RegistryServer_pb2.ListFeaturesRequest(
149+
project=project,
150+
feature_view=feature_view or "",
151+
name=name or "",
152+
allow_cache=allow_cache,
153+
kind="label",
154+
pagination=create_grpc_pagination_params(pagination_params),
155+
sorting=create_grpc_sorting_params(sorting_params),
156+
)
157+
response = grpc_call(grpc_handler.ListFeatures, req)
158+
if "features" not in response:
159+
response["features"] = []
160+
if "pagination" not in response:
161+
response["pagination"] = {}
162+
163+
# Rename key for clarity in the response
164+
response["labels"] = response.pop("features")
165+
166+
if include_relationships:
167+
labels = response.get("labels", [])
168+
relationships = get_relationships_for_objects(
169+
grpc_handler, labels, "feature", project, allow_cache
170+
)
171+
response["relationships"] = relationships
172+
return response
173+
174+
@router.get("/labels/all")
175+
def list_labels_all(
176+
page: int = Query(1, ge=1),
177+
limit: int = Query(50, ge=1, le=100),
178+
sort_by: str = Query(None),
179+
sort_order: str = Query("asc"),
180+
include_relationships: bool = Query(
181+
False, description="Include relationships for each label"
182+
),
183+
allow_cache: bool = Query(True),
184+
):
185+
result = aggregate_across_projects(
186+
grpc_handler=grpc_handler,
187+
list_method=grpc_handler.ListFeatures,
188+
request_cls=RegistryServer_pb2.ListFeaturesRequest,
189+
response_key="features",
190+
object_type="feature",
191+
include_relationships=include_relationships,
192+
allow_cache=allow_cache,
193+
page=page,
194+
limit=limit,
195+
sort_by=sort_by,
196+
sort_order=sort_order,
197+
extra_request_params={"kind": "label"},
198+
)
199+
if "features" in result:
200+
result["labels"] = result.pop("features")
201+
return result
202+
132203
return router
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
from fastapi import APIRouter, Depends, Query
2+
3+
from feast.api.registry.rest.rest_utils import (
4+
create_grpc_pagination_params,
5+
create_grpc_sorting_params,
6+
get_object_relationships,
7+
get_pagination_params,
8+
get_relationships_for_objects,
9+
get_sorting_params,
10+
grpc_call,
11+
paginate_and_sort,
12+
parse_tags,
13+
)
14+
from feast.registry_server import RegistryServer_pb2
15+
16+
17+
def get_label_view_router(grpc_handler) -> APIRouter:
18+
router = APIRouter()
19+
20+
@router.get("/label_views")
21+
def list_label_views(
22+
project: str = Query(...),
23+
allow_cache: bool = Query(default=True),
24+
include_relationships: bool = Query(
25+
False, description="Include relationships for each label view"
26+
),
27+
tags: dict = Depends(parse_tags),
28+
pagination_params: dict = Depends(get_pagination_params),
29+
sorting_params: dict = Depends(get_sorting_params),
30+
):
31+
req = RegistryServer_pb2.ListLabelViewsRequest(
32+
project=project,
33+
allow_cache=allow_cache,
34+
tags=tags,
35+
pagination=create_grpc_pagination_params(pagination_params),
36+
sorting=create_grpc_sorting_params(sorting_params),
37+
)
38+
response = grpc_call(grpc_handler.ListLabelViews, req)
39+
label_views = response.get("labelViews", [])
40+
41+
for lv in label_views:
42+
lv["type"] = "labelView"
43+
44+
result = {
45+
"featureViews": label_views,
46+
"pagination": response.get("pagination", {}),
47+
}
48+
49+
if include_relationships:
50+
relationships = get_relationships_for_objects(
51+
grpc_handler, label_views, "featureView", project, allow_cache
52+
)
53+
result["relationships"] = relationships
54+
55+
return result
56+
57+
@router.get("/label_views/all")
58+
def list_label_views_all(
59+
allow_cache: bool = Query(default=True),
60+
page: int = Query(1, ge=1),
61+
limit: int = Query(50, ge=1, le=100),
62+
sort_by: str = Query(None),
63+
sort_order: str = Query("asc"),
64+
include_relationships: bool = Query(
65+
False, description="Include relationships for each label view"
66+
),
67+
):
68+
projects_resp = grpc_call(
69+
grpc_handler.ListProjects,
70+
RegistryServer_pb2.ListProjectsRequest(allow_cache=allow_cache),
71+
)
72+
projects = projects_resp.get("projects", [])
73+
all_label_views = []
74+
for project in projects:
75+
project_name = project["spec"]["name"]
76+
req = RegistryServer_pb2.ListLabelViewsRequest(
77+
project=project_name,
78+
allow_cache=allow_cache,
79+
)
80+
response = grpc_call(grpc_handler.ListLabelViews, req)
81+
label_views = response.get("labelViews", [])
82+
for lv in label_views:
83+
lv["type"] = "labelView"
84+
lv["project"] = project_name
85+
all_label_views.extend(label_views)
86+
87+
paged_label_views, pagination = paginate_and_sort(
88+
all_label_views, page, limit, sort_by, sort_order
89+
)
90+
result = {
91+
"featureViews": paged_label_views,
92+
"pagination": pagination,
93+
}
94+
if include_relationships:
95+
relationships_map = {}
96+
for project in projects:
97+
project_name = project["spec"]["name"]
98+
project_lvs = [
99+
lv for lv in all_label_views if lv.get("project") == project_name
100+
]
101+
rels = get_relationships_for_objects(
102+
grpc_handler,
103+
project_lvs,
104+
"featureView",
105+
project_name,
106+
allow_cache,
107+
)
108+
relationships_map.update(rels)
109+
result["relationships"] = relationships_map
110+
return result
111+
112+
@router.get("/label_views/{name}")
113+
def get_label_view(
114+
name: str,
115+
project: str = Query(...),
116+
include_relationships: bool = Query(
117+
False, description="Include relationships for this label view"
118+
),
119+
allow_cache: bool = Query(True),
120+
):
121+
req = RegistryServer_pb2.GetLabelViewRequest(
122+
name=name,
123+
project=project,
124+
allow_cache=allow_cache,
125+
)
126+
response = grpc_call(grpc_handler.GetLabelView, req)
127+
response["type"] = "labelView"
128+
129+
if include_relationships:
130+
relationships = get_object_relationships(
131+
grpc_handler, "featureView", name, project, allow_cache
132+
)
133+
response["relationships"] = relationships
134+
135+
return response
136+
137+
return router

0 commit comments

Comments
 (0)