nautechsystems/nautilus_trader · error · anyhow::Error
Failed to call decode_record_batch_py: {e}
Error message
Failed to call decode_record_batch_py: {e} What it means
When decoding an Arrow RecordBatch back into custom data, the decoder calls the registered Python class's `decode_record_batch_py(metadata, batch)`. This error wraps any Python exception raised inside that call; the traceback is embedded in `{e}`. The decode of the whole batch is aborted.
Source
Thrown at crates/model/src/python/data/mod.rs:478
let pyarrow = py.import("pyarrow")?;
let cls = pyarrow.getattr("RecordBatch")?;
let py_batch = cls.call_method1(
"_import_from_c",
(
(&raw mut ffi_array as usize),
(&raw mut ffi_schema as usize),
),
)?;
let metadata_py = pyo3::types::PyDict::new(py);
for (k, v) in metadata {
metadata_py.set_item(k, v)?;
}
let py_list = data_class
.bind(py)
.call_method1("decode_record_batch_py", (metadata_py, py_batch))
.map_err(|e| anyhow::anyhow!("Failed to call decode_record_batch_py: {e}"))?;
let list = py_list
.cast::<pyo3::types::PyList>()
.map_err(|_| anyhow::anyhow!("Expected list from decode_record_batch_py"))?;
let mut result = Vec::new();
for item in list.iter() {
let wrapper = PythonCustomDataWrapper::new(py, &item)
.map_err(|e| anyhow::anyhow!("Failed to create wrapper: {e}"))?;
result.push(crate::data::Data::Custom(
crate::data::CustomData::from_arc(Arc::new(wrapper)),
));
}
Ok(result)
})
}
/// Registers a custom data **type** (class) with the catalog registry.View on GitHub (pinned to 18893faf8b)
Solutions
- Read the embedded Python traceback and fix the exception inside `decode_record_batch_py`.
- Verify the stored Parquet schema matches the currently registered class schema (column names, types, nullability).
- Re-register the type with `ensure_custom_data_registered::<T>()` so schema metadata is present, and rewrite old data if the schema changed.
- Handle nulls/missing columns defensively in the decoder implementation.
Example fix
# before
def decode_record_batch_py(cls, metadata, batch):
return [cls(x["price"], x["qty"]) for x in batch]
# after
def decode_record_batch_py(cls, metadata, batch):
return [cls(x.get("price"), x.get("qty")) for x in batch] # tolerate missing/null columns Defensive patterns
Strategy: try-catch
Validate before calling
required = {f.name for f in registered_schema}
stored_names = set(batch.schema.names)
assert required <= stored_names, f"missing columns: {required - stored_names}" Try / catch
try:
items = decode_record_batch(batch, MyData)
except Exception as e:
logger.error(f"decode_record_batch_py raised: {e}")
raise Prevention
- Call ensure_custom_data_registered::<T>() before reading data
- Handle nulls and absent columns in decode_record_batch_py
- Version your schema and migrate stored Parquet when fields change
When it happens
Trigger: Querying/decoding custom data from the catalog (via registered custom data classes) when `decode_record_batch_py` raises — e.g. the batch schema does not match what the class expects, columns are missing or renamed, or the metadata dict lacks expected keys (common after Parquet/DataFusion drops schema metadata).
Common situations: Parquet files written by an older schema version read with a newer class definition; DataFusion round-trips that lose Arrow schema metadata so the decoder can't recover type information; hand-written decoders that assume non-nullable columns receiving nulls.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Instances must have encode_record_batch_py method
- Failed to convert to Python: {e}
- Failed to create list: {e}
- Failed to call encode_record_batch_py: {e}
- Expected list from decode_record_batch_py
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/d3db03502fff28fd.
Report an issue: GitHub.