-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Python-centric feast deploy CLI #1362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9f03d32
290a57b
88433d8
bfd2902
1aa9544
9d9c3da
e14329e
3b198f8
c867b50
404543b
cfbc72c
ffe1879
4373a04
8bc0917
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # from .provider import Provider |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| from datetime import datetime | ||
| from typing import List, Optional | ||
|
|
||
| from feast import FeatureTable | ||
| from feast.infra.provider import Provider | ||
|
woop marked this conversation as resolved.
|
||
| from feast.repo_config import DatastoreOnlineStoreConfig | ||
|
|
||
|
|
||
| def _delete_all_values(client, key) -> None: | ||
| """ | ||
| Delete all data under the key path in datastore. | ||
| """ | ||
| while True: | ||
| query = client.query(kind="Value", ancestor=key) | ||
| entities = list(query.fetch(limit=1000)) | ||
| if not entities: | ||
| return | ||
|
|
||
| for entity in entities: | ||
| print("Deleting: {}".format(entity)) | ||
| client.delete(entity.key) | ||
|
|
||
|
|
||
| class Gcp(Provider): | ||
|
woop marked this conversation as resolved.
|
||
| _project_id: Optional[str] | ||
|
|
||
| def __init__(self, config: Optional[DatastoreOnlineStoreConfig]): | ||
| if config: | ||
| self._project_id = config.project_id | ||
| else: | ||
| self._project_id = None | ||
|
|
||
| def _initialize_client(self): | ||
| from google.cloud import datastore | ||
|
|
||
| if self._project_id is not None: | ||
| return datastore.Client(self.project_id) | ||
| else: | ||
| return datastore.Client() | ||
|
|
||
| def update_infra( | ||
|
woop marked this conversation as resolved.
|
||
| self, | ||
| project: str, | ||
| tables_to_delete: List[FeatureTable], | ||
| tables_to_keep: List[FeatureTable], | ||
| ): | ||
| from google.cloud import datastore | ||
|
|
||
| client = self._initialize_client() | ||
|
|
||
| for table in tables_to_keep: | ||
| key = client.key("FeastProject", project, "FeatureTable", table.name) | ||
| entity = datastore.Entity(key=key) | ||
| entity.update({"created_at": datetime.utcnow()}) | ||
| client.put(entity) | ||
|
|
||
| for table in tables_to_delete: | ||
| _delete_all_values( | ||
| client, client.key("FeastProject", project, "FeatureTable", table.name) | ||
| ) | ||
|
|
||
| # Delete the table metadata datastore entity | ||
| key = client.key("FeastProject", project, "FeatureTable", table.name) | ||
| client.delete(key) | ||
|
|
||
| def teardown_infra(self, project: str, tables: List[FeatureTable]) -> None: | ||
|
woop marked this conversation as resolved.
|
||
| client = self._initialize_client() | ||
|
|
||
| for table in tables: | ||
| _delete_all_values( | ||
| client, client.key("FeastProject", project, "FeatureTable", table.name) | ||
| ) | ||
|
|
||
| # Delete the table metadata datastore entity | ||
| key = client.key("FeastProject", project, "FeatureTable", table.name) | ||
| client.delete(key) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import os | ||
| import sqlite3 | ||
| from typing import List | ||
|
|
||
| from feast import FeatureTable | ||
| from feast.infra.provider import Provider | ||
| from feast.repo_config import LocalOnlineStoreConfig | ||
|
|
||
|
|
||
| def _table_id(project: str, table: FeatureTable) -> str: | ||
| return f"{project}_{table.name}" | ||
|
|
||
|
|
||
| class LocalSqlite(Provider): | ||
| _db_path: str | ||
|
|
||
| def __init__(self, config: LocalOnlineStoreConfig): | ||
| self._db_path = config.path | ||
|
|
||
| def update_infra( | ||
| self, | ||
| project: str, | ||
| tables_to_delete: List[FeatureTable], | ||
| tables_to_keep: List[FeatureTable], | ||
| ): | ||
| conn = sqlite3.connect(self._db_path) | ||
| for table in tables_to_keep: | ||
| conn.execute( | ||
| f"CREATE TABLE IF NOT EXISTS {_table_id(project, table)} (key BLOB, value BLOB)" | ||
| ) | ||
|
|
||
| for table in tables_to_delete: | ||
| conn.execute(f"DROP TABLE IF EXISTS {_table_id(project, table)}") | ||
|
|
||
| def teardown_infra(self, project: str, tables: List[FeatureTable]) -> None: | ||
| os.unlink(self._db_path) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import abc | ||
| from typing import List | ||
|
|
||
| from feast import FeatureTable | ||
| from feast.repo_config import RepoConfig | ||
|
|
||
|
|
||
| class Provider(abc.ABC): | ||
|
woop marked this conversation as resolved.
|
||
| @abc.abstractmethod | ||
| def update_infra( | ||
| self, | ||
| project: str, | ||
| tables_to_delete: List[FeatureTable], | ||
| tables_to_keep: List[FeatureTable], | ||
| ): | ||
| """ | ||
| Reconcile cloud resources with the objects declared in the feature repo. | ||
|
|
||
| Args: | ||
| tables_to_delete: Tables that were deleted from the feature repo, so provider needs to | ||
| clean up the corresponding cloud resources. | ||
| tables_to_keep: Tables that are still in the feature repo. Depending on implementation, | ||
| provider may or may not need to update the corresponding resources. | ||
| """ | ||
| ... | ||
|
|
||
| @abc.abstractmethod | ||
| def teardown_infra(self, project: str, tables: List[FeatureTable]): | ||
| """ | ||
| Tear down all cloud resources for a repo. | ||
|
|
||
| Args: | ||
| tables: Tables that are declared in the feature repo. | ||
| """ | ||
| ... | ||
|
|
||
|
|
||
| def get_provider(config: RepoConfig) -> Provider: | ||
| if config.provider == "gcp": | ||
| from feast.infra.gcp import Gcp | ||
|
|
||
| return Gcp(config.online_store.datastore) | ||
| elif config.provider == "local": | ||
| from feast.infra.local_sqlite import LocalSqlite | ||
|
|
||
| assert config.online_store.local is not None | ||
|
woop marked this conversation as resolved.
|
||
| return LocalSqlite(config.online_store.local) | ||
| else: | ||
| raise ValueError(config) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| from pathlib import Path | ||
| from typing import NamedTuple, Optional | ||
|
|
||
| import yaml | ||
| from bindr import bind | ||
|
|
||
|
|
||
| class LocalOnlineStoreConfig(NamedTuple): | ||
| path: str | ||
|
|
||
|
|
||
| class DatastoreOnlineStoreConfig(NamedTuple): | ||
| project_id: str | ||
|
|
||
|
|
||
| class OnlineStoreConfig(NamedTuple): | ||
| datastore: Optional[DatastoreOnlineStoreConfig] = None | ||
| local: Optional[LocalOnlineStoreConfig] = None | ||
|
|
||
|
|
||
| class RepoConfig(NamedTuple): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if we just called this
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we already have a Config or two so I wanted to be a bit more specific here
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should reconcile https://github.com/feast-dev/feast/blob/master/sdk/python/feast/feature_store_config.py and this since I think they're the same thing |
||
| metadata_store: str | ||
| project: str | ||
| provider: str | ||
| online_store: OnlineStoreConfig | ||
|
|
||
|
|
||
| def load_repo_config(repo_path: Path) -> RepoConfig: | ||
| with open(repo_path / "feature_store.yaml") as f: | ||
| raw_config = yaml.safe_load(f) | ||
| return bind(RepoConfig, raw_config) | ||
Uh oh!
There was an error while loading. Please reload this page.