Skip to main content

nautilus_model/python/data/
custom.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this code except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Python bindings for [`CustomData`].
17
18use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
19use pyo3::{basic::CompareOp, prelude::*, types::PyAny};
20
21use crate::data::{
22    CustomData, DataType,
23    custom::{PythonCustomDataWrapper, parse_custom_data_from_json_bytes},
24    registry::try_extract_from_py,
25};
26
27#[pymethods]
28#[pyo3_stub_gen::derive::gen_stub_pymethods]
29impl CustomData {
30    /// A wrapper for custom data including its data type.
31    ///
32    /// The `data` field holds an `Arc` to a `CustomDataTrait` implementation,
33    /// enabling cheap cloning when passing to Python (Arc clone is O(1)).
34    /// Custom data is always Rust-defined (optionally with PyO3 bindings).
35    #[new]
36    #[pyo3(signature = (data_type, data))]
37    fn py_new(py: Python<'_>, data_type: DataType, data: &Bound<'_, PyAny>) -> PyResult<Self> {
38        let type_name = data_type.type_name();
39        if let Some(arc) = try_extract_from_py(type_name, data) {
40            return Ok(Self::new(arc, data_type));
41        }
42        let wrapper = PythonCustomDataWrapper::new(py, data)?;
43        Ok(Self::new(std::sync::Arc::new(wrapper), data_type))
44    }
45
46    #[getter]
47    fn data(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
48        self.data.to_pyobject(py)
49    }
50
51    #[getter]
52    fn data_type(&self) -> DataType {
53        self.data_type.clone()
54    }
55
56    #[getter]
57    fn ts_event(&self) -> u64 {
58        self.data.ts_event().as_u64()
59    }
60
61    #[getter]
62    fn ts_init(&self) -> u64 {
63        self.data.ts_init().as_u64()
64    }
65
66    /// Serializes this `CustomData` to JSON bytes for roundtrip with `from_json_bytes`.
67    fn to_json_bytes(&self) -> PyResult<Vec<u8>> {
68        serde_json::to_vec(self).map_err(to_pyvalue_err)
69    }
70
71    #[expect(clippy::needless_pass_by_value)]
72    fn __richcmp__(
73        &self,
74        other: pyo3::Bound<'_, PyAny>,
75        op: CompareOp,
76        py: Python<'_>,
77    ) -> Py<PyAny> {
78        if let Ok(other) = other.extract::<Self>() {
79            match op {
80                CompareOp::Eq => self.eq(&other).into_py_any_unwrap(py),
81                CompareOp::Ne => self.ne(&other).into_py_any_unwrap(py),
82                _ => py.NotImplemented(),
83            }
84        } else {
85            py.NotImplemented()
86        }
87    }
88
89    fn __repr__(&self) -> String {
90        let type_name = self.data_type.type_name();
91        let id = self
92            .data_type
93            .identifier()
94            .map(|s| format!(", identifier={s:?}"))
95            .unwrap_or_default();
96        format!(
97            "CustomData(data_type={type_name:?}{id}, ts_event={}, ts_init={})",
98            self.data.ts_event().as_u64(),
99            self.data.ts_init().as_u64()
100        )
101    }
102}
103
104#[pymethods]
105impl CustomData {
106    /// Deserializes `CustomData` from JSON bytes (full `CustomData` format).
107    #[classmethod]
108    #[pyo3(name = "from_json_bytes")]
109    fn py_from_json_bytes_py(
110        _cls: pyo3::Bound<'_, pyo3::types::PyType>,
111        bytes: &[u8],
112    ) -> PyResult<Self> {
113        parse_custom_data_from_json_bytes(bytes).map_err(to_pyvalue_err)
114    }
115}
116
117#[pyfunction]
118#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.model")]
119#[must_use]
120pub fn custom_data_backend_kind(custom: &CustomData) -> &'static str {
121    if custom
122        .data
123        .as_any()
124        .downcast_ref::<PythonCustomDataWrapper>()
125        .is_some()
126    {
127        "python"
128    } else {
129        "native"
130    }
131}