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    #[allow(clippy::needless_pass_by_value)]
38    fn py_new(py: Python<'_>, data_type: DataType, data: &Bound<'_, PyAny>) -> PyResult<Self> {
39        let type_name = data_type.type_name();
40        if let Some(arc) = try_extract_from_py(type_name, data) {
41            return Ok(Self::new(arc, data_type));
42        }
43        let wrapper = PythonCustomDataWrapper::new(py, data)?;
44        Ok(Self::new(std::sync::Arc::new(wrapper), data_type))
45    }
46
47    #[getter]
48    fn data(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
49        self.data.to_pyobject(py)
50    }
51
52    #[getter]
53    fn data_type(&self) -> DataType {
54        self.data_type.clone()
55    }
56
57    #[getter]
58    fn ts_event(&self) -> u64 {
59        self.data.ts_event().as_u64()
60    }
61
62    #[getter]
63    fn ts_init(&self) -> u64 {
64        self.data.ts_init().as_u64()
65    }
66
67    /// Serializes this CustomData to JSON bytes for roundtrip with from_json_bytes.
68    fn to_json_bytes(&self) -> PyResult<Vec<u8>> {
69        serde_json::to_vec(self).map_err(to_pyvalue_err)
70    }
71
72    #[allow(clippy::needless_pass_by_value)]
73    fn __richcmp__(
74        &self,
75        other: pyo3::Bound<'_, PyAny>,
76        op: CompareOp,
77        py: Python<'_>,
78    ) -> Py<PyAny> {
79        if let Ok(other) = other.extract::<Self>() {
80            match op {
81                CompareOp::Eq => self.eq(&other).into_py_any_unwrap(py),
82                CompareOp::Ne => self.ne(&other).into_py_any_unwrap(py),
83                _ => py.NotImplemented(),
84            }
85        } else {
86            py.NotImplemented()
87        }
88    }
89
90    fn __repr__(&self) -> String {
91        let type_name = self.data_type.type_name();
92        let id = self
93            .data_type
94            .identifier()
95            .map(|s| format!(", identifier={s:?}"))
96            .unwrap_or_default();
97        format!(
98            "CustomData(data_type={type_name:?}{id}, ts_event={}, ts_init={})",
99            self.data.ts_event().as_u64(),
100            self.data.ts_init().as_u64()
101        )
102    }
103}
104
105#[pymethods]
106impl CustomData {
107    /// Deserializes CustomData from JSON bytes (full CustomData format).
108    #[classmethod]
109    #[pyo3(name = "from_json_bytes")]
110    fn py_from_json_bytes_py(
111        _cls: pyo3::Bound<'_, pyo3::types::PyType>,
112        bytes: &[u8],
113    ) -> PyResult<Self> {
114        parse_custom_data_from_json_bytes(bytes).map_err(to_pyvalue_err)
115    }
116}
117
118#[pyfunction]
119#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.model")]
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}