Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
f1fd9b8
Allow specifying FeatureServices in FeatureStore methods
achals Jul 6, 2021
27ea33e
format and lint
achals Jul 6, 2021
7ae1ed3
features_refs -> features
achals Jul 6, 2021
6269200
remote type: ignores
achals Jul 6, 2021
8574163
implement applying and persisting feature services into registry
achals Jul 6, 2021
dbe2995
Fix integration test references
achals Jul 6, 2021
7efd3c3
Add backwards compatibility
achals Jul 6, 2021
3e54197
More lint
achals Jul 6, 2021
3e6cad3
add integration tests
achals Jul 7, 2021
86a528a
refactor and lint
achals Jul 7, 2021
a929aa4
More tests
achals Jul 7, 2021
0368903
Add cli commands
achals Jul 7, 2021
7633674
Update sdk/python/feast/feature_store.py
achals Jul 7, 2021
f48542f
Update sdk/python/feast/feature_store.py
achals Jul 7, 2021
42c6a5f
Update sdk/python/feast/feature_store.py
achals Jul 7, 2021
f612ab3
Update sdk/python/feast/feature_store.py
achals Jul 7, 2021
8fe06c1
Update tests to use file offline source
achals Jul 7, 2021
7248a4c
Make tests integration and lint
achals Jul 7, 2021
09a651d
Retrieve feature services to avoid stale references
achals Jul 7, 2021
8dc58aa
Fix lint
achals Jul 7, 2021
6f09686
Merge changes from master
achals Jul 9, 2021
c656190
Merge master into branch
achals Jul 9, 2021
9540c72
fix tests
achals Jul 9, 2021
4241885
fix import
achals Jul 9, 2021
77ab6c0
fix integ tests
achals Jul 9, 2021
6eac6e8
CR
achals Jul 9, 2021
976293b
format
achals Jul 9, 2021
e1f9a07
Fix integ test
achals Jul 10, 2021
52a6507
Fix integ test
achals Jul 10, 2021
8a91fc1
Merge branch 'master' into achal/feature-service-api
achals Jul 12, 2021
c457f1e
Rename in comments
achals Jul 12, 2021
e3d70a9
Docstrings for Feature Services
achals Jul 12, 2021
545f675
make format
achals Jul 12, 2021
a77eb0f
merge from master
achals Jul 14, 2021
eaa3a89
format and refactor
achals Jul 14, 2021
e3cebc1
docs and updates
achals Jul 14, 2021
8a1534e
Update docs/concepts/feature-service.md
achals Jul 14, 2021
3274a1e
Update sdk/python/feast/feature_service.py
achals Jul 14, 2021
39bf511
Update sdk/python/feast/feature_service.py
achals Jul 14, 2021
2133abc
docs docs docs
achals Jul 14, 2021
f6938c0
docs docs
achals Jul 14, 2021
d495856
merge from master
achals Jul 19, 2021
1317d08
Fixes after merge
achals Jul 19, 2021
ea2210d
Remove dupe
achals Jul 19, 2021
2f0d698
Renames and deletions
achals Jul 19, 2021
ac211d2
lint
achals Jul 19, 2021
23c0d80
format and tests
achals Jul 19, 2021
798df64
remove unused imports
achals Jul 19, 2021
81fb228
fix registry
achals Jul 19, 2021
21aa6b6
fix docs
achals Jul 19, 2021
a6ecc6d
fix dangling print
achals Jul 20, 2021
54c95ae
make format and lint
achals Jul 20, 2021
2394f0a
list feature servces
achals Jul 20, 2021
2d11ab4
Merge branch 'master' into achal/feature-service-api
achals Jul 22, 2021
1ed9592
Merge from master
achals Jul 22, 2021
2a48ae8
fix tests
achals Jul 22, 2021
0943faf
Remove unused file
achals Jul 22, 2021
03d0ba5
single heading
achals Jul 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ store = FeatureStore(repo_path=".")

training_df = store.get_historical_features(
entity_df=entity_df,
feature_refs = [
features = [
'driver_hourly_stats:conv_rate',
'driver_hourly_stats:acc_rate',
'driver_hourly_stats:avg_daily_trips'
Expand Down Expand Up @@ -101,7 +101,7 @@ from feast import FeatureStore
store = FeatureStore(repo_path=".")

feature_vector = store.get_online_features(
feature_refs=[
features=[
'driver_hourly_stats:conv_rate',
'driver_hourly_stats:acc_rate',
'driver_hourly_stats:avg_daily_trips'
Expand Down
4 changes: 2 additions & 2 deletions docs/concepts/data-model-and-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Feature references are used for the retrieval of features from Feast:

```python
online_features = fs.get_online_features(
feature_refs=[
features=[
'driver_locations:lon',
'drivers_activity:trips_today'
],
Expand Down Expand Up @@ -53,7 +53,7 @@ A collection of entity rows. Entity dataframes are the "left table" that is enri
```python
training_df = store.get_historical_features(
entity_df=entity_df,
feature_refs = [
features = [
'drivers_activity:trips_today'
'drivers_activity:rating'
],
Expand Down
27 changes: 27 additions & 0 deletions docs/concepts/feature-service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Feature Service

A feature service is an object that represents a logical group of features from one or more [feature views](feature-view.md).
Feature Services allows features from within a feature view to be used as needed by an ML model. Users can expect to create one feature service per model, allowing for tracking of the features used by models.

{% tabs %}
{% tab title="driver\_trips\_feature\_service.py" %}
```python
from driver_ratings_feature_view import driver_ratings_fv
from driver_trips_feature_view import driver_stats_fv

driver_stats_fs = FeatureService(
name="driver_activity",
features=[driver_stats_fv, driver_ratings_fv[["lifetime_rating"]]]
)
```
{% endtab %}
{% endtabs %}

Feature services are used during

* The generation of training datasets when querying feature views in order to find historical feature values. A single training dataset may consist of features from multiple feature views.
* Retrieval of features from the online store. The features retrieved from the online store may also belong to multiple feature views.

{% hint style="info" %}
Applying a feature service does not result in an actual service being deployed.
{% endhint %}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Below is an example of the process required to produce a training dataset:

```python
# Feature references with target feature
feature_refs = [
features = [
"driver_trips:average_daily_rides",
"driver_trips:maximum_daily_rides",
"driver_trips:rating",
Expand All @@ -24,7 +24,7 @@ entity_source = FileSource(

# Retrieve historical dataset from Feast.
historical_feature_retrieval_job = client.get_historical_features(
feature_refs=feature_refs,
features=features,
entity_rows=entity_source
)

Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/build-a-training-dataset.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ from feast import FeatureStore
fs = FeatureStore(repo_path="path/to/your/feature/repo")

training_df = fs.get_historical_features(
feature_refs=[
features=[
"driver_hourly_stats:conv_rate",
"driver_hourly_stats:acc_rate"
],
Expand Down
4 changes: 2 additions & 2 deletions docs/getting-started/read-features-from-the-online-store.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Please ensure that you have materialized \(loaded\) your feature values into the
Create a list of features that you would like to retrieve. This list typically comes from the model training step and should accompany the model binary.

```python
feature_refs = [
features = [
"driver_hourly_stats:conv_rate",
"driver_hourly_stats:acc_rate"
]
Expand All @@ -32,7 +32,7 @@ Next, we will create a feature store object and call `get_online_features()` whi
```python
fs = FeatureStore(repo_path="path/to/feature/repo")
online_features = fs.get_online_features(
feature_refs=feature_refs,
features=features,
entity_rows=[
{"driver_id": 1001},
{"driver_id": 1002}]
Expand Down
4 changes: 2 additions & 2 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ store = FeatureStore(repo_path=".")

training_df = store.get_historical_features(
entity_df=entity_df,
feature_refs=[
features=[
"driver_hourly_stats:conv_rate",
"driver_hourly_stats:acc_rate",
"driver_hourly_stats:avg_daily_trips",
Expand Down Expand Up @@ -105,7 +105,7 @@ from feast import FeatureStore
store = FeatureStore(repo_path=".")

feature_vector = store.get_online_features(
feature_refs=[
features=[
"driver_hourly_stats:conv_rate",
"driver_hourly_stats:acc_rate",
"driver_hourly_stats:avg_daily_trips",
Expand Down
4 changes: 2 additions & 2 deletions docs/user-guide/getting-training-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Below is an example of the process required to produce a training dataset:

```python
# Feature references with target feature
feature_refs = [
features = [
"driver_trips:average_daily_rides",
"driver_trips:maximum_daily_rides",
"driver_trips:rating",
Expand All @@ -24,7 +24,7 @@ entity_source = FileSource(

# Retrieve historical dataset from Feast.
historical_feature_retrieval_job = client.get_historical_features(
feature_refs=feature_refs,
features=features,
entity_rows=entity_source
)

Expand Down
3 changes: 3 additions & 0 deletions protos/feast/core/Registry.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ option java_outer_classname = "RegistryProto";
option go_package = "github.com/feast-dev/feast/sdk/go/protos/feast/core";

import "feast/core/Entity.proto";
import "feast/core/FeatureService.proto";
import "feast/core/FeatureTable.proto";
import "feast/core/FeatureView.proto";
import "google/protobuf/timestamp.proto";
Expand All @@ -30,8 +31,10 @@ message Registry {
repeated Entity entities = 1;
repeated FeatureTable feature_tables = 2;
repeated FeatureView feature_views = 6;
repeated FeatureService feature_services = 7;

string registry_schema_version = 3; // to support migrations; incremented when schema is changed
string version_id = 4; // version id, random string generated on each update of the data; now used only for debugging purposes
google.protobuf.Timestamp last_updated = 5;

}
2 changes: 2 additions & 0 deletions sdk/python/feast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .data_source import KafkaSource, KinesisSource, SourceType
from .entity import Entity
from .feature import Feature
from .feature_service import FeatureService
from .feature_store import FeatureStore
from .feature_table import FeatureTable
from .feature_view import FeatureView
Expand All @@ -34,6 +35,7 @@
"KafkaSource",
"KinesisSource",
"Feature",
"FeatureService",
"FeatureStore",
"FeatureTable",
"FeatureView",
Expand Down
57 changes: 57 additions & 0 deletions sdk/python/feast/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,63 @@ def entity_list(ctx: click.Context):
print(tabulate(table, headers=["NAME", "DESCRIPTION", "TYPE"], tablefmt="plain"))


@cli.group(name="feature-services")
def feature_services_cmd():
"""
Access feature services
"""
pass


@feature_services_cmd.command("describe")
@click.argument("name", type=click.STRING)
@click.pass_context
def feature_service_describe(ctx: click.Context, name: str):
"""
Describe a feature service
"""
repo = ctx.obj["CHDIR"]
cli_check_repo(repo)
store = FeatureStore(repo_path=str(repo))

try:
feature_service = store.get_feature_service(name)
except FeastObjectNotFoundException as e:
print(e)
exit(1)

print(
yaml.dump(
yaml.safe_load(str(feature_service)),
default_flow_style=False,
sort_keys=False,
)
)


@feature_services_cmd.command(name="list")
@click.pass_context
def feature_service_list(ctx: click.Context):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can list the list of features for each feature service in a table? Currently it seems like we only list the feature service names

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure - something like [view_name:feature_name, ...]?

"""
List all feature services
"""
repo = ctx.obj["CHDIR"]
cli_check_repo(repo)
store = FeatureStore(repo_path=str(repo))
feature_services = []
for feature_service in store.list_feature_services():
feature_names = []
for projection in feature_service.features:
feature_names.extend(
[f"{projection.name}:{feature.name}" for feature in projection.features]
)
feature_services.append([feature_service.name, ", ".join(feature_names)])

from tabulate import tabulate

print(tabulate(feature_services, headers=["NAME", "FEATURES"], tablefmt="plain"))


@cli.group(name="feature-views")
def feature_views_cmd():
"""
Expand Down
10 changes: 10 additions & 0 deletions sdk/python/feast/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ def __init__(self, name, project=None):
super().__init__(f"Entity {name} does not exist")


class FeatureServiceNotFoundException(FeastObjectNotFoundException):
def __init__(self, name, project=None):
if project:
super().__init__(
f"Feature service {name} does not exist in project {project}"
)
else:
super().__init__(f"Feature service {name} does not exist")


class FeatureViewNotFoundException(FeastObjectNotFoundException):
def __init__(self, name, project=None):
if project:
Expand Down
8 changes: 8 additions & 0 deletions sdk/python/feast/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ def __eq__(self, other):
def __lt__(self, other):
return self.name < other.name

def __repr__(self):
# return string representation of the reference
return self.name

def __str__(self):
# readable string of the reference
return f"Feature<{self.__repr__()}>"

@property
def name(self):
"""
Expand Down
51 changes: 48 additions & 3 deletions sdk/python/feast/feature_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from datetime import datetime
from typing import List, Optional, Union
from typing import Dict, List, Optional, Union

from google.protobuf.json_format import MessageToJson

from feast.feature_table import FeatureTable
from feast.feature_view import FeatureView
Expand All @@ -14,16 +16,32 @@


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.
"""

name: str
features: List[FeatureViewProjection]
tags: Dict[str, str]
Comment thread
achals marked this conversation as resolved.
created_timestamp: Optional[datetime] = None
last_updated_timestamp: Optional[datetime] = None

def __init__(
self,
name: str,
features: List[Union[FeatureTable, FeatureView, FeatureViewProjection]],
tags: Optional[Dict[str, str]] = None,
):
"""
Create a new Feature Service object.
Comment thread
achals marked this conversation as resolved.

Args:
name: A unique name for 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: A dictionary of key-value pairs used for organizing Feature Services.
"""
self.name = name
self.features = []
for feature in features:
Expand All @@ -33,9 +51,32 @@ def __init__(
self.features.append(feature)
else:
raise ValueError(f"Unexpected type: {type(feature)}")
self.tags = tags or {}
self.created_timestamp = None
self.last_updated_timestamp = None

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(self.name)

def __eq__(self, other):
pass
if not isinstance(other, FeatureService):
raise TypeError(
"Comparisons should only involve FeatureService class objects."
)
if self.tags != other.tags or self.name != other.name:
return False

if sorted(self.features) != sorted(other.features):
return False

return True

@staticmethod
def from_proto(feature_service_proto: FeatureServiceProto):
Expand All @@ -45,6 +86,7 @@ def from_proto(feature_service_proto: FeatureServiceProto):
FeatureViewProjection.from_proto(fp)
for fp in feature_service_proto.spec.features
],
tags=dict(feature_service_proto.spec.tags),
)

if feature_service_proto.meta.HasField("created_timestamp"):
Expand All @@ -58,7 +100,7 @@ def from_proto(feature_service_proto: FeatureServiceProto):

return fs

def to_proto(self):
def to_proto(self) -> FeatureServiceProto:
meta = FeatureServiceMeta()
if self.created_timestamp:
meta.created_timestamp.FromDatetime(self.created_timestamp)
Expand All @@ -77,6 +119,9 @@ def to_proto(self):

spec.features.append(feature_ref.to_proto())

if self.tags:
spec.tags.update(self.tags)

feature_service_proto = FeatureServiceProto(spec=spec, meta=meta)
return feature_service_proto

Expand Down
Loading