2121
2222from feast import Entity , utils
2323from feast .feature_view import FeatureView
24+ from feast .infra .infra_object import InfraObject
2425from feast .infra .online_stores .helpers import compute_entity_id
2526from feast .infra .online_stores .online_store import OnlineStore
27+ from feast .protos .feast .core .DatastoreTable_pb2 import (
28+ DatastoreTable as DatastoreTableProto ,
29+ )
30+ from feast .protos .feast .core .InfraObject_pb2 import InfraObject as InfraObjectProto
2631from feast .protos .feast .types .EntityKey_pb2 import EntityKey as EntityKeyProto
2732from feast .protos .feast .types .Value_pb2 import Value as ValueProto
2833from feast .repo_config import FeastConfigBaseModel , RepoConfig
@@ -80,8 +85,6 @@ def update(
8085 entities_to_keep : Sequence [Entity ],
8186 partial : bool ,
8287 ):
83- """
84- """
8588 online_config = config .online_store
8689 assert isinstance (online_config , DatastoreOnlineStoreConfig )
8790 client = self ._get_client (online_config )
@@ -110,9 +113,6 @@ def teardown(
110113 tables : Sequence [FeatureView ],
111114 entities : Sequence [Entity ],
112115 ):
113- """
114- There's currently no teardown done for Datastore.
115- """
116116 online_config = config .online_store
117117 assert isinstance (online_config , DatastoreOnlineStoreConfig )
118118 client = self ._get_client (online_config )
@@ -128,18 +128,10 @@ def teardown(
128128 client .delete (key )
129129
130130 def _get_client (self , online_config : DatastoreOnlineStoreConfig ):
131-
132131 if not self ._client :
133- try :
134- self ._client = datastore .Client (
135- project = online_config .project_id , namespace = online_config .namespace ,
136- )
137- except DefaultCredentialsError as e :
138- raise FeastProviderLoginError (
139- str (e )
140- + '\n It may be necessary to run "gcloud auth application-default login" if you would like to use your '
141- "local Google Cloud account "
142- )
132+ self ._client = _initialize_client (
133+ online_config .project_id , online_config .namespace
134+ )
143135 return self ._client
144136
145137 @log_exceptions_and_usage (online_store = "datastore" )
@@ -267,7 +259,7 @@ def online_read(
267259 return result
268260
269261
270- def _delete_all_values (client , key ) -> None :
262+ def _delete_all_values (client , key ):
271263 """
272264 Delete all data under the key path in datastore.
273265 """
@@ -279,3 +271,96 @@ def _delete_all_values(client, key) -> None:
279271
280272 for entity in entities :
281273 client .delete (entity .key )
274+
275+
276+ def _initialize_client (
277+ project_id : Optional [str ], namespace : Optional [str ]
278+ ) -> datastore .Client :
279+ try :
280+ client = datastore .Client (project = project_id , namespace = namespace ,)
281+ return client
282+ except DefaultCredentialsError as e :
283+ raise FeastProviderLoginError (
284+ str (e )
285+ + '\n It may be necessary to run "gcloud auth application-default login" if you would like to use your '
286+ "local Google Cloud account "
287+ )
288+
289+
290+ class DatastoreTable (InfraObject ):
291+ """
292+ A Datastore table managed by Feast.
293+
294+ Attributes:
295+ project: The Feast project of the table.
296+ name: The name of the table.
297+ project_id (optional): The GCP project id.
298+ namespace (optional): Datastore namespace.
299+ client: Datastore client.
300+ """
301+
302+ project : str
303+ name : str
304+ project_id : Optional [str ]
305+ namespace : Optional [str ]
306+ client : datastore .Client
307+
308+ def __init__ (
309+ self ,
310+ project : str ,
311+ name : str ,
312+ project_id : Optional [str ] = None ,
313+ namespace : Optional [str ] = None ,
314+ ):
315+ self .project = project
316+ self .name = name
317+ self .project_id = project_id
318+ self .namespace = namespace
319+ self .client = _initialize_client (self .project_id , self .namespace )
320+
321+ def to_proto (self ) -> InfraObjectProto :
322+ datastore_table_proto = DatastoreTableProto ()
323+ datastore_table_proto .project = self .project
324+ datastore_table_proto .name = self .name
325+ if self .project_id :
326+ datastore_table_proto .project_id .FromString (bytes (self .project_id , "utf-8" ))
327+ if self .namespace :
328+ datastore_table_proto .namespace .FromString (bytes (self .namespace , "utf-8" ))
329+
330+ return InfraObjectProto (
331+ infra_object_class_type = "feast.infra.online_stores.datastore.DatastoreTable" ,
332+ datastore_table = datastore_table_proto ,
333+ )
334+
335+ @staticmethod
336+ def from_proto (infra_object_proto : InfraObjectProto ) -> Any :
337+ datastore_table = DatastoreTable (
338+ project = infra_object_proto .datastore_table .project ,
339+ name = infra_object_proto .datastore_table .name ,
340+ )
341+
342+ if infra_object_proto .datastore_table .HasField ("project_id" ):
343+ datastore_table .project_id = (
344+ infra_object_proto .datastore_table .project_id .SerializeToString ()
345+ ).decode ("utf-8" )
346+ if infra_object_proto .datastore_table .HasField ("namespace" ):
347+ datastore_table .namespace = (
348+ infra_object_proto .datastore_table .namespace .SerializeToString ()
349+ ).decode ("utf-8" )
350+
351+ return datastore_table
352+
353+ def update (self ):
354+ key = self .client .key ("Project" , self .project , "Table" , self .name )
355+ entity = datastore .Entity (
356+ key = key , exclude_from_indexes = ("created_ts" , "event_ts" , "values" )
357+ )
358+ entity .update ({"created_ts" : datetime .utcnow ()})
359+ self .client .put (entity )
360+
361+ def teardown (self ):
362+ key = self .client .key ("Project" , self .project , "Table" , self .name )
363+ _delete_all_values (self .client , key )
364+
365+ # Delete the table metadata datastore entity
366+ self .client .delete (key )
0 commit comments