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}