rigetti_pyo3/sync.rs
1//! Macros that allow asynchronous Rust functions to be exported as synchronous Python functions.
2
3/// Spawn and block on a future using the pyo3 tokio runtime.
4/// Useful for returning a synchronous `PyResult`.
5///
6///
7/// When used like the following:
8/// ```rs
9/// async fn say_hello(name: String) -> String {
10/// format!("hello {name}")
11/// }
12///
13/// #[pyo3(name="say_hello")]
14/// pub fn py_say_hello(name: String) -> PyResult<String> {
15/// py_sync!(say_hello(name))
16/// }
17/// ```
18///
19/// Becomes the associated "synchronous" python call:
20/// ```py
21/// assert say_hello("Rigetti") == "hello Rigetti"
22/// ```
23#[macro_export]
24macro_rules! py_sync {
25 ($py: ident, $body: expr) => {{
26 $py.allow_threads(|| {
27 let runtime = ::pyo3_asyncio::tokio::get_runtime();
28 let handle = runtime.spawn($body);
29
30 runtime.block_on(async {
31 tokio::select! {
32 result = handle => result.map_err(|err| ::pyo3::exceptions::PyRuntimeError::new_err(err.to_string()))?,
33 signal_err = async {
34 // A 100ms loop delay is a bit arbitrary, but seems to
35 // balance CPU usage and SIGINT responsiveness well enough.
36 let delay = ::std::time::Duration::from_millis(100);
37 loop {
38 ::pyo3::Python::with_gil(|py| {
39 py.check_signals()
40 })?;
41 ::tokio::time::sleep(delay).await;
42 }
43 } => signal_err,
44 }
45 })
46 })
47 }};
48}
49
50/// Convert a rust future into a Python awaitable using
51/// `pyo3_asyncio::tokio::future_into_py`
52#[macro_export]
53macro_rules! py_async {
54 ($py: ident, $body: expr) => {
55 ::pyo3_asyncio::tokio::future_into_py($py, $body)
56 };
57}
58
59/// Given a single implementation of an async function,
60/// create that function as private and two pyfunctions
61/// named after it that can be used to invoke either
62/// blocking or async variants of the same function.
63///
64/// In order to export the function to Python using pyo3
65/// you must include the `#[pyfunction]` attribute. This
66/// isn't included in the macro by default since one may
67/// wish to annotate `#[pyfunction]` with additional
68/// arguments.
69///
70/// The given function will be spawned on a Rust event loop
71/// this means functions like [`pyo3::Python::with_gil`](pyo3::Python::with_gil)
72/// should not be used, as acquiring Python's global
73/// interpreter lock from a Rust runtime
74/// isn't possible.
75///
76/// This macro cannot be used when lifetime specifiers are
77/// required, or the pyfunction bodies need additional
78/// parameter handling besides simply calling out to
79/// the underlying `py_async` or `py_sync` macros.
80///
81/// ```rs
82/// // ... becomes python package "things"
83/// create_init_submodule! {
84/// funcs: [
85/// py_do_thing,
86/// py_do_thing_async,
87/// ]
88/// }
89///
90/// py_function_sync_async! {
91/// #[pyfunction]
92/// #[args(timeout = "None")]
93/// async fn do_thing(timeout: Option<u64>) -> PyResult<String> {
94/// // ... sleep for timeout ...
95/// Ok(String::from("done"))
96/// }
97/// }
98/// ```
99///
100/// becomes in python:
101/// ```py
102/// from things import do_thing, do_thing_async
103/// assert do_thing() == "done"
104/// assert await do_thing_async() == "done"
105/// ```
106#[macro_export]
107macro_rules! py_function_sync_async {
108 (
109 $(#[$meta: meta])+
110 async fn $name: ident($($(#[$arg_meta: meta])*$arg: ident : $kind: ty),* $(,)?) $(-> $ret: ty)? $body: block
111 ) => {
112 async fn $name($($arg: $kind,)*) $(-> $ret)? {
113 $body
114 }
115
116 ::paste::paste! {
117 $(#[$meta])+
118 #[allow(clippy::too_many_arguments, missing_docs)]
119 #[pyo3(name = $name "")]
120 pub(crate) fn [< py_ $name >](py: ::pyo3::Python<'_> $(, $(#[$arg_meta])*$arg: $kind)*) $(-> $ret)? {
121 $crate::py_sync!(py, $name($($arg),*))
122 }
123
124 $(#[$meta])+
125 #[pyo3(name = $name "_async")]
126 #[allow(clippy::too_many_arguments, missing_docs)]
127 pub(crate) fn [< py_ $name _async >](py: ::pyo3::Python<'_> $(, $(#[$arg_meta])*$arg: $kind)*) -> ::pyo3::PyResult<&::pyo3::PyAny> {
128 $crate::py_async!(py, $name($($arg),*))
129 }
130 }
131 };
132}