nautilus_core/python/
params.rs1use pyo3::{
19 conversion::IntoPyObjectExt,
20 prelude::*,
21 types::{PyDict, PyList, PyModule},
22};
23use serde_json::Value;
24
25use crate::{
26 params::Params,
27 python::{serialization::from_pyobject_pyo3, to_pyvalue_err},
28};
29
30pub fn pydict_to_params(py: Python<'_>, dict: &Py<PyDict>) -> PyResult<Option<Params>> {
42 let dict_bound = dict.bind(py);
43 if dict_bound.is_empty() {
44 return Ok(None);
45 }
46
47 from_pyobject_pyo3(py, dict_bound.as_any()).map(Some)
48}
49
50pub fn value_to_pyobject(py: Python<'_>, val: &Value) -> PyResult<Py<PyAny>> {
59 match val {
60 Value::Null => Ok(py.None()),
61 Value::Bool(b) => b.into_py_any(py),
62 Value::String(s) => s.into_py_any(py),
63 Value::Number(n) => {
64 if n.is_i64() {
65 n.as_i64()
66 .ok_or_else(|| to_pyvalue_err("JSON number could not be read as i64"))?
67 .into_py_any(py)
68 } else if n.is_u64() {
69 n.as_u64()
70 .ok_or_else(|| to_pyvalue_err("JSON number could not be read as u64"))?
71 .into_py_any(py)
72 } else if n.is_f64() {
73 n.as_f64()
74 .ok_or_else(|| to_pyvalue_err("JSON number could not be read as f64"))?
75 .into_py_any(py)
76 } else {
77 Err(to_pyvalue_err("Unsupported JSON number type"))
78 }
79 }
80 Value::Array(arr) => {
81 let py_list = PyList::new(py, &[] as &[Py<PyAny>])?;
82 for item in arr {
83 let py_item = value_to_pyobject(py, item)?;
84 py_list.append(py_item)?;
85 }
86 py_list.into_py_any(py)
87 }
88 Value::Object(_) => {
89 let json_str = serde_json::to_string(val).map_err(to_pyvalue_err)?;
91 let py_dict: Py<PyDict> = PyModule::import(py, "json")?
92 .call_method("loads", (json_str,), None)?
93 .extract()?;
94 py_dict.into_py_any(py)
95 }
96 }
97}
98
99pub fn params_to_pydict(py: Python<'_>, params: &Params) -> PyResult<Py<PyDict>> {
105 let dict = PyDict::new(py);
106 for (key, value) in params {
107 let py_value = value_to_pyobject(py, value)?;
108 dict.set_item(key, py_value)?;
109 }
110 Ok(dict.into())
111}
112
113#[cfg(test)]
114mod tests {
115 use rstest::rstest;
116 use serde_json::json;
117
118 use super::*;
119
120 #[derive(Debug, Clone, Copy)]
121 enum ExpectedNumber {
122 I64(i64),
123 U64(u64),
124 F64(f64),
125 }
126
127 #[rstest]
128 #[case(json!(-100_i64), ExpectedNumber::I64(-100))]
129 #[case(json!(42_u64), ExpectedNumber::U64(42))]
130 #[case(json!(2.5_f64), ExpectedNumber::F64(2.5))]
131 fn test_value_to_pyobject_number_branches(
132 #[case] value: Value,
133 #[case] expected: ExpectedNumber,
134 ) {
135 Python::initialize();
136 Python::attach(|py| {
137 let py_obj = value_to_pyobject(py, &value).unwrap();
138
139 match expected {
140 ExpectedNumber::I64(expected) => {
141 assert_eq!(py_obj.extract::<i64>(py).unwrap(), expected);
142 }
143 ExpectedNumber::U64(expected) => {
144 assert_eq!(py_obj.extract::<u64>(py).unwrap(), expected);
145 }
146 ExpectedNumber::F64(expected) => {
147 let actual = py_obj.extract::<f64>(py).unwrap();
148 assert!((actual - expected).abs() < f64::EPSILON);
149 }
150 }
151 });
152 }
153}