How to call Python's async callback function in Rust?

use pyo3::prelude::{PyAnyMethods as _, PyModule, PyModuleMethods as _};
use pyo3::{pyfunction, pymodule, wrap_pyfunction, Bound, PyAny, PyObject, PyResult, Python};

#[pyfunction]
fn async_callback<'py>(
    py: Python<'py>,
    callback: PyObject
) -> PyResult<Bound<'py, PyAny>> {
    pyo3_async_runtimes::tokio::future_into_py(py, async move {
        match Python::with_gil(|py| {
            let asyncio = py.import("asyncio")?;
            let coroutine = callback.call0(py)?;
            pyo3_async_runtimes::tokio::into_future(
                asyncio.call_method1("ensure_future", (coroutine, ))?
            )
        }) {
            Ok(bound) => {
                if let Err(e) = bound.await {
                    Python::with_gil(|py| {
                        e.display(py);
                    })
                }
            },
            Err(e) => {
                Python::with_gil(|py| {
                    e.display(py);
                })
            },
        }

        Ok(())
    })
}

#[pymodule]
fn async_py(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(async_callback, m)?)?;

    Ok(())
}
import asyncio;
from async_py import async_callback

async def callback():
    print("Hello from asynco-py!")

async def main():
    await async_callback(callback)


if __name__ == "__main__":
    asyncio.run(main())

Shows:

Traceback (most recent call last):
  File "/usr/lib/python3.13/asyncio/tasks.py", line 746, in ensure_future
    loop = events.get_event_loop()
  File "/usr/lib/python3.13/asyncio/events.py", line 716, in get_event_loop
    raise RuntimeError('There is no current event loop in thread %r.'
                       % threading.current_thread().name)
RuntimeError: There is no current event loop in thread 'Dummy-1'.
<sys>:0: RuntimeWarning: coroutine 'callback' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
async fn rust_async_task(message: &str) {
    sleep(Duration::from_millis(100)).await;
    println!("Processed: {}", message)
}

#[pyfunction]
fn async_callback<'py>(py: Python<'py>, callback: PyObject) -> PyResult<Bound<'py, PyAny>> {
    pyo3_async_runtimes::tokio::future_into_py(py, async move {
        let _ = rust_async_task("Before Python callback").await;
        let _ = rust_async_task("test").await;

        println!("Rust async operations completed, now calling Python callback");

        let fut = Python::with_gil(|py| {
            // let asyncio = py.import("asyncio")?;
            let coroutine = callback.call0(py)?;

            pyo3_async_runtimes::tokio::into_future(
                coroutine.extract(py)?
            )
        })?;

        fut.await
    })
}

#[pymodule]
fn async_py(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(async_callback, m)?)?;

    Ok(())
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.