-
Notifications
You must be signed in to change notification settings - Fork 994
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allows registering of features in request data as RequestFeatureView.…
… Refactors common logic into a BaseFeatureView class (#1931) * Initial implementation of Request Feature View without validation, with some refactoring of feature views Signed-off-by: Danny Chiao <danny@tecton.ai> * Refactor constructor in base class Signed-off-by: Danny Chiao <danny@tecton.ai> * Refactor to have constructor init in base class Signed-off-by: Danny Chiao <danny@tecton.ai> * Moving copy methods into base class Signed-off-by: Danny Chiao <danny@tecton.ai> * lint Signed-off-by: Danny Chiao <danny@tecton.ai> * Implement request feature view validation in historical and online retrieval Signed-off-by: Danny Chiao <danny@tecton.ai> * Add tests for request_fv Signed-off-by: Danny Chiao <danny@tecton.ai> * Update CLI to understand request feature views Signed-off-by: Danny Chiao <danny@tecton.ai> * Fix error to be more generic and apply to all feature views Signed-off-by: Danny Chiao <danny@tecton.ai> * Add type of feature view to feature view list command Signed-off-by: Danny Chiao <danny@tecton.ai> * Add type of feature view to feature view list command Signed-off-by: Danny Chiao <danny@tecton.ai> * Lint imports Signed-off-by: Danny Chiao <danny@tecton.ai> * Fix new lines and nits Signed-off-by: Danny Chiao <danny@tecton.ai> * Fix repo apply bug Signed-off-by: Danny Chiao <danny@tecton.ai> * Fix comments Signed-off-by: Danny Chiao <danny@tecton.ai> * format Signed-off-by: Danny Chiao <danny@tecton.ai> * fix test Signed-off-by: Danny Chiao <danny@tecton.ai> * reverse naming Signed-off-by: Danny Chiao <danny@tecton.ai> * reverse naming Signed-off-by: Danny Chiao <danny@tecton.ai> * reverse naming Signed-off-by: Danny Chiao <danny@tecton.ai> * Add back to cli Signed-off-by: Danny Chiao <danny@tecton.ai> * Comments Signed-off-by: Danny Chiao <danny@tecton.ai> * Lint Signed-off-by: Danny Chiao <danny@tecton.ai> * Remove extra data in response Signed-off-by: Danny Chiao <danny@tecton.ai> * revert change Signed-off-by: Danny Chiao <danny@tecton.ai> * revert change Signed-off-by: Danny Chiao <danny@tecton.ai>
- Loading branch information
Showing
18 changed files
with
799 additions
and
284 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// | ||
// Copyright 2021 The Feast Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// https://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
|
||
|
||
syntax = "proto3"; | ||
package feast.core; | ||
|
||
option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/core"; | ||
option java_outer_classname = "RequestFeatureViewProto"; | ||
option java_package = "feast.proto.core"; | ||
|
||
import "feast/core/FeatureView.proto"; | ||
import "feast/core/Feature.proto"; | ||
import "feast/core/DataSource.proto"; | ||
|
||
message RequestFeatureView { | ||
// User-specified specifications of this feature view. | ||
RequestFeatureViewSpec spec = 1; | ||
} | ||
|
||
message RequestFeatureViewSpec { | ||
// Name of the feature view. Must be unique. Not updated. | ||
string name = 1; | ||
|
||
// Name of Feast project that this feature view belongs to. | ||
string project = 2; | ||
|
||
// Request data which contains the underlying data schema and list of associated features | ||
DataSource request_data_source = 3; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
# Copyright 2021 The Feast Authors | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# https://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
import warnings | ||
from abc import ABC, abstractmethod | ||
from typing import List, Type | ||
|
||
from google.protobuf.json_format import MessageToJson | ||
from proto import Message | ||
|
||
from feast.feature import Feature | ||
from feast.feature_view_projection import FeatureViewProjection | ||
|
||
warnings.simplefilter("once", DeprecationWarning) | ||
|
||
|
||
class BaseFeatureView(ABC): | ||
"""A FeatureView defines a logical grouping of features to be served.""" | ||
|
||
@abstractmethod | ||
def __init__(self, name: str, features: List[Feature]): | ||
self._name = name | ||
self._features = features | ||
self._projection = FeatureViewProjection.from_definition(self) | ||
|
||
@property | ||
def name(self) -> str: | ||
return self._name | ||
|
||
@property | ||
def features(self) -> List[Feature]: | ||
return self._features | ||
|
||
@features.setter | ||
def features(self, value): | ||
self._features = value | ||
|
||
@property | ||
def projection(self) -> FeatureViewProjection: | ||
return self._projection | ||
|
||
@projection.setter | ||
def projection(self, value): | ||
self._projection = value | ||
|
||
@property | ||
@abstractmethod | ||
def proto_class(self) -> Type[Message]: | ||
pass | ||
|
||
@abstractmethod | ||
def to_proto(self) -> Message: | ||
pass | ||
|
||
@classmethod | ||
@abstractmethod | ||
def from_proto(cls, feature_view_proto): | ||
pass | ||
|
||
@abstractmethod | ||
def __copy__(self): | ||
""" | ||
Generates a deep copy of this feature view | ||
Returns: | ||
A copy of this FeatureView | ||
""" | ||
pass | ||
|
||
def __repr__(self): | ||
items = (f"{k} = {v}" for k, v in self.__dict__.items()) | ||
return f"<{self.__class__.__name__}({', '.join(items)})>" | ||
|
||
def __str__(self): | ||
return str(MessageToJson(self.to_proto())) | ||
|
||
def __hash__(self): | ||
return hash((id(self), self.name)) | ||
|
||
def __getitem__(self, item): | ||
assert isinstance(item, list) | ||
|
||
referenced_features = [] | ||
for feature in self.features: | ||
if feature.name in item: | ||
referenced_features.append(feature) | ||
|
||
cp = self.__copy__() | ||
cp.projection.features = referenced_features | ||
|
||
return cp | ||
|
||
def __eq__(self, other): | ||
if not isinstance(other, BaseFeatureView): | ||
raise TypeError( | ||
"Comparisons should only involve BaseFeatureView class objects." | ||
) | ||
|
||
if self.name != other.name: | ||
return False | ||
|
||
if sorted(self.features) != sorted(other.features): | ||
return False | ||
|
||
return True | ||
|
||
def ensure_valid(self): | ||
""" | ||
Validates the state of this feature view locally. | ||
Raises: | ||
ValueError: The feature view is invalid. | ||
""" | ||
if not self.name: | ||
raise ValueError("Feature view needs a name.") | ||
|
||
def with_name(self, name: str): | ||
""" | ||
Renames this feature view by returning a copy of this feature view with an alias | ||
set for the feature view name. This rename operation is only used as part of query | ||
operations and will not modify the underlying FeatureView. | ||
Args: | ||
name: Name to assign to the FeatureView copy. | ||
Returns: | ||
A copy of this FeatureView with the name replaced with the 'name' input. | ||
""" | ||
cp = self.__copy__() | ||
cp.projection.name_alias = name | ||
|
||
return cp | ||
|
||
def set_projection(self, feature_view_projection: FeatureViewProjection) -> None: | ||
""" | ||
Setter for the projection object held by this FeatureView. A projection is an | ||
object that stores the modifications to a FeatureView that is applied to the FeatureView | ||
when the FeatureView is used such as during feature_store.get_historical_features. | ||
This method also performs checks to ensure the projection is consistent with this | ||
FeatureView before doing the set. | ||
Args: | ||
feature_view_projection: The FeatureViewProjection object to set this FeatureView's | ||
'projection' field to. | ||
""" | ||
if feature_view_projection.name != self.name: | ||
raise ValueError( | ||
f"The projection for the {self.name} FeatureView cannot be applied because it differs in name. " | ||
f"The projection is named {feature_view_projection.name} and the name indicates which " | ||
"FeatureView the projection is for." | ||
) | ||
|
||
for feature in feature_view_projection.features: | ||
if feature not in self.features: | ||
raise ValueError( | ||
f"The projection for {self.name} cannot be applied because it contains {feature.name} which the " | ||
"FeatureView doesn't have." | ||
) | ||
|
||
self.projection = feature_view_projection | ||
|
||
def with_projection(self, feature_view_projection: FeatureViewProjection): | ||
""" | ||
Sets the feature view projection by returning a copy of this on-demand feature view | ||
with its projection set to the given projection. A projection is an | ||
object that stores the modifications to a feature view that is used during | ||
query operations. | ||
Args: | ||
feature_view_projection: The FeatureViewProjection object to link to this | ||
OnDemandFeatureView. | ||
Returns: | ||
A copy of this OnDemandFeatureView with its projection replaced with the | ||
'feature_view_projection' argument. | ||
""" | ||
if feature_view_projection.name != self.name: | ||
raise ValueError( | ||
f"The projection for the {self.name} FeatureView cannot be applied because it differs in name. " | ||
f"The projection is named {feature_view_projection.name} and the name indicates which " | ||
"FeatureView the projection is for." | ||
) | ||
|
||
for feature in feature_view_projection.features: | ||
if feature not in self.features: | ||
raise ValueError( | ||
f"The projection for {self.name} cannot be applied because it contains {feature.name} which the " | ||
"FeatureView doesn't have." | ||
) | ||
|
||
cp = self.__copy__() | ||
cp.projection = feature_view_projection | ||
|
||
return cp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.