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>> {
81 let dict = PyDict::new(py);
82
83 match val {
84 Value::Object(map) => {
85 for (key, value) in map {
86 let py_value = value_to_pyobject(py, value)?;
87 dict.set_item(key, py_value)?;
88 }
89 }
90 _ => return Err(to_pyvalue_err("Expected JSON object")),
92 }
93
94 dict.into_py_any(py)
95}
96
97pub fn value_to_pyobject(py: Python<'_>, val: &Value) -> PyResult<Py<PyAny>> {
109 match val {
110 Value::Null => Ok(py.None()),
111 Value::Bool(b) => b.into_py_any(py),
112 Value::String(s) => s.into_py_any(py),
113 Value::Number(n) => {
114 if n.is_i64() {
115 n.as_i64().unwrap().into_py_any(py)
116 } else if n.is_u64() {
117 n.as_u64().unwrap().into_py_any(py)
118 } else if n.is_f64() {
119 n.as_f64().unwrap().into_py_any(py)
120 } else {
121 Err(to_pyvalue_err("Unsupported JSON number type"))
122 }
123 }
124 Value::Array(arr) => {
125 let py_list =
126 PyList::new(py, &[] as &[Py<PyAny>]).expect("Invalid `ExactSizeIterator`");
127 for item in arr {
128 let py_item = value_to_pyobject(py, item)?;
129 py_list.append(py_item)?;
130 }
131 py_list.into_py_any(py)
132 }
133 Value::Object(_) => value_to_pydict(py, val),
134 }
135}
136
137pub use nautilus_core::{
140 from_pydict as pydict_to_params, from_pydict, python::params::params_to_pydict,
141};
142
143pub fn commissions_from_vec(py: Python<'_>, commissions: Vec<Money>) -> PyResult<Bound<'_, PyAny>> {
153 let mut values = Vec::new();
154
155 for value in commissions {
156 values.push(value.to_string());
157 }
158
159 if values.is_empty() {
160 Ok(PyNone::get(py).to_owned().into_any())
161 } else {
162 values.sort();
163 Ok(PyList::new(py, &values)
164 .expect("ExactSizeIterator")
165 .into_any())
166 }
167}
168
169pub fn commissions_from_indexmap(
175 py: Python<'_>,
176 commissions: IndexMap<Currency, Money>,
177) -> PyResult<Bound<'_, PyAny>> {
178 commissions_from_vec(py, commissions.values().copied().collect())
179}
180
181#[cfg(test)]
182mod tests {
183 use pyo3::{
184 prelude::*,
185 types::{PyBool, PyInt, PyString},
186 };
187 use rstest::rstest;
188 use serde_json::Value;
189
190 use super::*;
191
192 #[rstest]
193 fn test_value_to_pydict() {
194 Python::initialize();
195 Python::attach(|py| {
196 let json_str = r#"
197 {
198 "type": "OrderAccepted",
199 "ts_event": 42,
200 "is_reconciliation": false
201 }
202 "#;
203
204 let val: Value = serde_json::from_str(json_str).unwrap();
205 let py_dict_ref = value_to_pydict(py, &val).unwrap();
206 let py_dict = py_dict_ref.bind(py);
207
208 assert_eq!(
209 py_dict
210 .get_item("type")
211 .unwrap()
212 .cast::<PyString>()
213 .unwrap()
214 .to_str()
215 .unwrap(),
216 "OrderAccepted"
217 );
218 assert_eq!(
219 py_dict
220 .get_item("ts_event")
221 .unwrap()
222 .cast::<PyInt>()
223 .unwrap()
224 .extract::<i64>()
225 .unwrap(),
226 42
227 );
228 assert!(
229 !py_dict
230 .get_item("is_reconciliation")
231 .unwrap()
232 .cast::<PyBool>()
233 .unwrap()
234 .is_true()
235 );
236 });
237 }
238
239 #[rstest]
240 fn test_value_to_pyobject_string() {
241 Python::initialize();
242 Python::attach(|py| {
243 let val = Value::String("Hello, world!".to_string());
244 let py_obj = value_to_pyobject(py, &val).unwrap();
245
246 assert_eq!(py_obj.extract::<&str>(py).unwrap(), "Hello, world!");
247 });
248 }
249
250 #[rstest]
251 fn test_value_to_pyobject_bool() {
252 Python::initialize();
253 Python::attach(|py| {
254 let val = Value::Bool(true);
255 let py_obj = value_to_pyobject(py, &val).unwrap();
256
257 assert!(py_obj.extract::<bool>(py).unwrap());
258 });
259 }
260
261 #[rstest]
262 fn test_value_to_pyobject_array() {
263 Python::initialize();
264 Python::attach(|py| {
265 let val = Value::Array(vec![
266 Value::String("item1".to_string()),
267 Value::String("item2".to_string()),
268 ]);
269 let binding = value_to_pyobject(py, &val).unwrap();
270 let py_list: &Bound<'_, PyList> = binding.bind(py).cast::<PyList>().unwrap();
271
272 assert_eq!(py_list.len(), 2);
273 assert_eq!(
274 py_list.get_item(0).unwrap().extract::<&str>().unwrap(),
275 "item1"
276 );
277 assert_eq!(
278 py_list.get_item(1).unwrap().extract::<&str>().unwrap(),
279 "item2"
280 );
281 });
282 }
283}