rumtk_core/
scripting.rs

1/*
2 * rumtk attempts to implement HL7 and medical protocols for interoperability in medicine.
3 * This toolkit aims to be reliable, simple, performant, and standards compliant.
4 * Copyright (C) 2025  Luis M. Santos, M.D.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19 */
20
21pub mod python_utils {
22    use std::ffi::{CString, OsStr};
23    use std::fmt::Debug;
24    use std::fs::read_to_string;
25    use std::path::Path;
26
27    use crate::core::RUMResult;
28    use crate::strings::RUMString;
29    use compact_str::format_compact;
30
31    use pyo3::prelude::*;
32    use pyo3::types::{PyList, PyTuple};
33
34    pub type RUMPyArgs = Py<PyTuple>;
35    pub type RUMPyList = Py<PyList>;
36    pub type RUMPyResultList = Vec<RUMString>;
37    pub type RUMPyModule = Py<PyModule>;
38    pub type RUMPyTuple = Py<PyTuple>;
39    pub type RUMPyFunction = Py<PyAny>;
40    pub type RUMPyAny = Py<PyAny>;
41    pub type RUMPython<'py> = Python<'py>;
42    pub type RUMPyResult<T> = PyResult<T>;
43
44    fn string_to_cstring(data: &str) -> RUMResult<CString> {
45        match CString::new(data) {
46            Ok(code) => Ok(code),
47            Err(e) => Err(format_compact!(
48                "Could not cast Python code string to a C string!"
49            )),
50        }
51    }
52
53    fn ostring_to_cstring(data: &OsStr) -> RUMResult<CString> {
54        let data_str = match data.to_str() {
55            Some(s) => s,
56            None => return Err(format_compact!("Could not cast OsStr to a str!")),
57        };
58        match CString::new(data_str) {
59            Ok(code) => Ok(code),
60            Err(e) => Err(format_compact!(
61                "Could not cast Python code string to a C string because {:#?}!",
62                e
63            )),
64        }
65    }
66
67    pub fn py_list_to_tuple(py: RUMPython, py_list: &RUMPyList) -> RUMResult<RUMPyTuple> {
68        match PyTuple::new(py, py_list.bind(py).iter()) {
69            Ok(py_args) => Ok(py_args.into()),
70            Err(e) => Err(format_compact!(
71                "Failed to convert arguments from PyList to PyTuple! Reason: {:?}",
72                e
73            )),
74        }
75    }
76
77    ///
78    /// Convert a vector of `T` to a Python List of `T`.
79    ///
80    /// ## Example
81    ///
82    /// ```
83    ///     use compact_str::format_compact;
84    ///     use pyo3::Python;
85    ///     use crate::rumtk_core::scripting::python_utils::{py_buildargs, py_extract_string_vector, py_list_to_tuple};
86    ///
87    ///     let expect: Vec<&str> = vec!["a", "1", "2"];
88    ///
89    ///     Python::attach( |py| {
90    ///             let py_args = py_buildargs(py, &expect).unwrap();
91    ///             let py_obj = py_list_to_tuple(py, &py_args).unwrap();
92    ///             let result = py_extract_string_vector(&py_obj).unwrap();
93    ///             assert_eq!(&result, &expect, "{}", format_compact!("Python list does not match the input list!\nGot: {:?}\nExpected: {:?}", &result, &expect));
94    ///         }
95    ///     )
96    /// ```
97    ///
98    pub fn py_buildargs<'a, 'py, T>(py: RUMPython<'py>, args: &Vec<T>) -> RUMResult<RUMPyList>
99    where
100        T: FromPyObject<'a, 'py> + IntoPyObject<'py> + Debug + Clone,
101    {
102        match PyList::new(py, args.clone()) {
103            Ok(py_args) => Ok(py_args.into()),
104            Err(e) => Err(
105                format_compact!(
106                    "Failed to convert arguments into a Python Object for transfer to Interpreter! Arguments: {:?} Reason: {:?}",
107                    &args,
108                    e.to_string()
109                )
110            )
111        }
112    }
113
114    ///
115    /// Create empty Python List, which can be used for creating a collection of arguments to pass
116    /// to script.
117    ///
118    /// ## Example
119    ///
120    /// ```
121    ///     use compact_str::format_compact;
122    ///     use pyo3::Python;
123    ///     use pyo3::types::{PyListMethods, PyAnyMethods};
124    ///     use crate::rumtk_core::scripting::python_utils::{py_new_args, py_push_arg, RUMPyArgs, RUMPyList};
125    ///     use crate::rumtk_core::scripting::python_utils::{py_buildargs, py_extract_string_vector};
126    ///
127    ///
128    ///     Python::attach( |py| {
129    ///             let example_arg_1 = 1;
130    ///             let example_arg_2 = "Hello";
131    ///             let mut py_args: RUMPyList = py_new_args(py);
132    ///             py_push_arg(py, &mut py_args, &example_arg_1.clone()).unwrap();
133    ///             py_push_arg(py, &mut py_args, &example_arg_2.clone()).unwrap();
134    ///             let arg_1: usize = py_args.bind(py).get_item(0).unwrap().extract().unwrap();
135    ///             assert_eq!(&example_arg_1, &arg_1, "{}", format_compact!("Python list does not match the input list!\nGot: {:?}\nExpected: {:?}", &arg_1, &example_arg_1));
136    ///         }
137    ///     )
138    /// ```
139    ///
140    pub fn py_new_args(py: RUMPython) -> RUMPyList {
141        PyList::empty(py).unbind()
142    }
143
144    ///
145    /// Push argument of type `T` into instance of Python List. We can then use the list to pass
146    /// arguments to Python function or method.
147    ///
148    /// ## Example
149    ///
150    /// ```
151    ///     use compact_str::format_compact;
152    ///     use pyo3::Python;
153    ///     use pyo3::types::{PyListMethods, PyAnyMethods};
154    ///     use crate::rumtk_core::scripting::python_utils::{py_new_args, py_push_arg, RUMPyArgs, RUMPyList};
155    ///     use crate::rumtk_core::scripting::python_utils::{py_buildargs, py_extract_string_vector};
156    ///
157    ///
158    ///     Python::attach( |py| {
159    ///             let example_arg_1 = 1;
160    ///             let example_arg_2 = "Hello";
161    ///             let mut py_args: RUMPyList = py_new_args(py);
162    ///             py_push_arg(py, &mut py_args, &example_arg_1.clone()).unwrap();
163    ///             py_push_arg(py, &mut py_args, &example_arg_2.clone()).unwrap();
164    ///             let arg_1: usize = py_args.bind(py).get_item(0).unwrap().extract().unwrap();
165    ///             assert_eq!(&example_arg_1, &arg_1, "{}", format_compact!("Python list does not match the input list!\nGot: {:?}\nExpected: {:?}", &arg_1, &example_arg_1));
166    ///         }
167    ///     )
168    /// ```
169    ///
170    pub fn py_push_arg<'a, 'py, T>(
171        py: RUMPython<'py>,
172        py_args: &mut RUMPyList,
173        arg: &T,
174    ) -> RUMResult<()>
175    where
176        T: FromPyObject<'a, 'py> + IntoPyObject<'py> + Debug + Clone,
177    {
178        match py_args.bind(py).append((*arg).clone()) {
179            Ok(_) => Ok(()),
180            Err(e) => Err(
181                format_compact!(
182                    "Failed to convert argument into a Python Object for transfer to Interpreter! Argument: {:?} Reason: {:?}",
183                    &arg,
184                    e.to_string()
185                )
186            )
187        }
188    }
189
190    fn string_vector_to_rumstring_vector(list: &Vec<String>) -> RUMPyResultList {
191        let mut rumstring_vector = Vec::<RUMString>::with_capacity(list.len());
192
193        for itm in list {
194            rumstring_vector.push(RUMString::from(itm));
195        }
196
197        rumstring_vector
198    }
199
200    pub fn py_extract_string_vector(pyargs: &RUMPyArgs) -> RUMResult<RUMPyResultList> {
201        Python::attach(|py| -> RUMResult<RUMPyResultList> {
202            let py_list: Vec<String> = match pyargs.extract(py) {
203                Ok(list) => list,
204                Err(e) => {
205                    return Err(format_compact!(
206                        "Could not extract list from Python args! Reason => {:?}",
207                        e
208                    ));
209                }
210            };
211            Ok(string_vector_to_rumstring_vector(&py_list))
212        })
213    }
214
215    ///
216    /// Extract value returned from functions and modules via a `PyAny` object.
217    ///
218    /// ## Example Usage
219    ///
220    /// ### Example W/ RustType
221    ///
222    /// ```
223    ///use compact_str::format_compact;
224    ///     use pyo3::Python;
225    ///     use pyo3::types::{PyListMethods, PyAnyMethods, PyString};
226    ///     use crate::rumtk_core::scripting::python_utils::{py_extract_any, py_new_args, py_push_arg, RUMPyArgs, RUMPyList};
227    ///     use crate::rumtk_core::scripting::python_utils::{py_buildargs, py_extract_string_vector};
228    ///
229    ///
230    ///     Python::attach(|py| {
231    ///             let example_arg_1 = "Hello";
232    ///             let py_arg = PyString::new(py, example_arg_1);
233    ///             let arg: String = py_arg.extract().unwrap();
234    ///             let arg_1: String = py_extract_any(py, &py_arg.as_any().clone().unbind()).unwrap();
235    ///             assert_eq!(&example_arg_1, &arg_1, "{}", format_compact!("Python conversion failed!\nGot: {:?}\nExpected: {:?}", &arg_1, &example_arg_1));
236    ///         }
237    ///     )
238    /// ```
239    ///
240    /// ### Example W/ Custom Type
241    ///
242    /// ```
243    ///use compact_str::format_compact;
244    ///     use pyo3::{Python, pyclass, IntoPyObjectExt};
245    ///     use pyo3::types::{PyListMethods, PyAnyMethods, PyString};
246    ///     use crate::rumtk_core::scripting::python_utils::{py_extract_any, py_new_args, py_push_arg, RUMPyAny, RUMPyArgs, RUMPyList};
247    ///     use crate::rumtk_core::scripting::python_utils::{py_buildargs, py_extract_string_vector};
248    ///
249    ///     #[pyclass]
250    ///     #[derive(Clone, Debug, PartialOrd, PartialEq)]
251    ///     struct MyWrapper {
252    ///         text: String
253    ///     }
254    ///
255    ///     Python::attach(|py| {
256    ///             let example_arg_1 = MyWrapper{text: String::from("Hello")};
257    ///             let py_arg: RUMPyAny = example_arg_1.clone().into_py_any(py).unwrap();
258    ///             let arg_1: MyWrapper = py_extract_any(py, &py_arg).unwrap();
259    ///             assert_eq!(&example_arg_1, &arg_1, "{}", format_compact!("Python conversion failed!\nGot: {:?}\nExpected: {:?}", &arg_1, &example_arg_1));
260    ///         }
261    ///     )
262    /// ```
263    ///
264    pub fn py_extract_any<'py, T>(py: Python<'py>, pyresult: &'py RUMPyAny) -> RUMResult<T>
265    where
266        T: FromPyObject<'py, 'py> + Clone,
267        <T as pyo3::FromPyObject<'py, 'py>>::Error: Debug,
268    {
269        match pyresult.extract(py) {
270            Ok(r) => {
271                let val = r;
272                Ok(val)
273            }
274            Err(e) => Err(format_compact!(
275                "Could not extract vector from Python result! Reason => {:?}",
276                e
277            )),
278        }
279    }
280
281    ///
282    /// Load a python module from a given file path!
283    ///
284    /// ## Example Usage
285    ///
286    /// ```
287    ///     use compact_str::format_compact;
288    /// use pyo3::Python;
289    ///     use pyo3::types::PyModule;
290    ///     use crate::rumtk_core::scripting::python_utils::RUMPyModule;
291    ///     use crate::rumtk_core::scripting::python_utils::{py_load};
292    ///     use crate::rumtk_core::strings::RUMString;
293    ///     use uuid::Uuid;
294    ///
295    ///     let expected: &str = "print('Hello World!')\ndef test():\n\treturn 'Hello'";
296    ///     let fpath: RUMString = format_compact!("/tmp/{}.py", Uuid::new_v4());
297    ///     std::fs::write(&fpath, expected.as_bytes()).expect("Failure to write test module.");
298    ///
299    ///     Python::attach(|py| {
300    ///         let py_obj: RUMPyModule = py_load(py, &fpath).expect("Failure to load module!");
301    ///     });
302    ///     std::fs::remove_file(&fpath).unwrap()
303    /// ```
304    ///
305    pub fn py_load(py: Python, fpath: &str) -> RUMResult<RUMPyModule> {
306        let pypath = Path::new(fpath);
307        let pycode = match read_to_string(fpath) {
308            Ok(code) => string_to_cstring(&code)?,
309            Err(e) => {
310                return Err(format_compact!(
311                    "Unable to read Python file {}. Is it valid?",
312                    &fpath
313                ));
314            }
315        };
316        let filename = match pypath.file_name() {
317            Some(name) => ostring_to_cstring(name)?,
318            None => {
319                return Err(format_compact!("Invalid Python module path {}!", &fpath));
320            }
321        };
322        let modname = match pypath.file_stem() {
323            Some(name) => ostring_to_cstring(name)?,
324            None => {
325                return Err(format_compact!("Invalid Python module path {}!", &fpath));
326            }
327        };
328        let pymod = match PyModule::from_code(py, pycode.as_c_str(), &filename, &modname) {
329            Ok(pymod) => pymod,
330            Err(e) => {
331                return Err(format_compact!(
332                    "Failed to load Python module {} because of {:#?}!",
333                    &fpath,
334                    e
335                ));
336            }
337        };
338        Ok(pymod.into())
339    }
340
341    ///
342    /// Function for executing a python module's function.
343    /// If you set the argument `func_name` to an empty string, `py_exec` will do nothing. Allegedly,
344    /// the module executed upon import.
345    ///
346    /// It is recommended you have a function to call from the module!!!
347    ///
348    /// # Examples
349    ///
350    /// ## Executing Function Within Module
351    ///
352    /// ```
353    ///     use compact_str::format_compact;
354    ///     use pyo3::{Python, IntoPyObjectExt};
355    ///     use pyo3::types::PyModule;
356    ///     use crate::rumtk_core::scripting::python_utils::{RUMPyAny, RUMPyArgs, RUMPyModule, RUMPyList};
357    ///     use crate::rumtk_core::scripting::python_utils::{py_load, py_exec_module, py_buildargs, py_list_to_tuple};
358    ///     use uuid::Uuid;
359    ///     use crate::rumtk_core::strings::RUMString;
360    ///
361    ///     let expected: &str = "print('Hello World!')\ndef test():\n\treturn 'Hello'";
362    ///     let fpath: RUMString = format_compact!("/tmp/{}.py", Uuid::new_v4());
363    ///     std::fs::write(&fpath, expected.as_bytes()).expect("Failure to write test module.");
364    ///
365    ///     let expect: Vec<&str> = vec![];
366    ///
367    ///     Python::attach( |py| {
368    ///         let py_obj: RUMPyModule = py_load(py, &fpath).expect("Failure to load module!");
369    ///         let args: RUMPyList = py_buildargs(py, &expect).unwrap();
370    ///
371    ///         let result = py_exec_module(py, &py_obj, "test", &args).expect("Failed to extract result!");
372    ///    });
373    ///
374    ///     std::fs::remove_file(&fpath).unwrap()
375    ///```
376    ///
377    /// ## Executing Module
378    ///
379    /// ```
380    ///     use compact_str::format_compact;
381    ///     use pyo3::{Python, IntoPyObjectExt};
382    ///     use pyo3::types::PyModule;
383    ///     use crate::rumtk_core::scripting::python_utils::{RUMPyAny, RUMPyArgs, RUMPyModule, RUMPyList};
384    ///     use crate::rumtk_core::scripting::python_utils::{py_load, py_exec_module, py_new_args};
385    ///     use uuid::Uuid;
386    ///     use crate::rumtk_core::strings::RUMString;
387    ///
388    ///     let expected: &str = "print('Hello World!')\ndef test():\n\treturn 'Hello'";
389    ///     let fpath: RUMString = format_compact!("/tmp/{}.py", Uuid::new_v4());
390    ///     std::fs::write(&fpath, expected.as_bytes()).expect("Failure to write test module.");
391    ///
392    ///     let expect: Vec<&str> = vec![];
393    ///
394    ///     Python::attach( |py| {
395    ///         let py_obj: RUMPyModule = py_load(py, &fpath).expect("Failure to load module!");
396    ///         let args: RUMPyList = py_new_args(py);
397    ///
398    ///         let result = py_exec_module(py, &py_obj, "", &args).expect("Failed to extract result!");
399    ///    });
400    ///
401    ///     std::fs::remove_file(&fpath).unwrap()
402    ///```
403    ///
404    pub fn py_exec_module(
405        py: Python,
406        pymod: &RUMPyModule,
407        func_name: &str,
408        args: &RUMPyList,
409    ) -> RUMResult<RUMPyAny> {
410        if !func_name.is_empty() {
411            let pyfunc: RUMPyFunction = match pymod.getattr(py, func_name) {
412                Ok(f) => f,
413                Err(e) => {
414                    return Err(format_compact!(
415                        "No function named {} found in module! Error: {:#?}",
416                        &func_name,
417                        e
418                    ));
419                }
420            };
421            match pyfunc.call1(py, py_list_to_tuple(py, args)?) {
422                Ok(r) => Ok(r),
423                Err(e) => Err(format_compact!(
424                    "An error occurred executing Python function {}. Error: {}",
425                    &func_name,
426                    e
427                )),
428            }
429        } else {
430            Ok(py_new_args(py).into_any())
431        }
432    }
433
434    ///
435    /// Runs a closure that follows the signature `|py: RUMPython| -> R {}`.
436    /// Remember, the type of the `py` token needs to be explicitly added or there will be a type
437    /// inference error from Rust about lifetimes when in fact the closure has no lifetime issues.
438    /// See example below.
439    ///
440    /// ## Examples
441    ///
442    /// ### Running A Function With Arguments and Result
443    ///
444    /// ```
445    ///     use std::fs::write;
446    ///     use pyo3::Python;
447    ///     use uuid::Uuid;
448    ///     use crate::rumtk_core::core::RUMResult;
449    ///     use crate::rumtk_core::scripting::python_utils::{py_extract_any, py_new_args, py_push_arg, py_exec, py_exec_module, py_load, RUMPython};
450    ///     use crate::rumtk_core::scripting::python_utils::{RUMPyModule};
451    ///
452    ///     fn test_module_exec() -> f64 {
453    ///         let module_fname = format!("{}_module.py", Uuid::new_v4());
454    ///         let module_contents = "def test(a,b):\n\treturn a+b";
455    ///         write(&module_fname, module_contents).expect("Failed to write file!");
456    ///
457    ///         let closure = |py: RUMPython| -> RUMResult<f64> {
458    ///             let a = 5;
459    ///             let b = 5.0;
460    ///
461    ///             let mut args = py_new_args(py);
462    ///             py_push_arg(py, &mut args, &a);
463    ///             py_push_arg(py, &mut args, &b);
464    ///
465    ///             let pymod: RUMPyModule = py_load(py, &module_fname).expect("Failure to load module!");
466    ///
467    ///             let result = py_exec_module(py, &pymod, "test", &args).unwrap();
468    ///             let val: f64 = py_extract_any(py, &result).unwrap();
469    ///
470    ///             Ok(val)
471    ///         };
472    ///
473    ///         let result = py_exec(closure);
474    ///         std::fs::remove_file(&module_fname).unwrap();
475    ///
476    ///         result.unwrap()
477    ///     }
478    ///
479    ///     let result = test_module_exec();
480    ///
481    ///     assert_eq!(10.0, result, "Bad value returned from Python snippet!")
482    ///
483    /// ```
484    ///
485    pub fn py_exec<F, R>(closure: F) -> R
486    where
487        F: FnOnce(RUMPython) -> R,
488    {
489        Python::attach(|py: RUMPython| -> R { closure(py) })
490    }
491}
492
493pub mod python_macros {
494    ///
495    /// Load a Python module and execute contents.
496    ///
497    /// ## Example
498    ///
499    /// ### Running the Module
500    ///
501    /// ```
502    ///     use std::fs::write;
503    ///     use pyo3::Python;
504    ///     use uuid::Uuid;
505    ///     use crate::rumtk_core::core::RUMResult;
506    ///     use crate::rumtk_core::rumtk_python_exec_module;
507    ///
508    ///     fn test_module_exec() {
509    ///         let module_fname = format!("{}_module.py", Uuid::new_v4());
510    ///         let module_contents = "print(\"Hello World!\")";
511    ///         write(&module_fname, module_contents).expect("Failed to write file!");
512    ///
513    ///         let result = Python::attach(|py| -> RUMResult<()> {
514    ///             rumtk_python_exec_module!(py, &module_fname);
515    ///             Ok(())
516    ///         });
517    ///         std::fs::remove_file(&module_fname).unwrap();
518    ///     }
519    ///
520    ///     test_module_exec()
521    ///
522    /// ```
523    ///
524    /// ### Running A Function
525    ///
526    /// ```
527    ///     use std::fs::write;
528    ///     use pyo3::Python;
529    ///     use uuid::Uuid;
530    ///     use crate::rumtk_core::core::RUMResult;
531    ///     use crate::rumtk_core::rumtk_python_exec_module;
532    ///
533    ///     fn test_module_exec() {
534    ///         let module_fname = format!("{}_module.py", Uuid::new_v4());
535    ///         let module_contents = "def test():\n\tprint(\"Hello World!\")";
536    ///         write(&module_fname, module_contents).expect("Failed to write file!");
537    ///
538    ///         let result = Python::attach(|py| -> RUMResult<()> {
539    ///             rumtk_python_exec_module!(py, &module_fname, "test");
540    ///             Ok(())
541    ///         });
542    ///         std::fs::remove_file(&module_fname).unwrap();
543    ///     }
544    ///
545    ///     test_module_exec()
546    ///
547    /// ```
548    ///
549    /// ### Running A Function With Result
550    ///
551    /// ```
552    ///     use std::fs::write;
553    ///     use pyo3::Python;
554    ///     use uuid::Uuid;
555    ///     use crate::rumtk_core::core::RUMResult;
556    ///     use crate::rumtk_core::scripting::python_utils::py_extract_any;
557    ///     use crate::rumtk_core::rumtk_python_exec_module;
558    ///
559    ///     fn test_module_exec() -> usize {
560    ///         let module_fname = format!("{}_module.py", Uuid::new_v4());
561    ///         let module_contents = "def test():\n\treturn 5+5";
562    ///         write(&module_fname, module_contents).expect("Failed to write file!");
563    ///
564    ///         let result = Python::attach(|py| -> RUMResult<usize> {
565    ///             let result = rumtk_python_exec_module!(py, &module_fname, "test");
566    ///             let val: usize = py_extract_any(py, &result)?;
567    ///             Ok(val)
568    ///         });
569    ///         std::fs::remove_file(&module_fname).unwrap();
570    ///
571    ///         result.unwrap()
572    ///     }
573    ///
574    ///     let result = test_module_exec();
575    ///
576    ///     assert_eq!(10, result, "Bad value returned from Python snippet!")
577    ///
578    /// ```
579    ///
580    /// ### Running A Function With Arguments and Result
581    ///
582    /// ```
583    ///     use std::fs::write;
584    ///     use pyo3::Python;
585    ///     use uuid::Uuid;
586    ///     use crate::rumtk_core::core::RUMResult;
587    ///     use crate::rumtk_core::scripting::python_utils::{py_extract_any, py_new_args, py_push_arg};
588    ///     use crate::rumtk_core::rumtk_python_exec_module;
589    ///
590    ///     fn test_module_exec() -> f64 {
591    ///         let a = 5;
592    ///         let b = 5.0;
593    ///
594    ///         let module_fname = format!("{}_module.py", Uuid::new_v4());
595    ///         let module_contents = "def test(a,b):\n\treturn a+b";
596    ///         write(&module_fname, module_contents).expect("Failed to write file!");
597    ///
598    ///         let result = Python::attach(|py| -> RUMResult<f64> {
599    ///             let mut args = py_new_args(py);
600    ///             py_push_arg(py, &mut args, &a);
601    ///             py_push_arg(py, &mut args, &b);
602    ///
603    ///             let result = rumtk_python_exec_module!(py, &module_fname, "test", &args);
604    ///             let val: f64 = py_extract_any(py, &result)?;
605    ///             Ok(val)
606    ///         });
607    ///         std::fs::remove_file(&module_fname).unwrap();
608    ///
609    ///         result.unwrap()
610    ///     }
611    ///
612    ///     let result = test_module_exec();
613    ///
614    ///     assert_eq!(10.0, result, "Bad value returned from Python snippet!")
615    ///
616    /// ```
617    ///
618    #[macro_export]
619    macro_rules! rumtk_python_exec_module {
620        ( $py:expr, $mod_path:expr) => {{
621            use pyo3::types::PyModule;
622            use pyo3::{IntoPyObjectExt, Python};
623            use $crate::scripting::python_utils::{
624                py_buildargs, py_exec_module, py_list_to_tuple, py_load, py_new_args,
625            };
626            use $crate::scripting::python_utils::{RUMPyAny, RUMPyArgs, RUMPyList, RUMPyModule};
627            use $crate::strings::RUMString;
628
629            // Load module
630            let pymod: RUMPyModule = py_load($py, $mod_path)?;
631
632            // Empty args
633            let args = py_new_args($py);
634
635            // Let's execute against arguments
636            py_exec_module($py, &pymod, "", &args)?
637        }};
638        ( $py:expr, $mod_path:expr, $func_name:expr ) => {{
639            use pyo3::types::PyModule;
640            use pyo3::{IntoPyObjectExt, Python};
641            use $crate::scripting::python_utils::{
642                py_buildargs, py_exec_module, py_list_to_tuple, py_load, py_new_args,
643            };
644            use $crate::scripting::python_utils::{RUMPyAny, RUMPyArgs, RUMPyList, RUMPyModule};
645            use $crate::strings::RUMString;
646
647            // Load module
648            let pymod: RUMPyModule = py_load($py, $mod_path)?;
649
650            // Empty args
651            let args = py_new_args($py);
652
653            // Let's execute against arguments
654            py_exec_module($py, &pymod, $func_name, &args)?
655        }};
656        ( $py:expr, $mod_path:expr, $func_name:expr, $args:expr ) => {{
657            use pyo3::types::PyModule;
658            use pyo3::{IntoPyObjectExt, Python};
659            use $crate::scripting::python_utils::{
660                py_buildargs, py_exec_module, py_list_to_tuple, py_load,
661            };
662            use $crate::scripting::python_utils::{RUMPyAny, RUMPyArgs, RUMPyList, RUMPyModule};
663            use $crate::strings::RUMString;
664
665            // Load module
666            let pymod: RUMPyModule = py_load($py, $mod_path)?;
667
668            // Let's execute against arguments
669            py_exec_module($py, &pymod, $func_name, $args)?
670        }};
671    }
672
673    ///
674    /// Execute the contents of a closure passed to this macro. This macro is an alias for
675    /// [crate::rumtk_core::scripting::python_utils::py_exec].
676    ///
677    /// See the blurp about that function to learn more!
678    ///
679    /// ## Closure Format
680    ///
681    /// ### Without Return
682    ///
683    /// ```text
684    ///     |py: RUMPython| {
685    ///         rumtk_python_exec_module!(py, "module/path");
686    ///     };
687    /// ```
688    ///
689    /// ### With Return
690    ///
691    /// ```text
692    ///     |py: RUMPython| -> usize {
693    ///         let result = rumtk_python_exec_module!(py, "module/path", "my_python_function");
694    ///         let val: usize = py_extract_any(py, &result)?;
695    ///         val
696    ///     };
697    /// ```
698    ///
699    /// ## Example
700    ///
701    /// ### Running the Module
702    ///
703    /// ```
704    ///     use std::fs::write;
705    ///     use pyo3::Python;
706    ///     use uuid::Uuid;
707    ///     use crate::rumtk_core::scripting::python_utils::RUMPython;
708    ///     use crate::rumtk_core::core::RUMResult;
709    ///     use crate::rumtk_core::{rumtk_python_exec_module, rumtk_python_exec};
710    ///
711    ///     fn test_module_exec() {
712    ///         let module_fname = format!("{}_module.py", Uuid::new_v4());
713    ///         let module_contents = "print(\"Hello World!\")";
714    ///         write(&module_fname, module_contents).expect("Failed to write file!");
715    ///
716    ///         let closure = |py: RUMPython| -> RUMResult<()> {
717    ///             rumtk_python_exec_module!(py, &module_fname);
718    ///             Ok(())
719    ///         };
720    ///
721    ///         let result = rumtk_python_exec!(closure);
722    ///         std::fs::remove_file(&module_fname).unwrap();
723    ///     }
724    ///
725    ///     test_module_exec()
726    ///
727    /// ```
728    ///
729    /// ### Running A Function
730    ///
731    /// ```
732    ///     use std::fs::write;
733    ///     use pyo3::Python;
734    ///     use uuid::Uuid;
735    ///     use crate::rumtk_core::core::RUMResult;
736    ///     use crate::rumtk_core::scripting::python_utils::RUMPython;
737    ///     use crate::rumtk_core::{rumtk_python_exec_module, rumtk_python_exec};
738    ///
739    ///     fn test_module_exec() {
740    ///         let module_fname = format!("{}_module.py", Uuid::new_v4());
741    ///         let module_contents = "def test():\n\tprint(\"Hello World!\")";
742    ///         write(&module_fname, module_contents).expect("Failed to write file!");
743    ///
744    ///         let closure = |py: RUMPython| -> RUMResult<()> {
745    ///             rumtk_python_exec_module!(py, &module_fname, "test");
746    ///             Ok(())
747    ///         };
748    ///
749    ///         let result = rumtk_python_exec!(closure);
750    ///         std::fs::remove_file(&module_fname).unwrap();
751    ///
752    ///     }
753    ///
754    ///     test_module_exec()
755    ///
756    /// ```
757    ///
758    /// ### Running A Function With Result
759    ///
760    /// ```
761    ///     use std::fs::write;
762    ///     use pyo3::Python;
763    ///     use uuid::Uuid;
764    ///     use crate::rumtk_core::core::RUMResult;
765    ///     use crate::rumtk_core::scripting::python_utils::{py_extract_any, RUMPython};
766    ///     use crate::rumtk_core::{rumtk_python_exec_module, rumtk_python_exec};
767    ///
768    ///     fn test_module_exec() -> usize {
769    ///         let module_fname = format!("{}_module.py", Uuid::new_v4());
770    ///         let module_contents = "def test():\n\treturn 5+5";
771    ///         write(&module_fname, module_contents).expect("Failed to write file!");
772    ///
773    ///         let closure = |py: RUMPython| -> RUMResult<usize> {
774    ///             let result = rumtk_python_exec_module!(py, &module_fname, "test");
775    ///             let val: usize = py_extract_any(py, &result)?;
776    ///             Ok(val)
777    ///         };
778    ///
779    ///         let result = rumtk_python_exec!(closure);
780    ///         std::fs::remove_file(&module_fname).unwrap();
781    ///
782    ///         result.unwrap()
783    ///     }
784    ///
785    ///     let result = test_module_exec();
786    ///
787    ///     assert_eq!(10, result, "Bad value returned from Python snippet!")
788    ///
789    /// ```
790    ///
791    /// ### Running A Function With Arguments and Result
792    ///
793    /// ```
794    ///     use std::fs::write;
795    ///     use pyo3::Python;
796    ///     use uuid::Uuid;
797    ///     use crate::rumtk_core::core::RUMResult;
798    ///     use crate::rumtk_core::scripting::python_utils::{py_extract_any, py_new_args, py_push_arg, RUMPython};
799    ///     use crate::rumtk_core::{rumtk_python_exec, rumtk_python_exec_module};
800    ///
801    ///     fn test_module_exec() -> f64 {
802    ///         let module_fname = format!("{}_module.py", Uuid::new_v4());
803    ///         let module_contents = "def test(a,b):\n\treturn a+b";
804    ///         write(&module_fname, module_contents).expect("Failed to write file!");
805    ///
806    ///         let closure = |py: RUMPython| -> RUMResult<f64> {
807    ///             let a = 5;
808    ///             let b = 5.0;
809    ///
810    ///             let mut args = py_new_args(py);
811    ///             py_push_arg(py, &mut args, &a);
812    ///             py_push_arg(py, &mut args, &b);
813    ///
814    ///             let result = rumtk_python_exec_module!(py, &module_fname, "test", &args);
815    ///             let val: f64 = py_extract_any(py, &result)?;
816    ///             Ok(val)
817    ///         };
818    ///
819    ///         let result = rumtk_python_exec!(closure);
820    ///         std::fs::remove_file(&module_fname).unwrap();
821    ///
822    ///         result.unwrap()
823    ///     }
824    ///
825    ///     let result = test_module_exec();
826    ///
827    ///     assert_eq!(10.0, result, "Bad value returned from Python snippet!")
828    ///
829    /// ```
830    ///
831    #[macro_export]
832    macro_rules! rumtk_python_exec {
833        ( $closure:expr ) => {{
834            use $crate::scripting::python_utils::py_exec;
835
836            py_exec($closure)
837        }};
838    }
839}