Skip to content

Commit 4a6b663

Browse files
EXPEbdodlaBhargav Dodla
and
Bhargav Dodla
authored
feat: Added Project object to Feast Objects (#4475)
* feat: Added Project object to Feast Objects Signed-off-by: Bhargav Dodla <bdodla@expediagroup.com> Signed-off-by: Bhargav Dodla <bdodla@expediagroup.com> * fix: Extend FeastError and fixed integration tests Signed-off-by: Bhargav Dodla <bdodla@expediagroup.com> * fix: Small optimization to test_modify_feature_views_success test Signed-off-by: Bhargav Dodla <bdodla@expediagroup.com> * fix: Added Project object to template and quick start Signed-off-by: Bhargav Dodla <bdodla@expediagroup.com> --------- Signed-off-by: Bhargav Dodla <bdodla@expediagroup.com> Co-authored-by: Bhargav Dodla <bdodla@expediagroup.com>
1 parent c28bee5 commit 4a6b663

40 files changed

+2064
-333
lines changed

docs/getting-started/quickstart.md

+5
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,17 @@ from feast import (
103103
FeatureView,
104104
Field,
105105
FileSource,
106+
Project,
106107
PushSource,
107108
RequestSource,
108109
)
109110
from feast.on_demand_feature_view import on_demand_feature_view
110111
from feast.types import Float32, Float64, Int64
111112

113+
# Define a project for the feature repo
114+
project = Project(name="my_project", description="A project for driver statistics")
115+
116+
112117
# Define an entity for the driver. You can think of an entity as a primary key used to
113118
# fetch features.
114119
driver = Entity(name="driver", join_keys=["driver_id"])

protos/feast/core/Permission.proto

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ message PermissionSpec {
4545
VALIDATION_REFERENCE = 7;
4646
SAVED_DATASET = 8;
4747
PERMISSION = 9;
48+
PROJECT = 10;
4849
}
4950

5051
repeated Type types = 3;

protos/feast/core/Project.proto

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//
2+
// * Copyright 2020 The Feast Authors
3+
// *
4+
// * Licensed under the Apache License, Version 2.0 (the "License");
5+
// * you may not use this file except in compliance with the License.
6+
// * You may obtain a copy of the License at
7+
// *
8+
// * https://www.apache.org/licenses/LICENSE-2.0
9+
// *
10+
// * Unless required by applicable law or agreed to in writing, software
11+
// * distributed under the License is distributed on an "AS IS" BASIS,
12+
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// * See the License for the specific language governing permissions and
14+
// * limitations under the License.
15+
//
16+
17+
syntax = "proto3";
18+
19+
package feast.core;
20+
option java_package = "feast.proto.core";
21+
option java_outer_classname = "ProjectProto";
22+
option go_package = "github.com/feast-dev/feast/go/protos/feast/core";
23+
24+
import "google/protobuf/timestamp.proto";
25+
26+
message Project {
27+
// User-specified specifications of this entity.
28+
ProjectSpec spec = 1;
29+
// System-populated metadata for this entity.
30+
ProjectMeta meta = 2;
31+
}
32+
33+
message ProjectSpec {
34+
// Name of the Project
35+
string name = 1;
36+
37+
// Description of the Project
38+
string description = 2;
39+
40+
// User defined metadata
41+
map<string,string> tags = 3;
42+
43+
// Owner of the Project
44+
string owner = 4;
45+
}
46+
47+
message ProjectMeta {
48+
// Time when the Project is created
49+
google.protobuf.Timestamp created_timestamp = 1;
50+
// Time when the Project is last updated with registry changes (Apply stage)
51+
google.protobuf.Timestamp last_updated_timestamp = 2;
52+
}

protos/feast/core/Registry.proto

+4-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ import "feast/core/SavedDataset.proto";
3333
import "feast/core/ValidationProfile.proto";
3434
import "google/protobuf/timestamp.proto";
3535
import "feast/core/Permission.proto";
36+
import "feast/core/Project.proto";
3637

37-
// Next id: 17
38+
// Next id: 18
3839
message Registry {
3940
repeated Entity entities = 1;
4041
repeated FeatureTable feature_tables = 2;
@@ -47,12 +48,13 @@ message Registry {
4748
repeated ValidationReference validation_references = 13;
4849
Infra infra = 10;
4950
// Tracking metadata of Feast by project
50-
repeated ProjectMetadata project_metadata = 15;
51+
repeated ProjectMetadata project_metadata = 15 [deprecated = true];
5152

5253
string registry_schema_version = 3; // to support migrations; incremented when schema is changed
5354
string version_id = 4; // version id, random string generated on each update of the data; now used only for debugging purposes
5455
google.protobuf.Timestamp last_updated = 5;
5556
repeated Permission permissions = 16;
57+
repeated Project projects = 17;
5658
}
5759

5860
message ProjectMetadata {

protos/feast/registry/RegistryServer.proto

+33
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import "feast/core/SavedDataset.proto";
1515
import "feast/core/ValidationProfile.proto";
1616
import "feast/core/InfraObject.proto";
1717
import "feast/core/Permission.proto";
18+
import "feast/core/Project.proto";
1819

1920
service RegistryServer{
2021
// Entity RPCs
@@ -67,6 +68,12 @@ service RegistryServer{
6768
rpc ListPermissions (ListPermissionsRequest) returns (ListPermissionsResponse) {}
6869
rpc DeletePermission (DeletePermissionRequest) returns (google.protobuf.Empty) {}
6970

71+
// Project RPCs
72+
rpc ApplyProject (ApplyProjectRequest) returns (google.protobuf.Empty) {}
73+
rpc GetProject (GetProjectRequest) returns (feast.core.Project) {}
74+
rpc ListProjects (ListProjectsRequest) returns (ListProjectsResponse) {}
75+
rpc DeleteProject (DeleteProjectRequest) returns (google.protobuf.Empty) {}
76+
7077
rpc ApplyMaterialization (ApplyMaterializationRequest) returns (google.protobuf.Empty) {}
7178
rpc ListProjectMetadata (ListProjectMetadataRequest) returns (ListProjectMetadataResponse) {}
7279
rpc UpdateInfra (UpdateInfraRequest) returns (google.protobuf.Empty) {}
@@ -356,3 +363,29 @@ message DeletePermissionRequest {
356363
string project = 2;
357364
bool commit = 3;
358365
}
366+
367+
// Projects
368+
369+
message ApplyProjectRequest {
370+
feast.core.Project project = 1;
371+
bool commit = 2;
372+
}
373+
374+
message GetProjectRequest {
375+
string name = 1;
376+
bool allow_cache = 2;
377+
}
378+
379+
message ListProjectsRequest {
380+
bool allow_cache = 1;
381+
map<string,string> tags = 2;
382+
}
383+
384+
message ListProjectsResponse {
385+
repeated feast.core.Project projects = 1;
386+
}
387+
388+
message DeleteProjectRequest {
389+
string name = 1;
390+
bool commit = 2;
391+
}

sdk/python/feast/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .feature_view import FeatureView
1919
from .field import Field
2020
from .on_demand_feature_view import OnDemandFeatureView
21+
from .project import Project
2122
from .repo_config import RepoConfig
2223
from .stream_feature_view import StreamFeatureView
2324
from .value_type import ValueType
@@ -49,4 +50,5 @@
4950
"PushSource",
5051
"RequestSource",
5152
"AthenaSource",
53+
"Project",
5254
]

sdk/python/feast/cli.py

+73
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,79 @@ def data_source_list(ctx: click.Context, tags: list[str]):
254254
print(tabulate(table, headers=["NAME", "CLASS"], tablefmt="plain"))
255255

256256

257+
@cli.group(name="projects")
258+
def projects_cmd():
259+
"""
260+
Access projects
261+
"""
262+
pass
263+
264+
265+
@projects_cmd.command("describe")
266+
@click.argument("name", type=click.STRING)
267+
@click.pass_context
268+
def project_describe(ctx: click.Context, name: str):
269+
"""
270+
Describe a project
271+
"""
272+
store = create_feature_store(ctx)
273+
274+
try:
275+
project = store.get_project(name)
276+
except FeastObjectNotFoundException as e:
277+
print(e)
278+
exit(1)
279+
280+
print(
281+
yaml.dump(
282+
yaml.safe_load(str(project)), default_flow_style=False, sort_keys=False
283+
)
284+
)
285+
286+
287+
@projects_cmd.command("current_project")
288+
@click.pass_context
289+
def project_current(ctx: click.Context):
290+
"""
291+
Returns the current project configured with FeatureStore object
292+
"""
293+
store = create_feature_store(ctx)
294+
295+
try:
296+
project = store.get_project(name=None)
297+
except FeastObjectNotFoundException as e:
298+
print(e)
299+
exit(1)
300+
301+
print(
302+
yaml.dump(
303+
yaml.safe_load(str(project)), default_flow_style=False, sort_keys=False
304+
)
305+
)
306+
307+
308+
@projects_cmd.command(name="list")
309+
@tagsOption
310+
@click.pass_context
311+
def project_list(ctx: click.Context, tags: list[str]):
312+
"""
313+
List all projects
314+
"""
315+
store = create_feature_store(ctx)
316+
table = []
317+
tags_filter = utils.tags_list_to_dict(tags)
318+
for project in store.list_projects(tags=tags_filter):
319+
table.append([project.name, project.description, project.tags, project.owner])
320+
321+
from tabulate import tabulate
322+
323+
print(
324+
tabulate(
325+
table, headers=["NAME", "DESCRIPTION", "TAGS", "OWNER"], tablefmt="plain"
326+
)
327+
)
328+
329+
257330
@cli.group(name="entities")
258331
def entities_cmd():
259332
"""

sdk/python/feast/diff/registry_diff.py

+6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from feast.infra.registry.base_registry import BaseRegistry
1212
from feast.infra.registry.registry import FEAST_OBJECT_TYPES, FeastObjectType
1313
from feast.permissions.permission import Permission
14+
from feast.project import Project
1415
from feast.protos.feast.core.DataSource_pb2 import DataSource as DataSourceProto
1516
from feast.protos.feast.core.Entity_pb2 import Entity as EntityProto
1617
from feast.protos.feast.core.FeatureService_pb2 import (
@@ -371,6 +372,11 @@ def apply_diff_to_registry(
371372
TransitionType.CREATE,
372373
TransitionType.UPDATE,
373374
]:
375+
if feast_object_diff.feast_object_type == FeastObjectType.PROJECT:
376+
registry.apply_project(
377+
cast(Project, feast_object_diff.new_feast_object),
378+
commit=False,
379+
)
374380
if feast_object_diff.feast_object_type == FeastObjectType.DATA_SOURCE:
375381
registry.apply_data_source(
376382
cast(DataSource, feast_object_diff.new_feast_object),

sdk/python/feast/errors.py

+10
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,16 @@ def __init__(self, name, project=None):
480480
super().__init__(f"Permission {name} does not exist")
481481

482482

483+
class ProjectNotFoundException(FeastError):
484+
def __init__(self, project):
485+
super().__init__(f"Project {project} does not exist in registry")
486+
487+
488+
class ProjectObjectNotFoundException(FeastObjectNotFoundException):
489+
def __init__(self, name, project=None):
490+
super().__init__(f"Project {name} does not exist")
491+
492+
483493
class ZeroRowsQueryResult(FeastError):
484494
def __init__(self, query: str):
485495
super().__init__(f"This query returned zero rows:\n{query}")

sdk/python/feast/feast_object.py

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from typing import Union, get_args
22

3+
from feast.project import Project
4+
from feast.protos.feast.core.Project_pb2 import ProjectSpec
5+
36
from .batch_feature_view import BatchFeatureView
47
from .data_source import DataSource
58
from .entity import Entity
@@ -23,6 +26,7 @@
2326

2427
# Convenience type representing all Feast objects
2528
FeastObject = Union[
29+
Project,
2630
FeatureView,
2731
OnDemandFeatureView,
2832
BatchFeatureView,
@@ -36,6 +40,7 @@
3640
]
3741

3842
FeastObjectSpecProto = Union[
43+
ProjectSpec,
3944
FeatureViewSpec,
4045
OnDemandFeatureViewSpec,
4146
StreamFeatureViewSpec,

0 commit comments

Comments
 (0)