|
11 | 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | 12 | # See the License for the specific language governing permissions and |
13 | 13 | # limitations under the License. |
14 | | -from datetime import datetime |
15 | | -from typing import Dict, List |
| 14 | +from datetime import timedelta |
| 15 | +from typing import Dict, List, Optional, Union |
16 | 16 |
|
17 | | -from feast.big_query_source import BigQuerySource |
18 | | -from feast.entity import Entity |
| 17 | +from google.protobuf.duration_pb2 import Duration |
| 18 | +from google.protobuf.timestamp_pb2 import Timestamp |
| 19 | + |
| 20 | +from feast.core.FeatureView_pb2 import FeatureView as FeatureViewProto |
| 21 | +from feast.core.FeatureView_pb2 import FeatureViewMeta as FeatureViewMetaProto |
| 22 | +from feast.core.FeatureView_pb2 import FeatureViewSpec as FeatureViewSpecProto |
| 23 | +from feast.data_source import BigQuerySource, DataSource |
19 | 24 | from feast.feature import Feature |
| 25 | +from feast.value_type import ValueType |
20 | 26 |
|
21 | 27 |
|
22 | 28 | class FeatureView: |
23 | 29 | """ |
24 | 30 | A FeatureView defines a logical grouping of servable features. |
25 | 31 | """ |
26 | 32 |
|
| 33 | + name: str |
| 34 | + entities: List[str] |
| 35 | + features: List[Feature] |
| 36 | + tags: Dict[str, str] |
| 37 | + ttl: Optional[Duration] |
| 38 | + online: bool |
| 39 | + input: BigQuerySource |
| 40 | + |
| 41 | + created_timestamp: Optional[Timestamp] = None |
| 42 | + last_updated_timestamp: Optional[Timestamp] = None |
| 43 | + |
27 | 44 | def __init__( |
28 | 45 | self, |
29 | 46 | name: str, |
30 | | - entities: List[Entity], |
| 47 | + entities: List[str], |
31 | 48 | features: List[Feature], |
32 | 49 | tags: Dict[str, str], |
33 | | - ttl: str, |
| 50 | + ttl: Optional[Union[Duration, timedelta]], |
34 | 51 | online: bool, |
35 | | - inputs: BigQuerySource, |
36 | | - feature_start_time: datetime, |
| 52 | + input: BigQuerySource, |
37 | 53 | ): |
38 | | - cols = [entity.name for entity in entities] + [feat.name for feat in features] |
| 54 | + cols = [entity for entity in entities] + [feat.name for feat in features] |
39 | 55 | for col in cols: |
40 | | - if inputs.field_mapping is not None and col in inputs.field_mapping.keys(): |
| 56 | + if input.field_mapping is not None and col in input.field_mapping.keys(): |
41 | 57 | raise ValueError( |
42 | | - f"The field {col} is mapped to {inputs.field_mapping[col]} for this data source. Please either remove this field mapping or use {inputs.field_mapping[col]} as the Entity or Feature name." |
| 58 | + f"The field {col} is mapped to {input.field_mapping[col]} for this data source. Please either remove this field mapping or use {input.field_mapping[col]} as the Entity or Feature name." |
43 | 59 | ) |
44 | 60 |
|
45 | 61 | self.name = name |
46 | 62 | self.entities = entities |
47 | 63 | self.features = features |
48 | 64 | self.tags = tags |
49 | | - self.ttl = ttl |
| 65 | + if isinstance(ttl, timedelta): |
| 66 | + proto_ttl = Duration() |
| 67 | + proto_ttl.FromTimedelta(ttl) |
| 68 | + self.ttl = proto_ttl |
| 69 | + else: |
| 70 | + self.ttl = ttl |
| 71 | + |
50 | 72 | self.online = online |
51 | | - self.inputs = inputs |
52 | | - self.feature_start_time = feature_start_time |
53 | | - return |
| 73 | + self.input = input |
| 74 | + |
| 75 | + def is_valid(self): |
| 76 | + """ |
| 77 | + Validates the state of a feature view locally. Raises an exception |
| 78 | + if feature view is invalid. |
| 79 | + """ |
| 80 | + |
| 81 | + if not self.name: |
| 82 | + raise ValueError("Feature view needs a name") |
| 83 | + |
| 84 | + if not self.entities: |
| 85 | + raise ValueError("Feature view has no entities") |
| 86 | + |
| 87 | + def to_proto(self) -> FeatureViewProto: |
| 88 | + """ |
| 89 | + Converts an feature view object to its protobuf representation. |
| 90 | +
|
| 91 | + Returns: |
| 92 | + FeatureViewProto protobuf |
| 93 | + """ |
| 94 | + |
| 95 | + meta = FeatureViewMetaProto( |
| 96 | + created_timestamp=self.created_timestamp, |
| 97 | + last_updated_timestamp=self.last_updated_timestamp, |
| 98 | + ) |
| 99 | + |
| 100 | + spec = FeatureViewSpecProto( |
| 101 | + name=self.name, |
| 102 | + entities=self.entities, |
| 103 | + features=[feature.to_proto() for feature in self.features], |
| 104 | + tags=self.tags, |
| 105 | + ttl=self.ttl, |
| 106 | + online=self.online, |
| 107 | + input=self.input.to_proto(), |
| 108 | + ) |
| 109 | + |
| 110 | + return FeatureViewProto(spec=spec, meta=meta) |
| 111 | + |
| 112 | + @classmethod |
| 113 | + def from_proto(cls, feature_view_proto: FeatureViewProto): |
| 114 | + """ |
| 115 | + Creates a feature view from a protobuf representation of a feature view |
| 116 | +
|
| 117 | + Args: |
| 118 | + feature_view_proto: A protobuf representation of a feature view |
| 119 | +
|
| 120 | + Returns: |
| 121 | + Returns a FeatureViewProto object based on the feature view protobuf |
| 122 | + """ |
| 123 | + |
| 124 | + feature_view = cls( |
| 125 | + name=feature_view_proto.spec.name, |
| 126 | + entities=[entity for entity in feature_view_proto.spec.entities], |
| 127 | + features=[ |
| 128 | + Feature( |
| 129 | + name=feature.name, |
| 130 | + dtype=ValueType(feature.value_type), |
| 131 | + labels=feature.labels, |
| 132 | + ) |
| 133 | + for feature in feature_view_proto.spec.features |
| 134 | + ], |
| 135 | + tags=dict(feature_view_proto.spec.tags), |
| 136 | + online=feature_view_proto.spec.online, |
| 137 | + ttl=( |
| 138 | + None |
| 139 | + if feature_view_proto.spec.ttl.seconds == 0 |
| 140 | + and feature_view_proto.spec.ttl.nanos == 0 |
| 141 | + else feature_view_proto.spec.ttl |
| 142 | + ), |
| 143 | + input=DataSource.from_proto(feature_view_proto.spec.input), |
| 144 | + ) |
| 145 | + |
| 146 | + feature_view.created_timestamp = feature_view_proto.meta.created_timestamp |
| 147 | + |
| 148 | + return feature_view |
0 commit comments