Skip to main content

nautilus_trading/python/
controller.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::collections::HashMap;
17
18use nautilus_core::python::to_pyvalue_err;
19use pyo3::{
20    prelude::*,
21    types::{PyDict, PyDictMethods, PyModule},
22};
23
24use crate::ImportableControllerConfig;
25
26#[pyo3::pymethods]
27#[pyo3_stub_gen::derive::gen_stub_pymethods]
28impl ImportableControllerConfig {
29    /// Configuration for creating controllers from importable paths.
30    #[new]
31    #[expect(clippy::needless_pass_by_value)]
32    fn py_new(controller_path: String, config_path: String, config: Py<PyDict>) -> PyResult<Self> {
33        let json_config = Python::attach(|py| -> PyResult<HashMap<String, serde_json::Value>> {
34            let kwargs = PyDict::new(py);
35            kwargs.set_item("default", py.eval(pyo3::ffi::c_str!("str"), None, None)?)?;
36            let json_str: String = PyModule::import(py, "json")?
37                .call_method("dumps", (config.bind(py),), Some(&kwargs))?
38                .extract()?;
39
40            let json_value: serde_json::Value =
41                serde_json::from_str(&json_str).map_err(to_pyvalue_err)?;
42
43            if let serde_json::Value::Object(map) = json_value {
44                Ok(map.into_iter().collect())
45            } else {
46                Err(to_pyvalue_err("Config must be a dictionary"))
47            }
48        })?;
49
50        Ok(Self {
51            controller_path,
52            config_path,
53            config: json_config,
54        })
55    }
56
57    #[getter]
58    fn controller_path(&self) -> &String {
59        &self.controller_path
60    }
61
62    #[getter]
63    fn config_path(&self) -> &String {
64        &self.config_path
65    }
66
67    #[getter]
68    fn config(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
69        let py_dict = PyDict::new(py);
70
71        for (key, value) in &self.config {
72            let json_str = serde_json::to_string(value).map_err(to_pyvalue_err)?;
73            let py_value = PyModule::import(py, "json")?.call_method("loads", (json_str,), None)?;
74            py_dict.set_item(key, py_value)?;
75        }
76        Ok(py_dict.unbind())
77    }
78}