@@ -909,6 +909,16 @@ def _convert_list_values_to_proto(
909909 ]
910910
911911
912+ def _is_array_like (value : Any ) -> bool :
913+ """Return True if *value* is array-like (numpy array or any sized,
914+ non-string, non-bytes container). Array-like values in a scalar
915+ feature column cannot be mapped to a protobuf scalar field and are
916+ therefore always treated as null."""
917+ return isinstance (value , np .ndarray ) or (
918+ hasattr (value , "__len__" ) and not isinstance (value , (str , bytes ))
919+ )
920+
921+
912922def _convert_scalar_values_to_proto (
913923 feast_value_type : ValueType ,
914924 values : List [Any ],
@@ -929,30 +939,29 @@ def _convert_scalar_values_to_proto(
929939 return [ProtoValue ()] * len (values )
930940
931941 if feast_value_type == ValueType .UNIX_TIMESTAMP :
932- out = []
933- for value in values :
934- if isinstance (value , np .ndarray ) or (
935- hasattr (value , "__len__" ) and not isinstance (value , (str , bytes ))
936- ):
937- # Array-like value in a scalar UNIX_TIMESTAMP column: treat as null.
938- out .append (ProtoValue ())
939- elif value is None :
940- out .append (ProtoValue ())
942+ out : List [Any ] = [None ] * len (values )
943+ clean_indices : List [int ] = []
944+ clean_values : List [Any ] = []
945+ for i , value in enumerate (values ):
946+ if _is_array_like (value ) or value is None :
947+ out [i ] = ProtoValue ()
941948 else :
942- (ts ,) = _python_datetime_to_int_timestamp ([value ])
943- out .append (ProtoValue (unix_timestamp_val = ts )) # type: ignore
949+ clean_indices .append (i )
950+ clean_values .append (value )
951+ if clean_values :
952+ timestamps = _python_datetime_to_int_timestamp (clean_values )
953+ for i , ts in zip (clean_indices , timestamps ):
954+ out [i ] = ProtoValue (unix_timestamp_val = ts ) # type: ignore
944955 return out
945956
946957 field_name , func , valid_scalar_types = PYTHON_SCALAR_VALUE_TYPE_TO_PROTO_VALUE [
947958 feast_value_type
948959 ]
949960
950- # Validate scalar types — skip for array-like samples (they will be treated
951- # as null or raw values in the conversion loop below).
952- if valid_scalar_types and not (
953- isinstance (sample , np .ndarray )
954- or (hasattr (sample , "__len__" ) and not isinstance (sample , (str , bytes )))
955- ):
961+ # Validate scalar types. The caller guarantees that *sample* is not
962+ # array-like (array-like values are filtered out when picking the sample
963+ # for scalar columns in python_values_to_proto_values).
964+ if valid_scalar_types :
956965 try :
957966 is_zero = sample == 0 or sample == 0.0
958967 except (ValueError , TypeError ):
@@ -972,9 +981,7 @@ def _convert_scalar_values_to_proto(
972981 if feast_value_type == ValueType .BOOL :
973982 out = []
974983 for value in values :
975- if isinstance (value , np .ndarray ) or (
976- hasattr (value , "__len__" ) and not isinstance (value , (str , bytes ))
977- ):
984+ if _is_array_like (value ):
978985 # Array-like value in a scalar BOOL column: treat as null.
979986 out .append (ProtoValue ())
980987 elif not pd .isnull (value ):
@@ -996,9 +1003,7 @@ def _convert_scalar_values_to_proto(
9961003 for value in values :
9971004 if isinstance (value , ProtoValue ):
9981005 out .append (value )
999- elif isinstance (value , np .ndarray ) or (
1000- hasattr (value , "__len__" ) and not isinstance (value , (str , bytes ))
1001- ):
1006+ elif _is_array_like (value ):
10021007 # Array-like value in a scalar column: always treat as null.
10031008 # pd.isnull() is vectorised and would return an ndarray here,
10041009 # making `not pd.isnull(value)` raise ValueError.
@@ -1145,12 +1150,18 @@ def _python_value_to_proto_value(
11451150 if "set" in type_name_lower :
11461151 return _python_set_to_proto_values (feast_value_type , values )
11471152
1148- # Scalar types
1153+ # Scalar types — pick a sample that is not array-like so that the type
1154+ # validation in _convert_scalar_values_to_proto always receives a plain
1155+ # scalar (array-like values in a scalar column are treated as null).
11491156 if (
11501157 feast_value_type in PYTHON_SCALAR_VALUE_TYPE_TO_PROTO_VALUE
11511158 or feast_value_type == ValueType .UNIX_TIMESTAMP
11521159 ):
1153- return _convert_scalar_values_to_proto (feast_value_type , values , sample )
1160+ scalar_sample = next (
1161+ (v for v in values if _non_empty_value (v ) and not _is_array_like (v )),
1162+ None ,
1163+ )
1164+ return _convert_scalar_values_to_proto (feast_value_type , values , scalar_sample )
11541165
11551166 raise Exception (f"Unsupported data type: { feast_value_type } " )
11561167
0 commit comments