forked from feast-dev/feast
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbase_feature_view.py
More file actions
206 lines (163 loc) · 6.73 KB
/
base_feature_view.py
File metadata and controls
206 lines (163 loc) · 6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# 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 datetime import datetime
from typing import List, Optional, 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)
self.created_timestamp: Optional[datetime] = None
@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