diff --git a/protos/feast/core/FeatureService.proto b/protos/feast/core/FeatureService.proto index 952b30eb0a7..4aaa0d5f06f 100644 --- a/protos/feast/core/FeatureService.proto +++ b/protos/feast/core/FeatureService.proto @@ -32,6 +32,9 @@ message FeatureServiceSpec { // Description of the feature service. string description = 5; + + // Owner of the feature service. + string owner = 6; } diff --git a/sdk/python/feast/feature_service.py b/sdk/python/feast/feature_service.py index 16815531a3b..bb6ec909bf7 100644 --- a/sdk/python/feast/feature_service.py +++ b/sdk/python/feast/feature_service.py @@ -11,29 +11,36 @@ FeatureService as FeatureServiceProto, ) from feast.protos.feast.core.FeatureService_pb2 import ( - FeatureServiceMeta, - FeatureServiceSpec, + FeatureServiceMeta as FeatureServiceMetaProto, +) +from feast.protos.feast.core.FeatureService_pb2 import ( + FeatureServiceSpec as FeatureServiceSpecProto, ) from feast.usage import log_exceptions class FeatureService: """ - A feature service is a logical grouping of features for retrieval (training or serving). - The features grouped by a feature service may come from any number of feature views. - - Args: - name: Unique name of the feature service. - features: A list of Features that are grouped as part of this FeatureService. - The list may contain Feature Views, Feature Tables, or a subset of either. - tags (optional): A dictionary of key-value pairs used for organizing Feature - Services. + A feature service defines a logical group of features from one or more feature views. + This group of features can be retrieved together during training or serving. + + Attributes: + name: The unique name of the feature service. + feature_view_projections: A list containing feature views and feature view + projections, representing the features in the feature service. + description: A human-readable description. + tags: A dictionary of key-value pairs to store arbitrary metadata. + owner: The owner of the feature service, typically the email of the primary + maintainer. + created_timestamp: The time when the feature service was created. + last_updated_timestamp: The time when the feature service was last updated. """ _name: str _feature_view_projections: List[FeatureViewProjection] + _description: str _tags: Dict[str, str] - _description: Optional[str] = None + _owner: str _created_timestamp: Optional[datetime] = None _last_updated_timestamp: Optional[datetime] = None @@ -42,8 +49,9 @@ def __init__( self, name: str, features: List[Union[FeatureView, OnDemandFeatureView]], - tags: Optional[Dict[str, str]] = None, - description: Optional[str] = None, + tags: Dict[str, str] = None, + description: str = "", + owner: str = "", ): """ Creates a FeatureService object. @@ -59,12 +67,13 @@ def __init__( self._feature_view_projections.append(feature_grouping.projection) else: raise ValueError( - "The FeatureService {fs_name} has been provided with an invalid type" + f"The feature service {name} has been provided with an invalid type " f'{type(feature_grouping)} as part of the "features" argument.)' ) - self._tags = tags or {} self._description = description + self._tags = tags or {} + self._owner = owner self._created_timestamp = None self._last_updated_timestamp = None @@ -83,7 +92,13 @@ def __eq__(self, other): raise TypeError( "Comparisons should only involve FeatureService class objects." ) - if self.tags != other.tags or self.name != other.name: + + if ( + self.name != other.name + or self.description != other.description + or self.tags != other.tags + or self.owner != other.owner + ): return False if sorted(self.feature_view_projections) != sorted( @@ -111,6 +126,14 @@ def feature_view_projections( ): self._feature_view_projections = feature_view_projections + @property + def description(self) -> str: + return self._description + + @description.setter + def description(self, description: str): + self._description = description + @property def tags(self) -> Dict[str, str]: return self._tags @@ -120,12 +143,12 @@ def tags(self, tags: Dict[str, str]): self._tags = tags @property - def description(self) -> Optional[str]: - return self._description + def owner(self) -> str: + return self._owner - @description.setter - def description(self, description: str): - self._description = description + @owner.setter + def owner(self, owner: str): + self._owner = owner @property def created_timestamp(self) -> Optional[datetime]: @@ -143,23 +166,20 @@ def last_updated_timestamp(self) -> Optional[datetime]: def last_updated_timestamp(self, last_updated_timestamp: datetime): self._last_updated_timestamp = last_updated_timestamp - @staticmethod - def from_proto(feature_service_proto: FeatureServiceProto): + @classmethod + def from_proto(cls, feature_service_proto: FeatureServiceProto): """ Converts a FeatureServiceProto to a FeatureService object. Args: feature_service_proto: A protobuf representation of a FeatureService. """ - fs = FeatureService( + fs = cls( name=feature_service_proto.spec.name, features=[], tags=dict(feature_service_proto.spec.tags), - description=( - feature_service_proto.spec.description - if feature_service_proto.spec.description != "" - else None - ), + description=feature_service_proto.spec.description, + owner=feature_service_proto.spec.owner, ) fs.feature_view_projections.extend( [ @@ -181,29 +201,28 @@ def from_proto(feature_service_proto: FeatureServiceProto): def to_proto(self) -> FeatureServiceProto: """ - Converts a FeatureService to its protobuf representation. + Converts a feature service to its protobuf representation. Returns: A FeatureServiceProto protobuf. """ - meta = FeatureServiceMeta() + meta = FeatureServiceMetaProto() if self.created_timestamp: meta.created_timestamp.FromDatetime(self.created_timestamp) + if self.last_updated_timestamp: + meta.last_updated_timestamp.FromDatetime(self.last_updated_timestamp) - spec = FeatureServiceSpec( + spec = FeatureServiceSpecProto( name=self.name, features=[ projection.to_proto() for projection in self.feature_view_projections ], + tags=self.tags, + description=self.description, + owner=self.owner, ) - if self.tags: - spec.tags.update(self.tags) - if self.description: - spec.description = self.description - - feature_service_proto = FeatureServiceProto(spec=spec, meta=meta) - return feature_service_proto + return FeatureServiceProto(spec=spec, meta=meta) def validate(self): pass diff --git a/sdk/python/feast/feature_view_projection.py b/sdk/python/feast/feature_view_projection.py index 97b3b0ab577..04d923122c5 100644 --- a/sdk/python/feast/feature_view_projection.py +++ b/sdk/python/feast/feature_view_projection.py @@ -10,6 +10,18 @@ @dataclass class FeatureViewProjection: + """ + A feature view projection represents a selection of one or more features from a + single feature view. + + Attributes: + name: The unique name of the feature view from which this projection is created. + name_alias: An optional alias for the name. + features: The list of features represented by the feature view projection. + join_key_map: A map to modify join key columns during retrieval of this feature + view projection. + """ + name: str name_alias: Optional[str] features: List[Feature] @@ -18,10 +30,10 @@ class FeatureViewProjection: def name_to_use(self): return self.name_alias or self.name - def to_proto(self): + def to_proto(self) -> FeatureViewProjectionProto: feature_reference_proto = FeatureViewProjectionProto( feature_view_name=self.name, - feature_view_name_alias=self.name_alias, + feature_view_name_alias=self.name_alias or "", join_key_map=self.join_key_map, ) for feature in self.features: @@ -31,16 +43,16 @@ def to_proto(self): @staticmethod def from_proto(proto: FeatureViewProjectionProto): - ref = FeatureViewProjection( + feature_view_projection = FeatureViewProjection( name=proto.feature_view_name, name_alias=proto.feature_view_name_alias, features=[], join_key_map=dict(proto.join_key_map), ) for feature_column in proto.feature_columns: - ref.features.append(Feature.from_proto(feature_column)) + feature_view_projection.features.append(Feature.from_proto(feature_column)) - return ref + return feature_view_projection @staticmethod def from_definition(feature_grouping):