nautilus_model/python/
common.rs1use indexmap::IndexMap;
17use nautilus_core::python::IntoPyObjectNautilusExt;
18use pyo3::{
19 conversion::IntoPyObjectExt,
20 exceptions::PyValueError,
21 prelude::*,
22 types::{PyDict, PyList, PyNone},
23};
24use serde_json::Value;
25use strum::IntoEnumIterator;
26
27use crate::types::{Currency, Money};
28
29pub const PY_MODULE_MODEL: &str = "nautilus_trader.core.nautilus_pyo3.model";
30
31#[allow(missing_debug_implementations)]
33#[pyclass]
34pub struct EnumIterator {
35 iter: Box<dyn Iterator<Item = Py<PyAny>> + Send + Sync>,
37}
38
39#[pymethods]
40impl EnumIterator {
41 fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
42 slf
43 }
44
45 fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<Py<PyAny>> {
46 slf.iter.next()
47 }
48}
49
50impl EnumIterator {
51 #[must_use]
57 pub fn new<'py, E>(py: Python<'py>) -> Self
58 where
59 E: strum::IntoEnumIterator + IntoPyObjectExt<'py>,
60 <E as IntoEnumIterator>::Iterator: Send,
61 {
62 Self {
63 iter: Box::new(
64 E::iter()
65 .map(|var| var.into_py_any_unwrap(py))
66 .collect::<Vec<_>>()
68 .into_iter(),
69 ),
70 }
71 }
72}
73
74pub fn value_to_pydict(py: Python<'_>, val: &Value) -> PyResult<Py<PyAny>> {
86 let dict = PyDict::new(py);
87
88 match val {
89 Value::Object(map) => {
90 for (key, value) in map {
91 let py_value = value_to_pyobject(py, value)?;
92 dict.set_item(key, py_value)?;
93 }
94 }
95 _ => return Err(PyValueError::new_err("Expected JSON object")),
97 }
98
99 dict.into_py_any(py)
100}
101
102pub fn value_to_pyobject(py: Python<'_>, val: &Value) -> PyResult<Py<PyAny>> {
114 match val {
115 Value::Null => Ok(py.None()),
116 Value::Bool(b) => b.into_py_any(py),
117 Value::String(s) => s.into_py_any(py),
118 Value::Number(n) => {
119 if n.is_i64() {
120 n.as_i64().unwrap().into_py_any(py)
121 } else if n.is_f64() {
122 n.as_f64().unwrap().into_py_any(py)
123 } else {
124 Err(PyValueError::new_err("Unsupported JSON number type"))
125 }
126 }
127 Value::Array(arr) => {
128 let py_list =
129 PyList::new(py, &[] as &[Py<PyAny>]).expect("Invalid `ExactSizeIterator`");
130 for item in arr {
131 let py_item = value_to_pyobject(py, item)?;
132 py_list.append(py_item)?;
133 }
134 py_list.into_py_any(py)
135 }
136 Value::Object(_) => value_to_pydict(py, val),
137 }
138}
139
140pub fn commissions_from_vec(py: Python<'_>, commissions: Vec<Money>) -> PyResult<Bound<'_, PyAny>> {
150 let mut values = Vec::new();
151
152 for value in commissions {
153 values.push(value.to_string());
154 }
155
156 if values.is_empty() {
157 Ok(PyNone::get(py).to_owned().into_any())
158 } else {
159 values.sort();
160 Ok(PyList::new(py, &values).unwrap().into_any())
162 }
163}
164
165pub fn commissions_from_indexmap(
171 py: Python<'_>,
172 commissions: IndexMap<Currency, Money>,
173) -> PyResult<Bound<'_, PyAny>> {
174 commissions_from_vec(py, commissions.values().cloned().collect())
175}
176
177#[cfg(test)]
178mod tests {
179 use pyo3::{
180 prelude::*,
181 types::{PyBool, PyInt, PyString},
182 };
183 use rstest::rstest;
184 use serde_json::Value;
185
186 use super::*;
187
188 #[rstest]
189 fn test_value_to_pydict() {
190 Python::initialize();
191 Python::attach(|py| {
192 let json_str = r#"
193 {
194 "type": "OrderAccepted",
195 "ts_event": 42,
196 "is_reconciliation": false
197 }
198 "#;
199
200 let val: Value = serde_json::from_str(json_str).unwrap();
201 let py_dict_ref = value_to_pydict(py, &val).unwrap();
202 let py_dict = py_dict_ref.bind(py);
203
204 assert_eq!(
205 py_dict
206 .get_item("type")
207 .unwrap()
208 .downcast::<PyString>()
209 .unwrap()
210 .to_str()
211 .unwrap(),
212 "OrderAccepted"
213 );
214 assert_eq!(
215 py_dict
216 .get_item("ts_event")
217 .unwrap()
218 .downcast::<PyInt>()
219 .unwrap()
220 .extract::<i64>()
221 .unwrap(),
222 42
223 );
224 assert!(
225 !py_dict
226 .get_item("is_reconciliation")
227 .unwrap()
228 .downcast::<PyBool>()
229 .unwrap()
230 .is_true()
231 );
232 });
233 }
234
235 #[rstest]
236 fn test_value_to_pyobject_string() {
237 Python::initialize();
238 Python::attach(|py| {
239 let val = Value::String("Hello, world!".to_string());
240 let py_obj = value_to_pyobject(py, &val).unwrap();
241
242 assert_eq!(py_obj.extract::<&str>(py).unwrap(), "Hello, world!");
243 });
244 }
245
246 #[rstest]
247 fn test_value_to_pyobject_bool() {
248 Python::initialize();
249 Python::attach(|py| {
250 let val = Value::Bool(true);
251 let py_obj = value_to_pyobject(py, &val).unwrap();
252
253 assert!(py_obj.extract::<bool>(py).unwrap());
254 });
255 }
256
257 #[rstest]
258 fn test_value_to_pyobject_array() {
259 Python::initialize();
260 Python::attach(|py| {
261 let val = Value::Array(vec![
262 Value::String("item1".to_string()),
263 Value::String("item2".to_string()),
264 ]);
265 let binding = value_to_pyobject(py, &val).unwrap();
266 let py_list: &Bound<'_, PyList> = binding.bind(py).downcast::<PyList>().unwrap();
267
268 assert_eq!(py_list.len(), 2);
269 assert_eq!(
270 py_list.get_item(0).unwrap().extract::<&str>().unwrap(),
271 "item1"
272 );
273 assert_eq!(
274 py_list.get_item(1).unwrap().extract::<&str>().unwrap(),
275 "item2"
276 );
277 });
278 }
279}