pyridis_node/
node.rs

1use std::ffi::CString;
2
3use pyo3::IntoPyObjectExt;
4
5use crate::prelude::{
6    thirdparty::{
7        ird::thirdparty::*,
8        pyo3::{ffi::c_str, prelude::*, types::*},
9    },
10    *,
11};
12
13#[derive(ird::Node)]
14pub struct PythonNode {
15    pub instance: PyObject,
16}
17
18impl ird::Node for PythonNode {
19    fn new(
20        inputs: ird::Inputs,
21        outputs: ird::Outputs,
22        queries: ird::Queries,
23        queryables: ird::Queryables,
24        configuration: serde_yml::Value,
25    ) -> tokio::task::JoinHandle<Result<Box<dyn ird::Node>>> {
26        pyo3::prepare_freethreaded_python();
27
28        let mut builder = tokio::runtime::Builder::new_current_thread();
29        builder.enable_all();
30
31        pyo3_async_runtimes::tokio::init(builder);
32
33        std::thread::spawn(|| {
34            pyo3_async_runtimes::tokio::get_runtime()
35                .block_on(pyo3_async_runtimes::tokio::re_exports::pending::<()>())
36        });
37
38        pyo3_async_runtimes::tokio::get_runtime().spawn_blocking(move || {
39            Python::with_gil(|py| {
40                pyo3_async_runtimes::tokio::run(py, async move {
41                    let file_path = configuration
42                        .get("python_file_path")
43                        .ok_or_eyre("Cannot find python file path inside configuration")?
44                        .as_str();
45
46                    let file_path = file_path
47                        .ok_or_eyre(format!("Invalid python file path: '{:?}'", file_path))?;
48
49                    let py_script = tokio::fs::read_to_string(file_path)
50                        .await
51                        .wrap_err(format!("Couldn't read path '{}'", file_path))?;
52
53                    let module: PyObject = Python::with_gil(|py| -> Result<PyObject> {
54                        Ok(PyModule::from_code(
55                            py,
56                            CString::new(py_script)?.as_c_str(),
57                            CString::new(file_path)?.as_c_str(),
58                            c_str!("pyridis_node"),
59                        )?
60                        .into())
61                    })?;
62
63                    let instance: PyObject = Python::with_gil(|py| -> PyResult<PyObject> {
64                        let class = module.call_method0(py, "pyridis_node")?;
65
66                        class.call0(py)
67                    })?;
68
69                    let configuration: PyObject = Python::with_gil(|py| -> PyResult<PyObject> {
70                        make_py_object(py, configuration)
71                    })?;
72
73                    let inputs = Inputs(inputs);
74                    let outputs = Outputs(outputs);
75                    let queries = Queries(queries);
76                    let queryables = Queryables(queryables);
77
78                    let fut = Python::with_gil(|py| {
79                        pyo3_async_runtimes::tokio::into_future(
80                            instance
81                                .call_method1(
82                                    py,
83                                    "new",
84                                    (inputs, outputs, queries, queryables, configuration),
85                                )?
86                                .into_bound(py),
87                        )
88                    })?;
89
90                    fut.await
91                        .wrap_err("Couldn't await for the instance call to 'new'")?;
92
93                    Ok(Box::new(Self { instance }) as Box<dyn ird::Node>)
94                })
95                .map_err(|e| {
96                    e.print_and_set_sys_last_vars(py);
97
98                    eyre::eyre!(e)
99                })
100            })
101        })
102    }
103
104    fn start(self: Box<Self>) -> tokio::task::JoinHandle<Result<()>> {
105        pyo3_async_runtimes::tokio::get_runtime().spawn_blocking(move || {
106            Python::with_gil(|py| {
107                pyo3_async_runtimes::tokio::run(py, async move {
108                    {
109                        let fut = Python::with_gil(|py| {
110                            pyo3_async_runtimes::tokio::into_future(
111                                self.instance.call_method0(py, "start")?.into_bound(py),
112                            )
113                        })?;
114
115                        fut.await
116                            .wrap_err("Couldn't await for the instance call to 'start'")?;
117
118                        Ok(())
119                    }
120                })
121                .map_err(|e| {
122                    e.print_and_set_sys_last_vars(py);
123
124                    eyre::eyre!(e)
125                })
126            })
127        })
128    }
129}
130
131pub fn make_py_object(py: Python, configuration: serde_yml::Value) -> PyResult<PyObject> {
132    match configuration {
133        serde_yml::Value::Null => Ok(py.None()),
134        serde_yml::Value::Bool(b) => b.into_py_any(py),
135        serde_yml::Value::Number(n) => {
136            if n.is_u64() {
137                n.as_u64().unwrap().into_py_any(py)
138            } else if n.is_i64() {
139                n.as_i64().unwrap().into_py_any(py)
140            } else if n.is_f64() {
141                n.as_f64().unwrap().into_py_any(py)
142            } else {
143                Ok(py.None())
144            }
145        }
146        serde_yml::Value::String(s) => s.into_py_any(py),
147        serde_yml::Value::Sequence(s) => {
148            let py_list = PyList::empty(py);
149
150            for v in s {
151                py_list.append(make_py_object(py, v)?)?;
152            }
153
154            py_list.into_py_any(py)
155        }
156        serde_yml::Value::Mapping(m) => {
157            let py_dict = PyDict::new(py);
158
159            for (k, v) in m.map {
160                let kp = make_py_object(py, k)?;
161                let vp = make_py_object(py, v)?;
162
163                py_dict.set_item(kp, vp)?;
164            }
165
166            py_dict.into_py_any(py)
167        }
168        _ => Ok(py.None()),
169    }
170}