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}