Function pyo3_asyncio::tokio::local_future_into_py_with_locals
source · pub fn local_future_into_py_with_locals<F, T>(
py: Python<'_>,
locals: TaskLocals,
fut: F
) -> PyResult<&PyAny>where
F: Future<Output = PyResult<T>> + 'static,
T: IntoPy<PyObject>,
Expand description
Convert a !Send
Rust Future into a Python awaitable
If the asyncio.Future
returned by this conversion is cancelled via asyncio.Future.cancel
,
the Rust future will be cancelled as well (new behaviour in v0.15
).
Python contextvars
are preserved when calling async Python functions within the Rust future
via into_future
(new behaviour in v0.15
).
Although
contextvars
are preserved for async Python functions, synchronous functions will unfortunately fail to resolve them when called within the Rust future. This is because the function is being called from a Rust thread, not inside an actual Python coroutine context.As a workaround, you can get the
contextvars
from the current task locals usingget_current_locals
andTaskLocals::context
, then wrap your synchronous function in a call tocontextvars.Context.run
. This will set the context, call the synchronous function, and restore the previous context when it returns or raises an exception.
Arguments
py
- PyO3 GIL guardlocals
- The task locals for the given futurefut
- The Rust future to be converted
Examples
use std::{rc::Rc, time::Duration};
use pyo3::prelude::*;
/// Awaitable non-send sleep function
#[pyfunction]
fn sleep_for(py: Python, secs: u64) -> PyResult<&PyAny> {
// Rc is non-send so it cannot be passed into pyo3_asyncio::tokio::future_into_py
let secs = Rc::new(secs);
pyo3_asyncio::tokio::local_future_into_py_with_locals(
py,
pyo3_asyncio::tokio::get_current_locals(py)?,
async move {
tokio::time::sleep(Duration::from_secs(*secs)).await;
Python::with_gil(|py| Ok(py.None()))
}
)
}
#[pyo3_asyncio::tokio::main]
async fn main() -> PyResult<()> {
let locals = Python::with_gil(|py| -> PyResult<_> {
pyo3_asyncio::tokio::get_current_locals(py)
})?;
// the main coroutine is running in a Send context, so we cannot use LocalSet here. Instead
// we use spawn_blocking in order to use LocalSet::block_on
tokio::task::spawn_blocking(move || {
// LocalSet allows us to work with !Send futures within tokio. Without it, any calls to
// pyo3_asyncio::tokio::local_future_into_py will panic.
tokio::task::LocalSet::new().block_on(
pyo3_asyncio::tokio::get_runtime(),
pyo3_asyncio::tokio::scope_local(locals, async {
Python::with_gil(|py| {
let py_future = sleep_for(py, 1)?;
pyo3_asyncio::tokio::into_future(py_future)
})?
.await?;
Ok(())
})
)
}).await.unwrap()
}