use indexmap::IndexMap;
use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
use pyo3::{
conversion::IntoPyObjectExt,
prelude::*,
types::{PyDict, PyList, PyNone},
};
use serde_json::Value;
use strum::IntoEnumIterator;
use crate::types::{Currency, Money};
pub const PY_MODULE_MODEL: &str = "nautilus_trader.core.nautilus_pyo3.model";
#[allow(missing_debug_implementations)]
#[pyclass]
#[pyo3_stub_gen::derive::gen_stub_pyclass(module = "nautilus_trader.model")]
pub struct EnumIterator {
iter: Box<dyn Iterator<Item = Py<PyAny>> + Send + Sync>,
}
#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl EnumIterator {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<Py<PyAny>> {
slf.iter.next()
}
}
impl EnumIterator {
#[must_use]
pub fn new<'py, E>(py: Python<'py>) -> Self
where
E: strum::IntoEnumIterator + IntoPyObjectExt<'py>,
<E as IntoEnumIterator>::Iterator: Send,
{
Self {
iter: Box::new(
E::iter()
.map(|var| var.into_py_any_unwrap(py))
.collect::<Vec<_>>()
.into_iter(),
),
}
}
}
pub fn value_to_pydict(py: Python<'_>, val: &Value) -> PyResult<Py<PyAny>> {
let dict = PyDict::new(py);
match val {
Value::Object(map) => {
for (key, value) in map {
let py_value = value_to_pyobject(py, value)?;
dict.set_item(key, py_value)?;
}
}
_ => return Err(to_pyvalue_err("Expected JSON object")),
}
dict.into_py_any(py)
}
pub fn value_to_pyobject(py: Python<'_>, val: &Value) -> PyResult<Py<PyAny>> {
match val {
Value::Null => Ok(py.None()),
Value::Bool(b) => b.into_py_any(py),
Value::String(s) => s.into_py_any(py),
Value::Number(n) => {
if n.is_i64() {
n.as_i64().unwrap().into_py_any(py)
} else if n.is_u64() {
n.as_u64().unwrap().into_py_any(py)
} else if n.is_f64() {
n.as_f64().unwrap().into_py_any(py)
} else {
Err(to_pyvalue_err("Unsupported JSON number type"))
}
}
Value::Array(arr) => {
let py_list =
PyList::new(py, &[] as &[Py<PyAny>]).expect("Invalid `ExactSizeIterator`");
for item in arr {
let py_item = value_to_pyobject(py, item)?;
py_list.append(py_item)?;
}
py_list.into_py_any(py)
}
Value::Object(_) => value_to_pydict(py, val),
}
}
pub use nautilus_core::{
from_pydict as pydict_to_params, from_pydict, python::params::params_to_pydict,
};
pub fn commissions_from_vec(py: Python<'_>, commissions: Vec<Money>) -> PyResult<Bound<'_, PyAny>> {
let mut values = Vec::new();
for value in commissions {
values.push(value.to_string());
}
if values.is_empty() {
Ok(PyNone::get(py).to_owned().into_any())
} else {
values.sort();
Ok(PyList::new(py, &values)
.expect("ExactSizeIterator")
.into_any())
}
}
pub fn commissions_from_indexmap<'py>(
py: Python<'py>,
commissions: &IndexMap<Currency, Money>,
) -> PyResult<Bound<'py, PyAny>> {
commissions_from_vec(py, commissions.values().copied().collect())
}
#[cfg(test)]
mod tests {
use pyo3::{
prelude::*,
types::{PyBool, PyInt, PyString},
};
use rstest::rstest;
use serde_json::Value;
use super::*;
#[rstest]
fn test_value_to_pydict() {
Python::initialize();
Python::attach(|py| {
let json_str = r#"
{
"type": "OrderAccepted",
"ts_event": 42,
"is_reconciliation": false
}
"#;
let val: Value = serde_json::from_str(json_str).unwrap();
let py_dict_ref = value_to_pydict(py, &val).unwrap();
let py_dict = py_dict_ref.bind(py);
assert_eq!(
py_dict
.get_item("type")
.unwrap()
.cast::<PyString>()
.unwrap()
.to_str()
.unwrap(),
"OrderAccepted"
);
assert_eq!(
py_dict
.get_item("ts_event")
.unwrap()
.cast::<PyInt>()
.unwrap()
.extract::<i64>()
.unwrap(),
42
);
assert!(
!py_dict
.get_item("is_reconciliation")
.unwrap()
.cast::<PyBool>()
.unwrap()
.is_true()
);
});
}
#[rstest]
fn test_value_to_pyobject_string() {
Python::initialize();
Python::attach(|py| {
let val = Value::String("Hello, world!".to_string());
let py_obj = value_to_pyobject(py, &val).unwrap();
assert_eq!(py_obj.extract::<&str>(py).unwrap(), "Hello, world!");
});
}
#[rstest]
fn test_value_to_pyobject_bool() {
Python::initialize();
Python::attach(|py| {
let val = Value::Bool(true);
let py_obj = value_to_pyobject(py, &val).unwrap();
assert!(py_obj.extract::<bool>(py).unwrap());
});
}
#[rstest]
fn test_value_to_pyobject_array() {
Python::initialize();
Python::attach(|py| {
let val = Value::Array(vec![
Value::String("item1".to_string()),
Value::String("item2".to_string()),
]);
let binding = value_to_pyobject(py, &val).unwrap();
let py_list: &Bound<'_, PyList> = binding.bind(py).cast::<PyList>().unwrap();
assert_eq!(py_list.len(), 2);
assert_eq!(
py_list.get_item(0).unwrap().extract::<&str>().unwrap(),
"item1"
);
assert_eq!(
py_list.get_item(1).unwrap().extract::<&str>().unwrap(),
"item2"
);
});
}
}