Skip to main content

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