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