1313# limitations under the License.
1414
1515import re
16- from datetime import datetime
17- from typing import Any , Dict , List , Optional , Set , Sized , Tuple , Type
16+ from datetime import datetime , timezone
17+ from typing import (
18+ Any ,
19+ Dict ,
20+ List ,
21+ Optional ,
22+ Sequence ,
23+ Set ,
24+ Sized ,
25+ Tuple ,
26+ Type ,
27+ Union ,
28+ cast ,
29+ )
1830
1931import numpy as np
2032import pandas as pd
@@ -49,8 +61,17 @@ def feast_value_type_to_python_type(field_value_proto: ProtoValue) -> Any:
4961 if val_attr is None :
5062 return None
5163 val = getattr (field_value_proto , val_attr )
64+
65+ # If it's a _LIST type extract the list.
5266 if hasattr (val , "val" ):
5367 val = list (val .val )
68+
69+ # Convert UNIX_TIMESTAMP values to `datetime`
70+ if val_attr == "unix_timestamp_list_val" :
71+ val = [datetime .fromtimestamp (v , tz = timezone .utc ) for v in val ]
72+ elif val_attr == "unix_timestamp_val" :
73+ val = datetime .fromtimestamp (val , tz = timezone .utc )
74+
5475 return val
5576
5677
@@ -240,6 +261,28 @@ def _type_err(item, dtype):
240261}
241262
242263
264+ def _python_datetime_to_int_timestamp (
265+ values : Sequence [Any ],
266+ ) -> Sequence [Union [int , np .int_ ]]:
267+ # Fast path for Numpy array.
268+ if isinstance (values , np .ndarray ) and isinstance (values .dtype , np .datetime64 ):
269+ if values .ndim != 1 :
270+ raise ValueError ("Only 1 dimensional arrays are supported." )
271+ return cast (Sequence [np .int_ ], values .astype ("datetime64[s]" ).astype (np .int_ ))
272+
273+ int_timestamps = []
274+ for value in values :
275+ if isinstance (value , datetime ):
276+ int_timestamps .append (int (value .timestamp ()))
277+ elif isinstance (value , Timestamp ):
278+ int_timestamps .append (int (value .ToSeconds ()))
279+ elif isinstance (value , np .datetime64 ):
280+ int_timestamps .append (value .astype ("datetime64[s]" ).astype (np .int_ ))
281+ else :
282+ int_timestamps .append (int (value ))
283+ return int_timestamps
284+
285+
243286def _python_value_to_proto_value (
244287 feast_value_type : ValueType , values : List [Any ]
245288) -> List [ProtoValue ]:
@@ -275,22 +318,14 @@ def _python_value_to_proto_value(
275318 raise _type_err (first_invalid , valid_types [0 ])
276319
277320 if feast_value_type == ValueType .UNIX_TIMESTAMP_LIST :
278- converted_values = []
279- for value in values :
280- converted_sub_values = []
281- for sub_value in value :
282- if isinstance (sub_value , datetime ):
283- converted_sub_values .append (int (sub_value .timestamp ()))
284- elif isinstance (sub_value , Timestamp ):
285- converted_sub_values .append (int (sub_value .ToSeconds ()))
286- elif isinstance (sub_value , np .datetime64 ):
287- converted_sub_values .append (
288- sub_value .astype ("datetime64[s]" ).astype ("int" )
289- )
290- else :
291- converted_sub_values .append (sub_value )
292- converted_values .append (converted_sub_values )
293- values = converted_values
321+ int_timestamps_lists = (
322+ _python_datetime_to_int_timestamp (value ) for value in values
323+ )
324+ return [
325+ # ProtoValue does actually accept `np.int_` but the typing complains.
326+ ProtoValue (unix_timestamp_list_val = Int64List (val = ts )) # type: ignore
327+ for ts in int_timestamps_lists
328+ ]
294329
295330 return [
296331 ProtoValue (** {field_name : proto_type (val = value )}) # type: ignore
@@ -302,20 +337,9 @@ def _python_value_to_proto_value(
302337 # Handle scalar types below
303338 else :
304339 if feast_value_type == ValueType .UNIX_TIMESTAMP :
305- if isinstance (sample , datetime ):
306- return [
307- ProtoValue (int64_val = int (value .timestamp ())) for value in values
308- ]
309- elif isinstance (sample , Timestamp ):
310- return [
311- ProtoValue (int64_val = int (value .ToSeconds ())) for value in values
312- ]
313- elif isinstance (sample , np .datetime64 ):
314- return [
315- ProtoValue (int64_val = value .astype ("datetime64[s]" ).astype ("int" ))
316- for value in values
317- ]
318- return [ProtoValue (int64_val = int (value )) for value in values ]
340+ int_timestamps = _python_datetime_to_int_timestamp (values )
341+ # ProtoValue does actually accept `np.int_` but the typing complains.
342+ return [ProtoValue (unix_timestamp_val = ts ) for ts in int_timestamps ] # type: ignore
319343
320344 if feast_value_type in PYTHON_SCALAR_VALUE_TYPE_TO_PROTO_VALUE :
321345 (
0 commit comments