55 Any ,
66 Callable ,
77 ContextManager ,
8+ Dict ,
89 Iterator ,
910 KeysView ,
1011 List ,
1314 Union ,
1415)
1516
17+ import numpy as np
1618import pandas as pd
1719import pyarrow as pa
1820from jinja2 import BaseLoader , Environment
2426from feast .errors import InvalidEntityType
2527from feast .feature_view import DUMMY_ENTITY_ID , DUMMY_ENTITY_VAL , FeatureView
2628from feast .infra .offline_stores import offline_utils
29+ from feast .infra .offline_stores .contrib .postgres_offline_store .postgres_source import (
30+ SavedDatasetPostgreSQLStorage ,
31+ )
2732from feast .infra .offline_stores .offline_store import (
2833 OfflineStore ,
2934 RetrievalJob ,
@@ -112,24 +117,24 @@ def get_historical_features(
112117 project : str ,
113118 full_feature_names : bool = False ,
114119 ) -> RetrievalJob :
120+
121+ entity_schema = _get_entity_schema (entity_df , config )
122+
123+ entity_df_event_timestamp_col = (
124+ offline_utils .infer_event_timestamp_from_entity_df (entity_schema )
125+ )
126+
127+ entity_df_event_timestamp_range = _get_entity_df_event_timestamp_range (
128+ entity_df ,
129+ entity_df_event_timestamp_col ,
130+ config ,
131+ )
132+
115133 @contextlib .contextmanager
116134 def query_generator () -> Iterator [str ]:
117- table_name = None
118- if isinstance (entity_df , pd .DataFrame ):
119- table_name = offline_utils .get_temp_entity_table_name ()
120- entity_schema = df_to_postgres_table (
121- config .offline_store , entity_df , table_name
122- )
123- df_query = table_name
124- elif isinstance (entity_df , str ):
125- df_query = f"({ entity_df } ) AS sub"
126- entity_schema = get_query_schema (config .offline_store , df_query )
127- else :
128- raise TypeError (entity_df )
129-
130- entity_df_event_timestamp_col = (
131- offline_utils .infer_event_timestamp_from_entity_df (entity_schema )
132- )
135+ table_name = offline_utils .get_temp_entity_table_name ()
136+
137+ _upload_entity_df (config , entity_df , table_name )
133138
134139 expected_join_keys = offline_utils .get_expected_join_keys (
135140 project , feature_views , registry
@@ -139,13 +144,6 @@ def query_generator() -> Iterator[str]:
139144 entity_schema , expected_join_keys , entity_df_event_timestamp_col
140145 )
141146
142- entity_df_event_timestamp_range = _get_entity_df_event_timestamp_range (
143- entity_df ,
144- entity_df_event_timestamp_col ,
145- config ,
146- df_query ,
147- )
148-
149147 query_context = offline_utils .get_feature_view_query_context (
150148 feature_refs ,
151149 feature_views ,
@@ -165,7 +163,7 @@ def query_generator() -> Iterator[str]:
165163 try :
166164 yield build_point_in_time_query (
167165 query_context_dict ,
168- left_table_query_string = df_query ,
166+ left_table_query_string = table_name ,
169167 entity_df_event_timestamp_col = entity_df_event_timestamp_col ,
170168 entity_df_columns = entity_schema .keys (),
171169 query_template = MULTIPLE_FEATURE_VIEW_POINT_IN_TIME_JOIN ,
@@ -189,6 +187,12 @@ def query_generator() -> Iterator[str]:
189187 on_demand_feature_views = OnDemandFeatureView .get_requested_odfvs (
190188 feature_refs , project , registry
191189 ),
190+ metadata = RetrievalMetadata (
191+ features = feature_refs ,
192+ keys = list (entity_schema .keys () - {entity_df_event_timestamp_col }),
193+ min_event_timestamp = entity_df_event_timestamp_range [0 ],
194+ max_event_timestamp = entity_df_event_timestamp_range [1 ],
195+ ),
192196 )
193197
194198 @staticmethod
@@ -294,14 +298,19 @@ def metadata(self) -> Optional[RetrievalMetadata]:
294298 return self ._metadata
295299
296300 def persist (self , storage : SavedDatasetStorage ):
297- pass
301+ assert isinstance (storage , SavedDatasetPostgreSQLStorage )
302+
303+ df_to_postgres_table (
304+ config = self .config .offline_store ,
305+ df = self .to_df (),
306+ table_name = storage .postgres_options ._table ,
307+ )
298308
299309
300310def _get_entity_df_event_timestamp_range (
301311 entity_df : Union [pd .DataFrame , str ],
302312 entity_df_event_timestamp_col : str ,
303313 config : RepoConfig ,
304- table_name : str ,
305314) -> Tuple [datetime , datetime ]:
306315 if isinstance (entity_df , pd .DataFrame ):
307316 entity_df_event_timestamp = entity_df .loc [
@@ -312,15 +321,15 @@ def _get_entity_df_event_timestamp_range(
312321 entity_df_event_timestamp , utc = True
313322 )
314323 entity_df_event_timestamp_range = (
315- entity_df_event_timestamp .min (),
316- entity_df_event_timestamp .max (),
324+ entity_df_event_timestamp .min (). to_pydatetime () ,
325+ entity_df_event_timestamp .max (). to_pydatetime () ,
317326 )
318327 elif isinstance (entity_df , str ):
319328 # If the entity_df is a string (SQL query), determine range
320329 # from table
321330 with _get_conn (config .offline_store ) as conn , conn .cursor () as cur :
322331 cur .execute (
323- f"SELECT MIN({ entity_df_event_timestamp_col } ) AS min, MAX({ entity_df_event_timestamp_col } ) AS max FROM { table_name } "
332+ f"SELECT MIN({ entity_df_event_timestamp_col } ) AS min, MAX({ entity_df_event_timestamp_col } ) AS max FROM ( { entity_df } ) as tmp_alias "
324333 ),
325334 res = cur .fetchone ()
326335 entity_df_event_timestamp_range = (res [0 ], res [1 ])
@@ -374,6 +383,34 @@ def build_point_in_time_query(
374383 return query
375384
376385
386+ def _upload_entity_df (
387+ config : RepoConfig , entity_df : Union [pd .DataFrame , str ], table_name : str
388+ ):
389+ if isinstance (entity_df , pd .DataFrame ):
390+ # If the entity_df is a pandas dataframe, upload it to Postgres
391+ df_to_postgres_table (config .offline_store , entity_df , table_name )
392+ elif isinstance (entity_df , str ):
393+ # If the entity_df is a string (SQL query), create a Postgres table out of it
394+ with _get_conn (config .offline_store ) as conn , conn .cursor () as cur :
395+ cur .execute (f"CREATE TABLE { table_name } AS ({ entity_df } )" )
396+ else :
397+ raise InvalidEntityType (type (entity_df ))
398+
399+
400+ def _get_entity_schema (
401+ entity_df : Union [pd .DataFrame , str ],
402+ config : RepoConfig ,
403+ ) -> Dict [str , np .dtype ]:
404+ if isinstance (entity_df , pd .DataFrame ):
405+ return dict (zip (entity_df .columns , entity_df .dtypes ))
406+
407+ elif isinstance (entity_df , str ):
408+ df_query = f"({ entity_df } ) AS sub"
409+ return get_query_schema (config .offline_store , df_query )
410+ else :
411+ raise InvalidEntityType (type (entity_df ))
412+
413+
377414# Copied from the Feast Redshift offline store implementation
378415# Note: Keep this in sync with sdk/python/feast/infra/offline_stores/redshift.py:
379416# MULTIPLE_FEATURE_VIEW_POINT_IN_TIME_JOIN
0 commit comments