`ValueError: The truth value of an empty array is ambiguous` during materialization
Expected Behavior
feast materialize should complete successfully even when the source DataFrame
contains an empty numpy array (np.array([])) in a scalar feature column.
The empty array should be treated as a null / missing value and produce an empty
ProtoValue(), consistent with how None and np.nan are already handled.
Current Behavior
feast materialize crashes with:
ValueError: The truth value of an empty array is ambiguous.
Use `array.size > 0` to check that an array is not empty.
Full stack trace:
Traceback (most recent call last):
File "/opt/app-root/bin/feast", line 10, in <module>
sys.exit(cli())
^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/click/core.py", line 1485, in __call__
return self.main(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/click/core.py", line 1406, in main
rv = self.invoke(ctx)
^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/click/core.py", line 1873, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/click/core.py", line 1269, in invoke
return ctx.invoke(self.callback, **ctx.params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/click/core.py", line 824, in invoke
return callback(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/click/decorators.py", line 34, in new_func
return f(get_current_context(), *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/feast/cli/cli.py", line 393, in materialize_command
store.materialize(
File "/opt/app-root/lib64/python3.11/site-packages/feast/feature_store.py", line 1816, in materialize
provider.materialize_single_feature_view(
File "/opt/app-root/lib64/python3.11/site-packages/feast/infra/passthrough_provider.py", line 456, in materialize_single_feature_view
raise e
File "/opt/app-root/lib64/python3.11/site-packages/feast/infra/compute_engines/local/compute.py", line 84, in _materialize_one
plan.execute(context)
File "/opt/app-root/lib64/python3.11/site-packages/feast/infra/compute_engines/dag/plan.py", line 51, in execute
output = node.execute(context)
^^^^^^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/feast/infra/compute_engines/local/nodes.py", line 274, in execute
rows_to_write = _convert_arrow_to_proto(
^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/feast/utils.py", line 281, in _convert_arrow_to_proto
return _convert_arrow_fv_to_proto(table, feature_view, join_keys) # type: ignore[arg-type]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/feast/utils.py", line 298, in _convert_arrow_fv_to_proto
proto_values_by_column = {
^
File "/opt/app-root/lib64/python3.11/site-packages/feast/utils.py", line 299, in <dictcomp>
column: python_values_to_proto_values(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/feast/type_map.py", line 840, in python_values_to_proto_values
proto_values = _python_value_to_proto_value(value_type, values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/app-root/lib64/python3.11/site-packages/feast/type_map.py", line 772, in _python_value_to_proto_value
elif not pd.isnull(value):
ValueError: The truth value of an empty array is ambiguous. Use `array.size > 0` to check that an array is not empty.
The root cause is in sdk/python/feast/type_map.py,
function _convert_scalar_values_to_proto (around line 968):
# Generic scalar conversion out = [] for value in values: if isinstance(value, ProtoValue): out.append(value) elif not pd.isnull(value): # ← crashes here out.append(ProtoValue(**{field_name: func(value)})) else: out.append(ProtoValue())
pd.isnull() is vectorised: when value is a numpy array (including an empty
one), it returns a numpy array of booleans instead of a scalar boolean. Applying
Python's not to that array raises ValueError. The same pattern exists a few lines
above in the ValueType.BOOL path (if not pd.isnull(value)).
Steps to reproduce
import numpy as np from feast.type_map import python_values_to_proto_values from feast.value_type import ValueType # A scalar column where one row contains an empty array python_values_to_proto_values([np.array([]), 1.0, 2.0], ValueType.DOUBLE) # → ValueError: The truth value of an empty array is ambiguous
Specifications
- Version:
v0.60.0(also reproducible onmainas of 2026-04-10) - Platform: Linux (Python 3.11), macOS (Python 3.11)
- Subsystem:
feast/type_map.py–_convert_scalar_values_to_proto
Possible Solution
The fix belongs in sdk/python/feast/type_map.py, specifically the generic scalar conversion loop at line 963–975 (elif not pd.isnull(value)).
Before calling not pd.isnull(value), check whether the value is array-like.
pd.isnull() is vectorised and returns an np.ndarray for array inputs, so
calling not on it raises ValueError. The fix must handle three sub-cases:
| Value | Expected outcome |
|---|---|
Empty array (size == 0) |
null → ProtoValue() |
| Non-empty array containing any null | null → ProtoValue() |
| Non-empty array with all valid data | convert → ProtoValue(**{field_name: func(value)}) |
| Plain scalar null | null → ProtoValue() |
| Plain scalar non-null | convert → ProtoValue(**{field_name: func(value)}) |
# Generic scalar conversion out = [] for value in values: if isinstance(value, ProtoValue): out.append(value) elif isinstance(value, np.ndarray) or ( hasattr(value, "__len__") and not isinstance(value, (str, bytes)) ): # Array-like value in a scalar column if hasattr(value, "size") and value.size == 0: # Empty numpy array – treat as null out.append(ProtoValue()) else: is_null = pd.isnull(value) if hasattr(is_null, "any"): # pd.isnull returned an array; null if any element is null out.append(ProtoValue() if is_null.any() else ProtoValue(**{field_name: func(value)})) elif not is_null: out.append(ProtoValue(**{field_name: func(value)})) else: out.append(ProtoValue()) elif not pd.isnull(value): out.append(ProtoValue(**{field_name: func(value)})) else: out.append(ProtoValue()) return out