nautechsystems/nautilus_trader · error
Python on_data failed: {e}
Error message
Python on_data failed: {e} What it means
Raised by the Rust `DataActor` bridge when the Python actor's `on_data(data)` handler raises an exception. The Rust side wraps the CustomData into a Python object (`Py::new`) and dispatches it via `dispatch_on_data`; a Python exception is rewrapped as this anyhow error. This fires for each custom data message delivered to a subscribed actor.
Source
Thrown at crates/common/src/python/actor.rs:1073
.map_err(|e| anyhow::anyhow!("Python on_save failed: {e}"))
}
fn on_load(&mut self, state: IndexMap<String, Vec<u8>>) -> anyhow::Result<()> {
self.dispatch_on_load(&state)
.map_err(|e| anyhow::anyhow!("Python on_load failed: {e}"))
}
fn on_time_event(&mut self, event: &TimeEvent) -> anyhow::Result<()> {
self.dispatch_on_time_event(event.clone())
.map_err(|e| anyhow::anyhow!("Python on_time_event failed: {e}"))
}
#[allow(unused_variables)]
fn on_data(&mut self, data: &CustomData) -> anyhow::Result<()> {
Python::attach(|py| {
let py_data: Py<PyAny> = Py::new(py, data.clone())?.into_any();
self.dispatch_on_data(py_data)
.map_err(|e| anyhow::anyhow!("Python on_data failed: {e}"))
})
}
fn on_signal(&mut self, signal: &Signal) -> anyhow::Result<()> {
self.dispatch_on_signal(signal)
.map_err(|e| anyhow::anyhow!("Python on_signal failed: {e}"))
}
fn on_queue_state(&mut self, event: &QueueStateChanged) -> anyhow::Result<()> {
self.dispatch_on_queue_state(event)
.map_err(|e| anyhow::anyhow!("Python on_queue_state failed: {e}"))
}
fn on_socket_state(&mut self, event: &SocketStateChanged) -> anyhow::Result<()> {
self.dispatch_on_socket_state(event)
.map_err(|e| anyhow::anyhow!("Python on_socket_state failed: {e}"))
}
View on GitHub (pinned to 18893faf8b)
Solutions
- Read the Python traceback in the error to locate the failing line in on_data.
- Type-check the incoming data (isinstance) before accessing its fields, especially with multiple subscriptions.
- Ensure the custom data type is properly registered/pyo3-compatible so Py::new conversion succeeds.
- Guard processing logic in try/except and log malformed messages instead of raising.
Example fix
// before (Python)
def on_data(self, data):
price = data.value
// after
def on_data(self, data):
if isinstance(data, MyCustomData):
price = data.value
else:
self.log.warning(f"unexpected data type: {type(data)}") Defensive patterns
Strategy: type-guard
Validate before calling
def check_data(data, expected_type):
if not isinstance(data, expected_type):
raise TypeError(f'on_data expected {expected_type}, got {type(data)}') # or log and return Type guard
def is_expected_data(data, cls):
return isinstance(data, cls) Try / catch
def on_data(self, data):
try:
... # processing
except Exception as e:
self.log.error(f"on_data processing failed: {e}") Prevention
- isinstance-check incoming data when subscribed to multiple data types.
- Ensure custom data types are pyo3-convertible (registered) so the Rust->Python wrap succeeds.
- Validate payload fields exist before accessing them.
- Log and skip malformed messages instead of raising from on_data.
When it happens
Trigger: Any exception in the Python `on_data` handler: unpacking the data payload into a wrong type, passing a non-serializable object between Rust and Python, or raising while processing a malformed message.
Common situations: Custom data subscriptions (external feeds, ML features) where on_data assumes a specific dataclass but receives a different payload type; attribute errors accessing fields absent from the data type; pyo3 conversion errors on Py::new for unsupported data types.
Related errors
- Python on_stop failed: {e}
- Python on_resume failed: {e}
- Python on_reset failed: {e}
- Python on_dispose failed: {e}
- Python on_degrade failed: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/468997cfd7538092.
Report an issue: GitHub.