{"record":{"id":"f78970542c3842bf","repo":"nautechsystems/nautilus_trader","slug":"expected-list-from-decode-record-batch-py","errorCode":null,"errorMessage":"Expected list from decode_record_batch_py","messagePattern":"Expected list from decode_record_batch_py","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/model/src/python/data/mod.rs","lineNumber":482,"sourceCode":"            (\n                (&raw mut ffi_array as usize),\n                (&raw mut ffi_schema as usize),\n            ),\n        )?;\n\n        let metadata_py = pyo3::types::PyDict::new(py);\n        for (k, v) in metadata {\n            metadata_py.set_item(k, v)?;\n        }\n\n        let py_list = data_class\n            .bind(py)\n            .call_method1(\"decode_record_batch_py\", (metadata_py, py_batch))\n            .map_err(|e| anyhow::anyhow!(\"Failed to call decode_record_batch_py: {e}\"))?;\n\n        let list = py_list\n            .cast::<pyo3::types::PyList>()\n            .map_err(|_| anyhow::anyhow!(\"Expected list from decode_record_batch_py\"))?;\n\n        let mut result = Vec::new();\n        for item in list.iter() {\n            let wrapper = PythonCustomDataWrapper::new(py, &item)\n                .map_err(|e| anyhow::anyhow!(\"Failed to create wrapper: {e}\"))?;\n            result.push(crate::data::Data::Custom(\n                crate::data::CustomData::from_arc(Arc::new(wrapper)),\n            ));\n        }\n        Ok(result)\n    })\n}\n\n/// Registers a custom data **type** (class) with the catalog registry.\n///\n/// Use this when you prefer to pass the class instead of a sample instance.\n/// The class must have:\n/// - `type_name_static()` class method or `__name__` (used as type name in storage)","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/python/data/mod.rs#L464-L500","documentation":"After `decode_record_batch_py` returns, the Rust decoder casts the result to a PyList. This error is raised if the Python method returned something other than a list (dict, tuple, generator, None), since the decoder expects a list of decoded custom data instances. It indicates the class's decode implementation violates the expected contract.","triggerScenarios":"Decoding a RecordBatch of registered custom data when the class's `decode_record_batch_py` returns a non-list object — e.g. returning a tuple, a generator, a single item instead of a list, or None when no rows decode.","commonSituations":"Hand-written decoders returning a generator or tuple for convenience; a decoder that returns None on empty input; API change between nautilus versions altering the expected return type.","solutions":["Make `decode_record_batch_py` return an actual Python `list` of data instances (`return list(decoded)`).","Check what your implementation returns on the empty-batch path — return `[]`, not None.","Wrap the return value in `list(...)` if it is built from a generator or tuple.","Confirm against the nautilus version's decoder contract for custom data."],"exampleFix":"# before\ndef decode_record_batch_py(cls, metadata, batch):\n    return (cls(**row) for row in batch)  # generator, not list\n\n# after\ndef decode_record_batch_py(cls, metadata, batch):\n    return [cls(**row) for row in batch]","handlingStrategy":"validation","validationCode":"result = MyClass.decode_record_batch_py(metadata, batch)\nassert isinstance(result, list), f\"decoder must return list, got {type(result)}\"","typeGuard":"def is_decoded_list(result) -> bool:\n    return isinstance(result, list)","tryCatchPattern":"try:\n    items = decode_record_batch(batch, MyData)\nexcept Exception as e:\n    logger.error(f\"decoder returned non-list: {e}\")\n    raise","preventionTips":["Always return a Python list from decode_record_batch_py","Return [] (not None) on empty batches","Avoid generators/tuples as decoder return values"],"tags":["python","custom-data","decoding","return-type"],"backgroundTag":"unexpected-response-shape","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}