use proc_macro2::TokenStream;
use quote::quote;
use crate::codegen::{PythonApi, format_tokens_static};
pub(crate) fn generate_runtime(api: PythonApi) -> String {
let runtime_fn = if api.has_async() {
quote! {
pub fn runtime() -> &'static ::tokio::runtime::Runtime {
::pyo3_async_runtimes::tokio::get_runtime()
}
}
} else {
quote! {
pub fn runtime() -> &'static ::tokio::runtime::Runtime {
static RUNTIME: ::std::sync::OnceLock<::tokio::runtime::Runtime> =
::std::sync::OnceLock::new();
RUNTIME.get_or_init(|| {
::tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name(concat!(env!("CARGO_PKG_NAME"), "-tokio"))
.build()
.expect("failed to build the Tokio runtime")
})
}
}
};
let async_pager = api.has_async().then(async_pager);
let tokens: TokenStream = quote! {
use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
use futures::stream::{BoxStream, Stream, StreamExt};
use pyo3::prelude::*;
const SIGNAL_CHECK_INTERVAL: Duration = Duration::from_millis(100);
#runtime_fn
pub fn block_on<F>(py: Python<'_>, fut: F) -> PyResult<F::Output>
where
F: Future + Send,
F::Output: Send,
{
let runtime = runtime();
let mut fut = ::std::pin::pin!(fut);
loop {
let polled = py.detach(|| {
runtime.block_on(async {
::tokio::time::timeout(SIGNAL_CHECK_INTERVAL, &mut fut).await.ok()
})
});
if let Some(output) = polled {
return Ok(output);
}
py.check_signals()?;
}
}
type ItemStream = BoxStream<'static, PyResult<Py<PyAny>>>;
#[pyclass(name = "ListIterator")]
pub struct ListIterator {
stream: Arc<::tokio::sync::Mutex<ItemStream>>,
}
impl ListIterator {
pub fn new(stream: impl Stream<Item = PyResult<Py<PyAny>>> + Send + 'static) -> Self {
Self { stream: Arc::new(::tokio::sync::Mutex::new(stream.fuse().boxed())) }
}
}
#[pymethods]
impl ListIterator {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __next__(&self, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
let stream = Arc::clone(&self.stream);
block_on(py, async move { stream.lock().await.next().await })?.transpose()
}
}
#async_pager
};
format_tokens_static(tokens)
}
fn async_pager() -> TokenStream {
quote! {
#[pyclass(name = "AsyncListIterator")]
pub struct AsyncListIterator {
stream: Arc<::tokio::sync::Mutex<ItemStream>>,
}
impl AsyncListIterator {
pub fn new(stream: impl Stream<Item = PyResult<Py<PyAny>>> + Send + 'static) -> Self {
Self { stream: Arc::new(::tokio::sync::Mutex::new(stream.fuse().boxed())) }
}
}
#[pymethods]
impl AsyncListIterator {
fn __aiter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __anext__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let stream = Arc::clone(&self.stream);
::pyo3_async_runtimes::tokio::future_into_py(py, async move {
match stream.lock().await.next().await {
Some(item) => item,
None => Err(::pyo3::exceptions::PyStopAsyncIteration::new_err(())),
}
})
}
}
}
}