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]
33#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")]
34pub struct EnumIterator {
35 iter: Box<dyn Iterator<Item = Py<PyAny>> + Send + Sync>,
37}
38
39#[pymethods]
40#[pyo3_stub_gen::derive::gen_stub_pymethods]
41impl EnumIterator {
42 fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
43 slf
44 }
45
46 fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<Py<PyAny>> {
47 slf.iter.next()
48 }
49}
50
51impl EnumIterator {
52 #[must_use]
58 pub fn new<'py, E>(py: Python<'py>) -> Self
59 where
60 E: strum::IntoEnumIterator + IntoPyObjectExt<'py>,
61 <E as IntoEnumIterator>::Iterator: Send,
62 {
63 Self {
64 iter: Box::new(
65 E::iter()
66 .map(|var| var.into_py_any_unwrap(py))
67 .collect::<Vec<_>>()
69 .into_iter(),
70 ),
71 }
72 }
73}
74
75pub fn value_to_pydict(py: Python<'_>, val: &Value) -> PyResult<Py<PyAny>> {
83 let dict = PyDict::new(py);
84
85 match val {
86 Value::Object(map) => {
87 for (key, value) in map {
88 let py_value = value_to_pyobject(py, value)?;
89 dict.set_item(key, py_value)?;
90 }
91 }
92 _ => return Err(to_pyvalue_err("Expected JSON object")),
94 }
95
96 dict.into_py_any(py)
97}
98
99pub fn value_to_pyobject(py: Python<'_>, val: &Value) -> PyResult<Py<PyAny>> {
111 match val {
112 Value::Null => Ok(py.None()),
113 Value::Bool(b) => b.into_py_any(py),
114 Value::String(s) => s.into_py_any(py),
115 Value::Number(n) => {
116 if n.is_i64() {
117 n.as_i64().unwrap().into_py_any(py)
118 } else if n.is_u64() {
119 n.as_u64().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 use nautilus_core::{
142 from_pydict as pydict_to_params, from_pydict, python::params::params_to_pydict,
143};
144
145pub fn commissions_from_vec(py: Python<'_>, commissions: Vec<Money>) -> PyResult<Bound<'_, PyAny>> {
155 let mut values = Vec::new();
156
157 for value in commissions {
158 values.push(value.to_string());
159 }
160
161 if values.is_empty() {
162 Ok(PyNone::get(py).to_owned().into_any())
163 } else {
164 values.sort();
165 Ok(PyList::new(py, &values)
166 .expect("ExactSizeIterator")
167 .into_any())
168 }
169}
170
171pub fn commissions_from_indexmap<'py>(
177 py: Python<'py>,
178 commissions: &IndexMap<Currency, Money>,
179) -> PyResult<Bound<'py, PyAny>> {
180 commissions_from_vec(py, commissions.values().copied().collect())
181}
182
183#[cfg(test)]
184mod tests {
185 use pyo3::{
186 prelude::*,
187 types::{PyBool, PyInt, PyString},
188 };
189 use rstest::rstest;
190 use serde_json::Value;
191
192 use super::*;
193
194 #[rstest]
195 fn test_value_to_pydict() {
196 Python::initialize();
197 Python::attach(|py| {
198 let json_str = r#"
199 {
200 "type": "OrderAccepted",
201 "ts_event": 42,
202 "is_reconciliation": false
203 }
204 "#;
205
206 let val: Value = serde_json::from_str(json_str).unwrap();
207 let py_dict_ref = value_to_pydict(py, &val).unwrap();
208 let py_dict = py_dict_ref.bind(py);
209
210 assert_eq!(
211 py_dict
212 .get_item("type")
213 .unwrap()
214 .cast::<PyString>()
215 .unwrap()
216 .to_str()
217 .unwrap(),
218 "OrderAccepted"
219 );
220 assert_eq!(
221 py_dict
222 .get_item("ts_event")
223 .unwrap()
224 .cast::<PyInt>()
225 .unwrap()
226 .extract::<i64>()
227 .unwrap(),
228 42
229 );
230 assert!(
231 !py_dict
232 .get_item("is_reconciliation")
233 .unwrap()
234 .cast::<PyBool>()
235 .unwrap()
236 .is_true()
237 );
238 });
239 }
240
241 #[rstest]
242 fn test_value_to_pyobject_string() {
243 Python::initialize();
244 Python::attach(|py| {
245 let val = Value::String("Hello, world!".to_string());
246 let py_obj = value_to_pyobject(py, &val).unwrap();
247
248 assert_eq!(py_obj.extract::<&str>(py).unwrap(), "Hello, world!");
249 });
250 }
251
252 #[rstest]
253 fn test_value_to_pyobject_bool() {
254 Python::initialize();
255 Python::attach(|py| {
256 let val = Value::Bool(true);
257 let py_obj = value_to_pyobject(py, &val).unwrap();
258
259 assert!(py_obj.extract::<bool>(py).unwrap());
260 });
261 }
262
263 #[rstest]
264 fn test_value_to_pyobject_array() {
265 Python::initialize();
266 Python::attach(|py| {
267 let val = Value::Array(vec![
268 Value::String("item1".to_string()),
269 Value::String("item2".to_string()),
270 ]);
271 let binding = value_to_pyobject(py, &val).unwrap();
272 let py_list: &Bound<'_, PyList> = binding.bind(py).cast::<PyList>().unwrap();
273
274 assert_eq!(py_list.len(), 2);
275 assert_eq!(
276 py_list.get_item(0).unwrap().extract::<&str>().unwrap(),
277 "item1"
278 );
279 assert_eq!(
280 py_list.get_item(1).unwrap().extract::<&str>().unwrap(),
281 "item2"
282 );
283 });
284 }
285}