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}