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

  1. Read the Python traceback in the error to locate the failing line in on_data.
  2. Type-check the incoming data (isinstance) before accessing its fields, especially with multiple subscriptions.
  3. Ensure the custom data type is properly registered/pyo3-compatible so Py::new conversion succeeds.
  4. 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

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/468997cfd7538092. Report an issue: GitHub.