Skip to main content

pyo3_async_runtimes/
generic.rs

1//! Generic implementations of PyO3 Asyncio utilities that can be used for any Rust runtime
2//!
3//! Items marked with
4//! <span
5//!   class="module-item stab portability"
6//!   style="display: inline; border-radius: 3px; padding: 2px; font-size: 80%; line-height: 1.2;"
7//! ><code>unstable-streams</code></span>
8//! > are only available when the `unstable-streams` Cargo feature is enabled:
9//!
10//! ```toml
11//! [dependencies.pyo3-async-runtimes]
12//! version = "0.24"
13//! features = ["unstable-streams"]
14//! ```
15
16use std::{
17    future::Future,
18    pin::Pin,
19    sync::{Arc, Mutex},
20    task::{Context, Poll},
21};
22
23use crate::{
24    asyncio, call_soon_threadsafe, close, create_future, dump_err, err::RustPanic,
25    get_running_loop, into_future_with_locals, TaskLocals,
26};
27#[cfg(feature = "unstable-streams")]
28use futures_channel::mpsc;
29use futures_channel::oneshot;
30#[cfg(feature = "unstable-streams")]
31use futures_util::SinkExt;
32use pin_project_lite::pin_project;
33use pyo3::prelude::*;
34use pyo3::IntoPyObjectExt;
35#[cfg(feature = "unstable-streams")]
36use std::marker::PhantomData;
37
38/// Generic utilities for a JoinError
39pub trait JoinError {
40    /// Check if the spawned task exited because of a panic
41    fn is_panic(&self) -> bool;
42    /// Get the panic object associated with the error.  Panics if `is_panic` is not true.
43    fn into_panic(self) -> Box<dyn std::any::Any + Send + 'static>;
44}
45
46/// Generic Rust async/await runtime
47pub trait Runtime: Send + 'static {
48    /// The error returned by a JoinHandle after being awaited
49    type JoinError: JoinError + Send;
50    /// A future that completes with the result of the spawned task
51    type JoinHandle: Future<Output = Result<(), Self::JoinError>> + Send;
52
53    /// Spawn a future onto this runtime's event loop
54    fn spawn<F>(fut: F) -> Self::JoinHandle
55    where
56        F: Future<Output = ()> + Send + 'static;
57
58    /// Spawn a function onto this runtime's blocking event loop
59    fn spawn_blocking<F>(f: F) -> Self::JoinHandle
60    where
61        F: FnOnce() + Send + 'static;
62}
63
64/// Extension trait for async/await runtimes that support spawning local tasks
65pub trait SpawnLocalExt: Runtime {
66    /// Spawn a !Send future onto this runtime's event loop
67    fn spawn_local<F>(fut: F) -> Self::JoinHandle
68    where
69        F: Future<Output = ()> + 'static;
70}
71
72/// Exposes the utilities necessary for using task-local data in the Runtime
73pub trait ContextExt: Runtime {
74    /// Set the task locals for the given future
75    fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
76    where
77        F: Future<Output = R> + Send + 'static;
78
79    /// Get the task locals for the current task
80    fn get_task_locals() -> Option<TaskLocals>;
81}
82
83/// Adds the ability to scope task-local data for !Send futures
84pub trait LocalContextExt: Runtime {
85    /// Set the task locals for the given !Send future
86    fn scope_local<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R>>>
87    where
88        F: Future<Output = R> + 'static;
89}
90
91/// Get the current event loop from either Python or Rust async task local context
92///
93/// This function first checks if the runtime has a task-local reference to the Python event loop.
94/// If not, it calls [`get_running_loop`](crate::get_running_loop`) to get the event loop associated
95/// with the current OS thread.
96pub fn get_current_loop<R>(py: Python) -> PyResult<Bound<PyAny>>
97where
98    R: ContextExt,
99{
100    if let Some(locals) = R::get_task_locals() {
101        Ok(locals.0.event_loop.clone_ref(py).into_bound(py))
102    } else {
103        get_running_loop(py)
104    }
105}
106
107/// Either copy the task locals from the current task OR get the current running loop and
108/// contextvars from Python.
109pub fn get_current_locals<R>(py: Python) -> PyResult<TaskLocals>
110where
111    R: ContextExt,
112{
113    if let Some(locals) = R::get_task_locals() {
114        Ok(locals)
115    } else {
116        Ok(TaskLocals::with_running_loop(py)?.copy_context(py)?)
117    }
118}
119
120/// Run the event loop until the given Future completes
121///
122/// After this function returns, the event loop can be resumed with [`run_until_complete`]
123///
124/// # Arguments
125/// * `event_loop` - The Python event loop that should run the future
126/// * `fut` - The future to drive to completion
127///
128/// # Examples
129///
130/// ```no_run
131/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future};
132/// #
133/// # use pyo3_async_runtimes::{
134/// #     TaskLocals,
135/// #     generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime}
136/// # };
137/// #
138/// # struct MyCustomJoinError;
139/// #
140/// # impl JoinError for MyCustomJoinError {
141/// #     fn is_panic(&self) -> bool {
142/// #         unreachable!()
143/// #     }
144/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
145/// #         unreachable!()
146/// #     }
147/// # }
148/// #
149/// # struct MyCustomJoinHandle;
150/// #
151/// # impl Future for MyCustomJoinHandle {
152/// #     type Output = Result<(), MyCustomJoinError>;
153/// #
154/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
155/// #         unreachable!()
156/// #     }
157/// # }
158/// #
159/// # struct MyCustomRuntime;
160/// #
161/// # impl Runtime for MyCustomRuntime {
162/// #     type JoinError = MyCustomJoinError;
163/// #     type JoinHandle = MyCustomJoinHandle;
164/// #
165/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
166/// #     where
167/// #         F: Future<Output = ()> + Send + 'static
168/// #     {
169/// #         unreachable!()
170/// #     }
171/// #
172/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
173/// #         unreachable!()
174/// #     }
175/// # }
176/// #
177/// # impl ContextExt for MyCustomRuntime {
178/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
179/// #     where
180/// #         F: Future<Output = R> + Send + 'static
181/// #     {
182/// #         unreachable!()
183/// #     }
184/// #     fn get_task_locals() -> Option<TaskLocals> {
185/// #         unreachable!()
186/// #     }
187/// # }
188/// #
189/// # use std::time::Duration;
190/// #
191/// # use pyo3::prelude::*;
192/// #
193/// # Python::attach(|py| -> PyResult<()> {
194/// # let event_loop = py.import("asyncio")?.call_method0("new_event_loop")?;
195/// # #[cfg(feature = "tokio-runtime")]
196/// pyo3_async_runtimes::generic::run_until_complete::<MyCustomRuntime, _, _>(&event_loop, async move {
197///     tokio::time::sleep(Duration::from_secs(1)).await;
198///     Ok(())
199/// })?;
200/// # Ok(())
201/// # }).unwrap();
202/// ```
203pub fn run_until_complete<R, F, T>(event_loop: &Bound<PyAny>, fut: F) -> PyResult<T>
204where
205    R: Runtime + ContextExt,
206    F: Future<Output = PyResult<T>> + Send + 'static,
207    T: Send + Sync + 'static,
208{
209    let py = event_loop.py();
210    let result_tx = Arc::new(Mutex::new(None));
211    let result_rx = Arc::clone(&result_tx);
212    let coro = future_into_py_with_locals::<R, _, ()>(
213        py,
214        TaskLocals::new(event_loop.clone()).copy_context(py)?,
215        async move {
216            let val = fut.await?;
217            if let Ok(mut result) = result_tx.lock() {
218                *result = Some(val);
219            }
220            Ok(())
221        },
222    )?;
223
224    event_loop.call_method1(pyo3::intern!(py, "run_until_complete"), (coro,))?;
225
226    let result = result_rx.lock().unwrap().take().unwrap();
227    Ok(result)
228}
229
230/// Run the event loop until the given Future completes
231///
232/// # Arguments
233/// * `py` - The current PyO3 GIL guard
234/// * `fut` - The future to drive to completion
235///
236/// # Examples
237///
238/// ```no_run
239/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future};
240/// #
241/// # use pyo3_async_runtimes::{
242/// #     TaskLocals,
243/// #     generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime}
244/// # };
245/// #
246/// # struct MyCustomJoinError;
247/// #
248/// # impl JoinError for MyCustomJoinError {
249/// #     fn is_panic(&self) -> bool {
250/// #         unreachable!()
251/// #     }
252/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
253/// #         unreachable!()
254/// #     }
255/// # }
256/// #
257/// # struct MyCustomJoinHandle;
258/// #
259/// # impl Future for MyCustomJoinHandle {
260/// #     type Output = Result<(), MyCustomJoinError>;
261/// #
262/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
263/// #         unreachable!()
264/// #     }
265/// # }
266/// #
267/// # struct MyCustomRuntime;
268/// #
269/// # impl Runtime for MyCustomRuntime {
270/// #     type JoinError = MyCustomJoinError;
271/// #     type JoinHandle = MyCustomJoinHandle;
272/// #
273/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
274/// #     where
275/// #         F: Future<Output = ()> + Send + 'static
276/// #     {
277/// #         unreachable!()
278/// #     }
279/// #
280/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
281/// #         unreachable!()
282/// #     }
283/// # }
284/// #
285/// # impl ContextExt for MyCustomRuntime {
286/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
287/// #     where
288/// #         F: Future<Output = R> + Send + 'static
289/// #     {
290/// #         unreachable!()
291/// #     }
292/// #     fn get_task_locals() -> Option<TaskLocals> {
293/// #         unreachable!()
294/// #     }
295/// # }
296/// #
297/// # use std::time::Duration;
298/// # async fn custom_sleep(_duration: Duration) { }
299/// #
300/// # use pyo3::prelude::*;
301/// #
302/// fn main() {
303///     Python::attach(|py| {
304///         pyo3_async_runtimes::generic::run::<MyCustomRuntime, _, _>(py, async move {
305///             custom_sleep(Duration::from_secs(1)).await;
306///             Ok(())
307///         })
308///         .map_err(|e| {
309///             e.print_and_set_sys_last_vars(py);
310///         })
311///         .unwrap();
312///     })
313/// }
314/// ```
315pub fn run<R, F, T>(py: Python, fut: F) -> PyResult<T>
316where
317    R: Runtime + ContextExt,
318    F: Future<Output = PyResult<T>> + Send + 'static,
319    T: Send + Sync + 'static,
320{
321    let event_loop = asyncio(py)?.call_method0(pyo3::intern!(py, "new_event_loop"))?;
322
323    let result = run_until_complete::<R, F, T>(&event_loop, fut);
324
325    close(event_loop)?;
326
327    result
328}
329
330fn cancelled(future: &Bound<PyAny>) -> PyResult<bool> {
331    future
332        .getattr(pyo3::intern!(future.py(), "cancelled"))?
333        .call0()?
334        .is_truthy()
335}
336
337#[pyclass]
338struct CheckedCompletor;
339
340#[pymethods]
341impl CheckedCompletor {
342    fn __call__(
343        &self,
344        future: &Bound<PyAny>,
345        complete: &Bound<PyAny>,
346        value: &Bound<PyAny>,
347    ) -> PyResult<()> {
348        if cancelled(future)? {
349            return Ok(());
350        }
351
352        complete.call1((value,))?;
353
354        Ok(())
355    }
356}
357
358fn set_result(
359    event_loop: &Bound<PyAny>,
360    future: &Bound<PyAny>,
361    result: PyResult<Py<PyAny>>,
362) -> PyResult<()> {
363    let py = event_loop.py();
364    let none = py.None().into_bound(py);
365
366    let (complete, val) = match result {
367        Ok(val) => (
368            future.getattr(pyo3::intern!(py, "set_result"))?,
369            val.into_pyobject(py)?,
370        ),
371        Err(err) => (
372            future.getattr(pyo3::intern!(py, "set_exception"))?,
373            err.into_bound_py_any(py)?,
374        ),
375    };
376    call_soon_threadsafe(event_loop, &none, (CheckedCompletor, future, complete, val))?;
377
378    Ok(())
379}
380
381/// Convert a Python `awaitable` into a Rust Future
382///
383/// This function simply forwards the future and the task locals returned by [`get_current_locals`]
384/// to [`into_future_with_locals`](`crate::into_future_with_locals`). See
385/// [`into_future_with_locals`](`crate::into_future_with_locals`) for more details.
386///
387/// # Arguments
388/// * `awaitable` - The Python `awaitable` to be converted
389///
390/// # Examples
391///
392/// ```no_run
393/// # use std::{any::Any, pin::Pin, future::Future, task::{Context, Poll}, time::Duration};
394/// # use std::ffi::CString;
395/// #
396/// # use pyo3::prelude::*;
397/// #
398/// # use pyo3_async_runtimes::{
399/// #     TaskLocals,
400/// #     generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime}
401/// # };
402/// #
403/// # struct MyCustomJoinError;
404/// #
405/// # impl JoinError for MyCustomJoinError {
406/// #     fn is_panic(&self) -> bool {
407/// #         unreachable!()
408/// #     }
409/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
410/// #         unreachable!()
411/// #     }
412/// # }
413/// #
414/// # struct MyCustomJoinHandle;
415/// #
416/// # impl Future for MyCustomJoinHandle {
417/// #     type Output = Result<(), MyCustomJoinError>;
418/// #
419/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
420/// #         unreachable!()
421/// #     }
422/// # }
423/// #
424/// # struct MyCustomRuntime;
425/// #
426/// # impl MyCustomRuntime {
427/// #     async fn sleep(_: Duration) {
428/// #         unreachable!()
429/// #     }
430/// # }
431/// #
432/// # impl Runtime for MyCustomRuntime {
433/// #     type JoinError = MyCustomJoinError;
434/// #     type JoinHandle = MyCustomJoinHandle;
435/// #
436/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
437/// #     where
438/// #         F: Future<Output = ()> + Send + 'static
439/// #     {
440/// #         unreachable!()
441/// #     }
442/// #
443/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
444/// #         unreachable!()
445/// #     }
446/// # }
447/// #
448/// # impl ContextExt for MyCustomRuntime {
449/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
450/// #     where
451/// #         F: Future<Output = R> + Send + 'static
452/// #     {
453/// #         unreachable!()
454/// #     }
455/// #     fn get_task_locals() -> Option<TaskLocals> {
456/// #         unreachable!()
457/// #     }
458/// # }
459/// #
460/// const PYTHON_CODE: &'static str = r#"
461/// import asyncio
462///
463/// async def py_sleep(duration):
464///     await asyncio.sleep(duration)
465/// "#;
466///
467/// async fn py_sleep(seconds: f32) -> PyResult<()> {
468///     let test_mod = Python::attach(|py| -> PyResult<Py<PyAny>> {
469///         Ok(
470///             PyModule::from_code(
471///                 py,
472///                 &CString::new(PYTHON_CODE).unwrap(),
473///                 &CString::new("test_into_future/test_mod.py").unwrap(),
474///                 &CString::new("test_mod").unwrap(),
475///             )?
476///             .into()
477///         )
478///     })?;
479///
480///     Python::attach(|py| {
481///         pyo3_async_runtimes::generic::into_future::<MyCustomRuntime>(
482///             test_mod
483///                 .call_method1(py, "py_sleep", (seconds,))?
484///                 .into_bound(py),
485///         )
486///     })?
487///     .await?;
488///     Ok(())
489/// }
490/// ```
491pub fn into_future<R>(
492    awaitable: Bound<PyAny>,
493) -> PyResult<impl Future<Output = PyResult<Py<PyAny>>> + Send>
494where
495    R: Runtime + ContextExt,
496{
497    into_future_with_locals(&get_current_locals::<R>(awaitable.py())?, awaitable)
498}
499
500/// Convert a Rust Future into a Python awaitable with a generic runtime
501///
502/// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`,
503/// the Rust future will be cancelled as well (new behaviour in `v0.15`).
504///
505/// Python `contextvars` are preserved when calling async Python functions within the Rust future
506/// via [`into_future`] (new behaviour in `v0.15`).
507///
508/// > Although `contextvars` are preserved for async Python functions, synchronous functions will
509/// > unfortunately fail to resolve them when called within the Rust future. This is because the
510/// > function is being called from a Rust thread, not inside an actual Python coroutine context.
511/// >
512/// > As a workaround, you can get the `contextvars` from the current task locals using
513/// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your
514/// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the
515/// > synchronous function, and restore the previous context when it returns or raises an exception.
516///
517/// # Arguments
518/// * `py` - PyO3 GIL guard
519/// * `locals` - The task-local data for Python
520/// * `fut` - The Rust future to be converted
521///
522/// # Examples
523///
524/// ```no_run
525/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future};
526/// #
527/// # use pyo3_async_runtimes::{
528/// #     TaskLocals,
529/// #     generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime}
530/// # };
531/// #
532/// # struct MyCustomJoinError;
533/// #
534/// # impl JoinError for MyCustomJoinError {
535/// #     fn is_panic(&self) -> bool {
536/// #         unreachable!()
537/// #     }
538/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
539/// #         unreachable!()
540/// #     }
541/// # }
542/// #
543/// # struct MyCustomJoinHandle;
544/// #
545/// # impl Future for MyCustomJoinHandle {
546/// #     type Output = Result<(), MyCustomJoinError>;
547/// #
548/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
549/// #         unreachable!()
550/// #     }
551/// # }
552/// #
553/// # struct MyCustomRuntime;
554/// #
555/// # impl MyCustomRuntime {
556/// #     async fn sleep(_: Duration) {
557/// #         unreachable!()
558/// #     }
559/// # }
560/// #
561/// # impl Runtime for MyCustomRuntime {
562/// #     type JoinError = MyCustomJoinError;
563/// #     type JoinHandle = MyCustomJoinHandle;
564/// #
565/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
566/// #     where
567/// #         F: Future<Output = ()> + Send + 'static
568/// #     {
569/// #         unreachable!()
570/// #     }
571/// #
572/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
573/// #         unreachable!()
574/// #     }
575/// # }
576/// #
577/// # impl ContextExt for MyCustomRuntime {
578/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
579/// #     where
580/// #         F: Future<Output = R> + Send + 'static
581/// #     {
582/// #         unreachable!()
583/// #     }
584/// #     fn get_task_locals() -> Option<TaskLocals> {
585/// #         unreachable!()
586/// #     }
587/// # }
588/// #
589/// use std::time::Duration;
590///
591/// use pyo3::prelude::*;
592///
593/// /// Awaitable sleep function
594/// #[pyfunction]
595/// fn sleep_for<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult<Bound<'p, PyAny>> {
596///     let secs = secs.extract()?;
597///     pyo3_async_runtimes::generic::future_into_py_with_locals::<MyCustomRuntime, _, _>(
598///         py,
599///         pyo3_async_runtimes::generic::get_current_locals::<MyCustomRuntime>(py)?,
600///         async move {
601///             MyCustomRuntime::sleep(Duration::from_secs(secs)).await;
602///             Ok(())
603///         }
604///     )
605/// }
606/// ```
607#[allow(unused_must_use)]
608pub fn future_into_py_with_locals<R, F, T>(
609    py: Python,
610    locals: TaskLocals,
611    fut: F,
612) -> PyResult<Bound<PyAny>>
613where
614    R: Runtime + ContextExt,
615    F: Future<Output = PyResult<T>> + Send + 'static,
616    T: for<'py> IntoPyObject<'py> + Send + 'static,
617{
618    let (cancel_tx, cancel_rx) = oneshot::channel();
619
620    let py_fut = create_future(locals.0.event_loop.bind(py).clone())?;
621    py_fut.call_method1(
622        pyo3::intern!(py, "add_done_callback"),
623        (PyDoneCallback {
624            cancel_tx: Some(cancel_tx),
625        },),
626    )?;
627
628    let future_tx1: Py<PyAny> = py_fut.clone().into();
629    let future_tx2 = future_tx1.clone_ref(py);
630
631    R::spawn(async move {
632        let locals2 = locals.clone();
633
634        if let Err(e) = R::spawn(async move {
635            let result = R::scope(
636                locals2.clone(),
637                Cancellable::new_with_cancel_rx(fut, cancel_rx),
638            )
639            .await;
640
641            // We should not hold GIL inside async-std/tokio event loop,
642            // because a blocked task may prevent other tasks from progressing.
643            R::spawn_blocking(|| {
644                Python::attach(move |py| {
645                    if cancelled(future_tx1.bind(py))
646                        .map_err(dump_err(py))
647                        .unwrap_or(false)
648                    {
649                        return;
650                    }
651
652                    let _ = set_result(
653                        &locals2.event_loop(py),
654                        future_tx1.bind(py),
655                        result.and_then(|val| val.into_py_any(py)),
656                    )
657                    .map_err(dump_err(py));
658                });
659            });
660        })
661        .await
662        {
663            if e.is_panic() {
664                R::spawn_blocking(|| {
665                    Python::attach(move |py| {
666                        if cancelled(future_tx2.bind(py))
667                            .map_err(dump_err(py))
668                            .unwrap_or(false)
669                        {
670                            return;
671                        }
672
673                        let panic_message = format!(
674                            "rust future panicked: {}",
675                            get_panic_message(&e.into_panic())
676                        );
677                        let _ = set_result(
678                            locals.0.event_loop.bind(py),
679                            future_tx2.bind(py),
680                            Err(RustPanic::new_err(panic_message)),
681                        )
682                        .map_err(dump_err(py));
683                    });
684                });
685            }
686        }
687    });
688
689    Ok(py_fut)
690}
691
692fn get_panic_message(any: &dyn std::any::Any) -> &str {
693    if let Some(str_slice) = any.downcast_ref::<&str>() {
694        str_slice
695    } else if let Some(string) = any.downcast_ref::<String>() {
696        string.as_str()
697    } else {
698        "unknown error"
699    }
700}
701
702pin_project! {
703    /// Future returned by [`timeout`](timeout) and [`timeout_at`](timeout_at).
704    #[must_use = "futures do nothing unless you `.await` or poll them"]
705    #[derive(Debug)]
706    struct Cancellable<T> {
707        #[pin]
708        future: T,
709        #[pin]
710        cancel_rx: oneshot::Receiver<()>,
711
712        poll_cancel_rx: bool
713    }
714}
715
716impl<T> Cancellable<T> {
717    fn new_with_cancel_rx(future: T, cancel_rx: oneshot::Receiver<()>) -> Self {
718        Self {
719            future,
720            cancel_rx,
721
722            poll_cancel_rx: true,
723        }
724    }
725}
726
727impl<'py, F, T> Future for Cancellable<F>
728where
729    F: Future<Output = PyResult<T>>,
730    T: IntoPyObject<'py>,
731{
732    type Output = F::Output;
733
734    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
735        let this = self.project();
736
737        // First, try polling the future
738        if let Poll::Ready(v) = this.future.poll(cx) {
739            return Poll::Ready(v);
740        }
741
742        // Now check for cancellation
743        if *this.poll_cancel_rx {
744            match this.cancel_rx.poll(cx) {
745                Poll::Ready(Ok(())) => {
746                    *this.poll_cancel_rx = false;
747                    // The python future has already been cancelled, so this return value will never
748                    // be used.
749                    Poll::Ready(Err(pyo3::exceptions::PyBaseException::new_err(
750                        "unreachable",
751                    )))
752                }
753                Poll::Ready(Err(_)) => {
754                    *this.poll_cancel_rx = false;
755                    Poll::Pending
756                }
757                Poll::Pending => Poll::Pending,
758            }
759        } else {
760            Poll::Pending
761        }
762    }
763}
764
765#[pyclass]
766struct PyDoneCallback {
767    cancel_tx: Option<oneshot::Sender<()>>,
768}
769
770#[pymethods]
771impl PyDoneCallback {
772    pub fn __call__(&mut self, fut: &Bound<PyAny>) -> PyResult<()> {
773        let py = fut.py();
774
775        if cancelled(fut).map_err(dump_err(py)).unwrap_or(false) {
776            let _ = self.cancel_tx.take().unwrap().send(());
777        }
778
779        Ok(())
780    }
781}
782
783/// Convert a Rust Future into a Python awaitable with a generic runtime
784///
785/// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`,
786/// the Rust future will be cancelled as well (new behaviour in `v0.15`).
787///
788/// Python `contextvars` are preserved when calling async Python functions within the Rust future
789/// via [`into_future`] (new behaviour in `v0.15`).
790///
791/// > Although `contextvars` are preserved for async Python functions, synchronous functions will
792/// > unfortunately fail to resolve them when called within the Rust future. This is because the
793/// > function is being called from a Rust thread, not inside an actual Python coroutine context.
794/// >
795/// > As a workaround, you can get the `contextvars` from the current task locals using
796/// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your
797/// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the
798/// > synchronous function, and restore the previous context when it returns or raises an exception.
799///
800/// # Arguments
801/// * `py` - The current PyO3 GIL guard
802/// * `fut` - The Rust future to be converted
803///
804/// # Examples
805///
806/// ```no_run
807/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future};
808/// #
809/// # use pyo3_async_runtimes::{
810/// #     TaskLocals,
811/// #     generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime}
812/// # };
813/// #
814/// # struct MyCustomJoinError;
815/// #
816/// # impl JoinError for MyCustomJoinError {
817/// #     fn is_panic(&self) -> bool {
818/// #         unreachable!()
819/// #     }
820/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
821/// #         unreachable!()
822/// #     }
823/// # }
824/// #
825/// # struct MyCustomJoinHandle;
826/// #
827/// # impl Future for MyCustomJoinHandle {
828/// #     type Output = Result<(), MyCustomJoinError>;
829/// #
830/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
831/// #         unreachable!()
832/// #     }
833/// # }
834/// #
835/// # struct MyCustomRuntime;
836/// #
837/// # impl MyCustomRuntime {
838/// #     async fn sleep(_: Duration) {
839/// #         unreachable!()
840/// #     }
841/// # }
842/// #
843/// # impl Runtime for MyCustomRuntime {
844/// #     type JoinError = MyCustomJoinError;
845/// #     type JoinHandle = MyCustomJoinHandle;
846/// #
847/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
848/// #     where
849/// #         F: Future<Output = ()> + Send + 'static
850/// #     {
851/// #         unreachable!()
852/// #     }
853/// #
854/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
855/// #         unreachable!()
856/// #     }
857/// # }
858/// #
859/// # impl ContextExt for MyCustomRuntime {
860/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
861/// #     where
862/// #         F: Future<Output = R> + Send + 'static
863/// #     {
864/// #         unreachable!()
865/// #     }
866/// #     fn get_task_locals() -> Option<TaskLocals> {
867/// #         unreachable!()
868/// #     }
869/// # }
870/// #
871/// use std::time::Duration;
872///
873/// use pyo3::prelude::*;
874///
875/// /// Awaitable sleep function
876/// #[pyfunction]
877/// fn sleep_for<'p>(py: Python<'p>, secs: Bound<'p, PyAny>) -> PyResult<Bound<'p, PyAny>> {
878///     let secs = secs.extract()?;
879///     pyo3_async_runtimes::generic::future_into_py::<MyCustomRuntime, _, _>(py, async move {
880///         MyCustomRuntime::sleep(Duration::from_secs(secs)).await;
881///         Ok(())
882///     })
883/// }
884/// ```
885pub fn future_into_py<R, F, T>(py: Python, fut: F) -> PyResult<Bound<PyAny>>
886where
887    R: Runtime + ContextExt,
888    F: Future<Output = PyResult<T>> + Send + 'static,
889    T: for<'py> IntoPyObject<'py> + Send + 'static,
890{
891    future_into_py_with_locals::<R, F, T>(py, get_current_locals::<R>(py)?, fut)
892}
893
894/// Convert a `!Send` Rust Future into a Python awaitable with a generic runtime and manual
895/// specification of task locals.
896///
897/// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`,
898/// the Rust future will be cancelled as well (new behaviour in `v0.15`).
899///
900/// Python `contextvars` are preserved when calling async Python functions within the Rust future
901/// via [`into_future`] (new behaviour in `v0.15`).
902///
903/// > Although `contextvars` are preserved for async Python functions, synchronous functions will
904/// > unfortunately fail to resolve them when called within the Rust future. This is because the
905/// > function is being called from a Rust thread, not inside an actual Python coroutine context.
906/// >
907/// > As a workaround, you can get the `contextvars` from the current task locals using
908/// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your
909/// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the
910/// > synchronous function, and restore the previous context when it returns or raises an exception.
911///
912/// # Arguments
913/// * `py` - PyO3 GIL guard
914/// * `locals` - The task locals for the future
915/// * `fut` - The Rust future to be converted
916///
917/// # Examples
918///
919/// ```no_run
920/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future};
921/// #
922/// # use pyo3_async_runtimes::{
923/// #     TaskLocals,
924/// #     generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime}
925/// # };
926/// #
927/// # struct MyCustomJoinError;
928/// #
929/// # impl JoinError for MyCustomJoinError {
930/// #     fn is_panic(&self) -> bool {
931/// #         unreachable!()
932/// #     }
933/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
934/// #         unreachable!()
935/// #     }
936/// # }
937/// #
938/// # struct MyCustomJoinHandle;
939/// #
940/// # impl Future for MyCustomJoinHandle {
941/// #     type Output = Result<(), MyCustomJoinError>;
942/// #
943/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
944/// #         unreachable!()
945/// #     }
946/// # }
947/// #
948/// # struct MyCustomRuntime;
949/// #
950/// # impl MyCustomRuntime {
951/// #     async fn sleep(_: Duration) {
952/// #         unreachable!()
953/// #     }
954/// # }
955/// #
956/// # impl Runtime for MyCustomRuntime {
957/// #     type JoinError = MyCustomJoinError;
958/// #     type JoinHandle = MyCustomJoinHandle;
959/// #
960/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
961/// #     where
962/// #         F: Future<Output = ()> + Send + 'static
963/// #     {
964/// #         unreachable!()
965/// #     }
966/// #
967/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
968/// #         unreachable!()
969/// #     }
970/// # }
971/// #
972/// # impl ContextExt for MyCustomRuntime {
973/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
974/// #     where
975/// #         F: Future<Output = R> + Send + 'static
976/// #     {
977/// #         unreachable!()
978/// #     }
979/// #     fn get_task_locals() -> Option<TaskLocals> {
980/// #         unreachable!()
981/// #     }
982/// # }
983/// #
984/// # impl SpawnLocalExt for MyCustomRuntime {
985/// #     fn spawn_local<F>(fut: F) -> Self::JoinHandle
986/// #     where
987/// #         F: Future<Output = ()> + 'static
988/// #     {
989/// #         unreachable!()
990/// #     }
991/// # }
992/// #
993/// # impl LocalContextExt for MyCustomRuntime {
994/// #     fn scope_local<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R>>>
995/// #     where
996/// #         F: Future<Output = R> + 'static
997/// #     {
998/// #         unreachable!()
999/// #     }
1000/// # }
1001/// #
1002/// use std::{rc::Rc, time::Duration};
1003///
1004/// use pyo3::prelude::*;
1005///
1006/// /// Awaitable sleep function
1007/// #[pyfunction]
1008/// fn sleep_for(py: Python, secs: u64) -> PyResult<Bound<PyAny>> {
1009///     // Rc is !Send so it cannot be passed into pyo3_async_runtimes::generic::future_into_py
1010///     let secs = Rc::new(secs);
1011///
1012///     pyo3_async_runtimes::generic::local_future_into_py_with_locals::<MyCustomRuntime, _, _>(
1013///         py,
1014///         pyo3_async_runtimes::generic::get_current_locals::<MyCustomRuntime>(py)?,
1015///         async move {
1016///             MyCustomRuntime::sleep(Duration::from_secs(*secs)).await;
1017///             Ok(())
1018///         }
1019///     )
1020/// }
1021/// ```
1022#[deprecated(
1023    since = "0.18.0",
1024    note = "Questionable whether these conversions have real-world utility (see https://github.com/awestlake87/pyo3-asyncio/issues/59#issuecomment-1008038497 and let me know if you disagree!)"
1025)]
1026#[allow(unused_must_use)]
1027pub fn local_future_into_py_with_locals<R, F, T>(
1028    py: Python,
1029    locals: TaskLocals,
1030    fut: F,
1031) -> PyResult<Bound<PyAny>>
1032where
1033    R: Runtime + SpawnLocalExt + LocalContextExt,
1034    F: Future<Output = PyResult<T>> + 'static,
1035    T: for<'py> IntoPyObject<'py>,
1036{
1037    let (cancel_tx, cancel_rx) = oneshot::channel();
1038
1039    let py_fut = create_future(locals.0.event_loop.clone_ref(py).into_bound(py))?;
1040    py_fut.call_method1(
1041        pyo3::intern!(py, "add_done_callback"),
1042        (PyDoneCallback {
1043            cancel_tx: Some(cancel_tx),
1044        },),
1045    )?;
1046
1047    let future_tx1: Py<PyAny> = py_fut.clone().into();
1048    let future_tx2 = future_tx1.clone_ref(py);
1049
1050    R::spawn_local(async move {
1051        let locals2 = locals.clone();
1052
1053        if let Err(e) = R::spawn_local(async move {
1054            let result = R::scope_local(
1055                locals2.clone(),
1056                Cancellable::new_with_cancel_rx(fut, cancel_rx),
1057            )
1058            .await;
1059
1060            Python::attach(move |py| {
1061                if cancelled(future_tx1.bind(py))
1062                    .map_err(dump_err(py))
1063                    .unwrap_or(false)
1064                {
1065                    return;
1066                }
1067
1068                let _ = set_result(
1069                    locals2.0.event_loop.bind(py),
1070                    future_tx1.bind(py),
1071                    result.and_then(|val| val.into_py_any(py)),
1072                )
1073                .map_err(dump_err(py));
1074            });
1075        })
1076        .await
1077        {
1078            if e.is_panic() {
1079                Python::attach(move |py| {
1080                    if cancelled(future_tx2.bind(py))
1081                        .map_err(dump_err(py))
1082                        .unwrap_or(false)
1083                    {
1084                        return;
1085                    }
1086
1087                    let panic_message = format!(
1088                        "rust future panicked: {}",
1089                        get_panic_message(&e.into_panic())
1090                    );
1091                    let _ = set_result(
1092                        locals.0.event_loop.bind(py),
1093                        future_tx2.bind(py),
1094                        Err(RustPanic::new_err(panic_message)),
1095                    )
1096                    .map_err(dump_err(py));
1097                });
1098            }
1099        }
1100    });
1101
1102    Ok(py_fut)
1103}
1104
1105/// Convert a `!Send` Rust Future into a Python awaitable with a generic runtime
1106///
1107/// If the `asyncio.Future` returned by this conversion is cancelled via `asyncio.Future.cancel`,
1108/// the Rust future will be cancelled as well (new behaviour in `v0.15`).
1109///
1110/// Python `contextvars` are preserved when calling async Python functions within the Rust future
1111/// via [`into_future`] (new behaviour in `v0.15`).
1112///
1113/// > Although `contextvars` are preserved for async Python functions, synchronous functions will
1114/// > unfortunately fail to resolve them when called within the Rust future. This is because the
1115/// > function is being called from a Rust thread, not inside an actual Python coroutine context.
1116/// >
1117/// > As a workaround, you can get the `contextvars` from the current task locals using
1118/// > [`get_current_locals`] and [`TaskLocals::context`](`crate::TaskLocals::context`), then wrap your
1119/// > synchronous function in a call to `contextvars.Context.run`. This will set the context, call the
1120/// > synchronous function, and restore the previous context when it returns or raises an exception.
1121///
1122/// # Arguments
1123/// * `py` - The current PyO3 GIL guard
1124/// * `fut` - The Rust future to be converted
1125///
1126/// # Examples
1127///
1128/// ```no_run
1129/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future};
1130/// #
1131/// # use pyo3_async_runtimes::{
1132/// #     TaskLocals,
1133/// #     generic::{JoinError, SpawnLocalExt, ContextExt, LocalContextExt, Runtime}
1134/// # };
1135/// #
1136/// # struct MyCustomJoinError;
1137/// #
1138/// # impl JoinError for MyCustomJoinError {
1139/// #     fn is_panic(&self) -> bool {
1140/// #         unreachable!()
1141/// #     }
1142/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
1143/// #         unreachable!()
1144/// #     }
1145/// # }
1146/// #
1147/// # struct MyCustomJoinHandle;
1148/// #
1149/// # impl Future for MyCustomJoinHandle {
1150/// #     type Output = Result<(), MyCustomJoinError>;
1151/// #
1152/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
1153/// #         unreachable!()
1154/// #     }
1155/// # }
1156/// #
1157/// # struct MyCustomRuntime;
1158/// #
1159/// # impl MyCustomRuntime {
1160/// #     async fn sleep(_: Duration) {
1161/// #         unreachable!()
1162/// #     }
1163/// # }
1164/// #
1165/// # impl Runtime for MyCustomRuntime {
1166/// #     type JoinError = MyCustomJoinError;
1167/// #     type JoinHandle = MyCustomJoinHandle;
1168/// #
1169/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
1170/// #     where
1171/// #         F: Future<Output = ()> + Send + 'static
1172/// #     {
1173/// #         unreachable!()
1174/// #     }
1175/// #
1176/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
1177/// #         unreachable!()
1178/// #     }
1179/// # }
1180/// #
1181/// # impl ContextExt for MyCustomRuntime {
1182/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
1183/// #     where
1184/// #         F: Future<Output = R> + Send + 'static
1185/// #     {
1186/// #         unreachable!()
1187/// #     }
1188/// #     fn get_task_locals() -> Option<TaskLocals> {
1189/// #         unreachable!()
1190/// #     }
1191/// # }
1192/// #
1193/// # impl SpawnLocalExt for MyCustomRuntime {
1194/// #     fn spawn_local<F>(fut: F) -> Self::JoinHandle
1195/// #     where
1196/// #         F: Future<Output = ()> + 'static
1197/// #     {
1198/// #         unreachable!()
1199/// #     }
1200/// # }
1201/// #
1202/// # impl LocalContextExt for MyCustomRuntime {
1203/// #     fn scope_local<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R>>>
1204/// #     where
1205/// #         F: Future<Output = R> + 'static
1206/// #     {
1207/// #         unreachable!()
1208/// #     }
1209/// # }
1210/// #
1211/// use std::{rc::Rc, time::Duration};
1212///
1213/// use pyo3::prelude::*;
1214///
1215/// /// Awaitable sleep function
1216/// #[pyfunction]
1217/// fn sleep_for(py: Python, secs: u64) -> PyResult<Bound<PyAny>> {
1218///     // Rc is !Send so it cannot be passed into pyo3_async_runtimes::generic::future_into_py
1219///     let secs = Rc::new(secs);
1220///
1221///     pyo3_async_runtimes::generic::local_future_into_py::<MyCustomRuntime, _, _>(py, async move {
1222///         MyCustomRuntime::sleep(Duration::from_secs(*secs)).await;
1223///         Ok(())
1224///     })
1225/// }
1226/// ```
1227#[deprecated(
1228    since = "0.18.0",
1229    note = "Questionable whether these conversions have real-world utility (see https://github.com/awestlake87/pyo3-asyncio/issues/59#issuecomment-1008038497 and let me know if you disagree!)"
1230)]
1231#[allow(deprecated)]
1232pub fn local_future_into_py<R, F, T>(py: Python, fut: F) -> PyResult<Bound<PyAny>>
1233where
1234    R: Runtime + ContextExt + SpawnLocalExt + LocalContextExt,
1235    F: Future<Output = PyResult<T>> + 'static,
1236    T: for<'py> IntoPyObject<'py>,
1237{
1238    local_future_into_py_with_locals::<R, F, T>(py, get_current_locals::<R>(py)?, fut)
1239}
1240
1241/// <span class="module-item stab portability" style="display: inline; border-radius: 3px; padding: 2px; font-size: 80%; line-height: 1.2;"><code>unstable-streams</code></span> Convert an async generator into a stream
1242///
1243/// **This API is marked as unstable** and is only available when the
1244/// `unstable-streams` crate feature is enabled. This comes with no
1245/// stability guarantees, and could be changed or removed at any time.
1246///
1247/// # Arguments
1248/// * `locals` - The current task locals
1249/// * `gen` - The Python async generator to be converted
1250///
1251/// # Examples
1252/// ```no_run
1253/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future};
1254/// #
1255/// # use pyo3_async_runtimes::{
1256/// #     TaskLocals,
1257/// #     generic::{JoinError, ContextExt, Runtime}
1258/// # };
1259/// #
1260/// # struct MyCustomJoinError;
1261/// #
1262/// # impl JoinError for MyCustomJoinError {
1263/// #     fn is_panic(&self) -> bool {
1264/// #         unreachable!()
1265/// #     }
1266/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
1267/// #         unreachable!()
1268/// #     }
1269/// # }
1270/// #
1271/// # struct MyCustomJoinHandle;
1272/// #
1273/// # impl Future for MyCustomJoinHandle {
1274/// #     type Output = Result<(), MyCustomJoinError>;
1275/// #
1276/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
1277/// #         unreachable!()
1278/// #     }
1279/// # }
1280/// #
1281/// # struct MyCustomRuntime;
1282/// #
1283/// # impl Runtime for MyCustomRuntime {
1284/// #     type JoinError = MyCustomJoinError;
1285/// #     type JoinHandle = MyCustomJoinHandle;
1286/// #
1287/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
1288/// #     where
1289/// #         F: Future<Output = ()> + Send + 'static
1290/// #     {
1291/// #         unreachable!()
1292/// #     }
1293/// #
1294/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
1295/// #         unreachable!()
1296/// #     }
1297/// # }
1298/// #
1299/// # impl ContextExt for MyCustomRuntime {
1300/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
1301/// #     where
1302/// #         F: Future<Output = R> + Send + 'static
1303/// #     {
1304/// #         unreachable!()
1305/// #     }
1306/// #     fn get_task_locals() -> Option<TaskLocals> {
1307/// #         unreachable!()
1308/// #     }
1309/// # }
1310///
1311/// use pyo3::prelude::*;
1312/// use futures_util::stream::{StreamExt, TryStreamExt};
1313/// use std::ffi::CString;
1314///
1315/// const TEST_MOD: &str = r#"
1316/// import asyncio
1317///
1318/// async def gen():
1319///     for i in range(10):
1320///         await asyncio.sleep(0.1)
1321///         yield i
1322/// "#;
1323///
1324/// # async fn test_async_gen() -> PyResult<()> {
1325/// let stream = Python::attach(|py| {
1326///     let test_mod = PyModule::from_code(
1327///         py,
1328///         &CString::new(TEST_MOD).unwrap(),
1329///         &CString::new("test_rust_coroutine/test_mod.py").unwrap(),
1330///         &CString::new("test_mod").unwrap(),
1331///     )?;
1332///
1333///     pyo3_async_runtimes::generic::into_stream_with_locals_v1::<MyCustomRuntime>(
1334///         pyo3_async_runtimes::generic::get_current_locals::<MyCustomRuntime>(py)?,
1335///         test_mod.call_method0("gen")?
1336///     )
1337/// })?;
1338///
1339/// let vals = stream
1340///     .map(|item| Python::attach(|py| -> PyResult<i32> { Ok(item?.bind(py).extract()?) }))
1341///     .try_collect::<Vec<i32>>()
1342///     .await?;
1343///
1344/// assert_eq!((0..10).collect::<Vec<i32>>(), vals);
1345///
1346/// Ok(())
1347/// # }
1348/// ```
1349#[cfg(feature = "unstable-streams")]
1350#[allow(unused_must_use)] // False positive unused lint on `R::spawn`
1351pub fn into_stream_with_locals_v1<R>(
1352    locals: TaskLocals,
1353    gen: Bound<'_, PyAny>,
1354) -> PyResult<impl futures_util::stream::Stream<Item = PyResult<Py<PyAny>>> + 'static>
1355where
1356    R: Runtime,
1357{
1358    let (tx, rx) = async_channel::bounded(1);
1359    let py = gen.py();
1360    let anext: Py<PyAny> = gen.getattr(pyo3::intern!(py, "__anext__"))?.into();
1361
1362    R::spawn(async move {
1363        loop {
1364            let fut = Python::attach(|py| -> PyResult<_> {
1365                into_future_with_locals(&locals, anext.bind(py).call0()?)
1366            });
1367            let item = match fut {
1368                Ok(fut) => match fut.await {
1369                    Ok(item) => Ok(item),
1370                    Err(e) => {
1371                        let stop_iter = Python::attach(|py| {
1372                            e.is_instance_of::<pyo3::exceptions::PyStopAsyncIteration>(py)
1373                        });
1374
1375                        if stop_iter {
1376                            // end the iteration
1377                            break;
1378                        } else {
1379                            Err(e)
1380                        }
1381                    }
1382                },
1383                Err(e) => Err(e),
1384            };
1385
1386            if tx.send(item).await.is_err() {
1387                // receiving side was dropped
1388                break;
1389            }
1390        }
1391    });
1392
1393    Ok(rx)
1394}
1395
1396/// <span class="module-item stab portability" style="display: inline; border-radius: 3px; padding: 2px; font-size: 80%; line-height: 1.2;"><code>unstable-streams</code></span> Convert an async generator into a stream
1397///
1398/// **This API is marked as unstable** and is only available when the
1399/// `unstable-streams` crate feature is enabled. This comes with no
1400/// stability guarantees, and could be changed or removed at any time.
1401///
1402/// # Arguments
1403/// * `gen` - The Python async generator to be converted
1404///
1405/// # Examples
1406/// ```no_run
1407/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future};
1408/// #
1409/// # use pyo3_async_runtimes::{
1410/// #     TaskLocals,
1411/// #     generic::{JoinError, ContextExt, Runtime}
1412/// # };
1413/// #
1414/// # struct MyCustomJoinError;
1415/// #
1416/// # impl JoinError for MyCustomJoinError {
1417/// #     fn is_panic(&self) -> bool {
1418/// #         unreachable!()
1419/// #     }
1420/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
1421/// #         unreachable!()
1422/// #     }
1423/// # }
1424/// #
1425/// # struct MyCustomJoinHandle;
1426/// #
1427/// # impl Future for MyCustomJoinHandle {
1428/// #     type Output = Result<(), MyCustomJoinError>;
1429/// #
1430/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
1431/// #         unreachable!()
1432/// #     }
1433/// # }
1434/// #
1435/// # struct MyCustomRuntime;
1436/// #
1437/// # impl Runtime for MyCustomRuntime {
1438/// #     type JoinError = MyCustomJoinError;
1439/// #     type JoinHandle = MyCustomJoinHandle;
1440/// #
1441/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
1442/// #     where
1443/// #         F: Future<Output = ()> + Send + 'static
1444/// #     {
1445/// #         unreachable!()
1446/// #     }
1447/// #
1448/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
1449/// #         unreachable!()
1450/// #     }
1451/// # }
1452/// #
1453/// # impl ContextExt for MyCustomRuntime {
1454/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
1455/// #     where
1456/// #         F: Future<Output = R> + Send + 'static
1457/// #     {
1458/// #         unreachable!()
1459/// #     }
1460/// #     fn get_task_locals() -> Option<TaskLocals> {
1461/// #         unreachable!()
1462/// #     }
1463/// # }
1464///
1465/// use pyo3::prelude::*;
1466/// use futures_util::stream::{StreamExt, TryStreamExt};
1467/// use std::ffi::CString;
1468///
1469/// const TEST_MOD: &str = r#"
1470/// import asyncio
1471///
1472/// async def gen():
1473///     for i in range(10):
1474///         await asyncio.sleep(0.1)
1475///         yield i
1476/// "#;
1477///
1478/// # async fn test_async_gen() -> PyResult<()> {
1479/// let stream = Python::attach(|py| {
1480///     let test_mod = PyModule::from_code(
1481///         py,
1482///         &CString::new(TEST_MOD).unwrap(),
1483///         &CString::new("test_rust_coroutine/test_mod.py").unwrap(),
1484///         &CString::new("test_mod").unwrap(),
1485///     )?;
1486///
1487///     pyo3_async_runtimes::generic::into_stream_v1::<MyCustomRuntime>(test_mod.call_method0("gen")?)
1488/// })?;
1489///
1490/// let vals = stream
1491///     .map(|item| Python::attach(|py| -> PyResult<i32> { Ok(item?.bind(py).extract()?) }))
1492///     .try_collect::<Vec<i32>>()
1493///     .await?;
1494///
1495/// assert_eq!((0..10).collect::<Vec<i32>>(), vals);
1496///
1497/// Ok(())
1498/// # }
1499/// ```
1500#[cfg(feature = "unstable-streams")]
1501pub fn into_stream_v1<R>(
1502    gen: Bound<'_, PyAny>,
1503) -> PyResult<impl futures_util::stream::Stream<Item = PyResult<Py<PyAny>>> + 'static>
1504where
1505    R: Runtime + ContextExt,
1506{
1507    into_stream_with_locals_v1::<R>(get_current_locals::<R>(gen.py())?, gen)
1508}
1509
1510trait Sender: Send + 'static {
1511    fn send(&mut self, py: Python, locals: TaskLocals, item: Py<PyAny>) -> PyResult<Py<PyAny>>;
1512    fn close(&mut self) -> PyResult<()>;
1513}
1514
1515#[cfg(feature = "unstable-streams")]
1516struct GenericSender<R>
1517where
1518    R: Runtime,
1519{
1520    runtime: PhantomData<R>,
1521    tx: mpsc::Sender<Py<PyAny>>,
1522}
1523
1524#[cfg(feature = "unstable-streams")]
1525impl<R> Sender for GenericSender<R>
1526where
1527    R: Runtime + ContextExt,
1528{
1529    fn send(&mut self, py: Python, locals: TaskLocals, item: Py<PyAny>) -> PyResult<Py<PyAny>> {
1530        match self.tx.try_send(item.clone_ref(py)) {
1531            Ok(_) => true.into_py_any(py),
1532            Err(e) => {
1533                if e.is_full() {
1534                    let mut tx = self.tx.clone();
1535
1536                    future_into_py_with_locals::<R, _, bool>(py, locals, async move {
1537                        if tx.flush().await.is_err() {
1538                            // receiving side disconnected
1539                            return Ok(false);
1540                        }
1541                        if tx.send(item).await.is_err() {
1542                            // receiving side disconnected
1543                            return Ok(false);
1544                        }
1545                        Ok(true)
1546                    })
1547                    .map(Bound::unbind)
1548                } else {
1549                    false.into_py_any(py)
1550                }
1551            }
1552        }
1553    }
1554    fn close(&mut self) -> PyResult<()> {
1555        self.tx.close_channel();
1556        Ok(())
1557    }
1558}
1559
1560#[pyclass]
1561struct SenderGlue {
1562    locals: TaskLocals,
1563    tx: Arc<Mutex<dyn Sender>>,
1564}
1565#[pymethods]
1566impl SenderGlue {
1567    pub fn send(&mut self, item: Py<PyAny>) -> PyResult<Py<PyAny>> {
1568        Python::attach(|py| self.tx.lock().unwrap().send(py, self.locals.clone(), item))
1569    }
1570    pub fn close(&mut self) -> PyResult<()> {
1571        self.tx.lock().unwrap().close()
1572    }
1573}
1574
1575#[cfg(feature = "unstable-streams")]
1576const STREAM_GLUE: &str = r#"
1577import inspect
1578
1579async def forward(gen, sender):
1580    async for item in gen:
1581        should_continue = sender.send(item)
1582
1583        if inspect.isawaitable(should_continue):
1584            should_continue = await should_continue
1585
1586        if should_continue:
1587            continue
1588        else:
1589            break
1590
1591    sender.close()
1592"#;
1593
1594/// <span class="module-item stab portability" style="display: inline; border-radius: 3px; padding: 2px; font-size: 80%; line-height: 1.2;"><code>unstable-streams</code></span> Convert an async generator into a stream
1595///
1596/// **This API is marked as unstable** and is only available when the
1597/// `unstable-streams` crate feature is enabled. This comes with no
1598/// stability guarantees, and could be changed or removed at any time.
1599///
1600/// # Arguments
1601/// * `locals` - The current task locals
1602/// * `gen` - The Python async generator to be converted
1603///
1604/// # Examples
1605/// ```no_run
1606/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future};
1607/// #
1608/// # use pyo3_async_runtimes::{
1609/// #     TaskLocals,
1610/// #     generic::{JoinError, ContextExt, Runtime}
1611/// # };
1612/// #
1613/// # struct MyCustomJoinError;
1614/// #
1615/// # impl JoinError for MyCustomJoinError {
1616/// #     fn is_panic(&self) -> bool {
1617/// #         unreachable!()
1618/// #     }
1619/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
1620/// #         unreachable!()
1621/// #     }
1622/// # }
1623/// #
1624/// # struct MyCustomJoinHandle;
1625/// #
1626/// # impl Future for MyCustomJoinHandle {
1627/// #     type Output = Result<(), MyCustomJoinError>;
1628/// #
1629/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
1630/// #         unreachable!()
1631/// #     }
1632/// # }
1633/// #
1634/// # struct MyCustomRuntime;
1635/// #
1636/// # impl Runtime for MyCustomRuntime {
1637/// #     type JoinError = MyCustomJoinError;
1638/// #     type JoinHandle = MyCustomJoinHandle;
1639/// #
1640/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
1641/// #     where
1642/// #         F: Future<Output = ()> + Send + 'static
1643/// #     {
1644/// #         unreachable!()
1645/// #     }
1646/// #
1647/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
1648/// #         unreachable!()
1649/// #     }
1650/// # }
1651/// #
1652/// # impl ContextExt for MyCustomRuntime {
1653/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
1654/// #     where
1655/// #         F: Future<Output = R> + Send + 'static
1656/// #     {
1657/// #         unreachable!()
1658/// #     }
1659/// #     fn get_task_locals() -> Option<TaskLocals> {
1660/// #         unreachable!()
1661/// #     }
1662/// # }
1663///
1664/// use pyo3::prelude::*;
1665/// use futures_util::stream::{StreamExt, TryStreamExt};
1666/// use std::ffi::CString;
1667///
1668/// const TEST_MOD: &str = r#"
1669/// import asyncio
1670///
1671/// async def gen():
1672///     for i in range(10):
1673///         await asyncio.sleep(0.1)
1674///         yield i
1675/// "#;
1676///
1677/// # async fn test_async_gen() -> PyResult<()> {
1678/// let stream = Python::attach(|py| {
1679///     let test_mod = PyModule::from_code(
1680///         py,
1681///         &CString::new(TEST_MOD).unwrap(),
1682///         &CString::new("test_rust_coroutine/test_mod.py").unwrap(),
1683///         &CString::new("test_mod").unwrap(),
1684///     )?;
1685///
1686///     pyo3_async_runtimes::generic::into_stream_with_locals_v2::<MyCustomRuntime>(
1687///         pyo3_async_runtimes::generic::get_current_locals::<MyCustomRuntime>(py)?,
1688///         test_mod.call_method0("gen")?
1689///     )
1690/// })?;
1691///
1692/// let vals = stream
1693///     .map(|item| Python::attach(|py| -> PyResult<i32> { Ok(item.bind(py).extract()?) }))
1694///     .try_collect::<Vec<i32>>()
1695///     .await?;
1696///
1697/// assert_eq!((0..10).collect::<Vec<i32>>(), vals);
1698///
1699/// Ok(())
1700/// # }
1701/// ```
1702#[cfg(feature = "unstable-streams")]
1703pub fn into_stream_with_locals_v2<R>(
1704    locals: TaskLocals,
1705    gen: Bound<'_, PyAny>,
1706) -> PyResult<impl futures_util::stream::Stream<Item = Py<PyAny>> + 'static>
1707where
1708    R: Runtime + ContextExt,
1709{
1710    use std::ffi::CString;
1711
1712    use pyo3::sync::PyOnceLock;
1713
1714    static GLUE_MOD: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
1715    let py = gen.py();
1716    let glue = GLUE_MOD
1717        .get_or_try_init(py, || -> PyResult<Py<PyAny>> {
1718            Ok(PyModule::from_code(
1719                py,
1720                &CString::new(STREAM_GLUE).unwrap(),
1721                &CString::new("pyo3_async_runtimes/pyo3_async_runtimes_glue.py").unwrap(),
1722                &CString::new("pyo3_async_runtimes_glue").unwrap(),
1723            )?
1724            .into())
1725        })?
1726        .bind(py);
1727
1728    let (tx, rx) = mpsc::channel(10);
1729
1730    locals.event_loop(py).call_method1(
1731        pyo3::intern!(py, "call_soon_threadsafe"),
1732        (
1733            locals
1734                .event_loop(py)
1735                .getattr(pyo3::intern!(py, "create_task"))?,
1736            glue.call_method1(
1737                pyo3::intern!(py, "forward"),
1738                (
1739                    gen,
1740                    SenderGlue {
1741                        locals,
1742                        tx: Arc::new(Mutex::new(GenericSender {
1743                            runtime: PhantomData::<R>,
1744                            tx,
1745                        })),
1746                    },
1747                ),
1748            )?,
1749        ),
1750    )?;
1751    Ok(rx)
1752}
1753
1754/// <span class="module-item stab portability" style="display: inline; border-radius: 3px; padding: 2px; font-size: 80%; line-height: 1.2;"><code>unstable-streams</code></span> Convert an async generator into a stream
1755///
1756/// **This API is marked as unstable** and is only available when the
1757/// `unstable-streams` crate feature is enabled. This comes with no
1758/// stability guarantees, and could be changed or removed at any time.
1759///
1760/// # Arguments
1761/// * `gen` - The Python async generator to be converted
1762///
1763/// # Examples
1764/// ```no_run
1765/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future};
1766/// #
1767/// # use pyo3_async_runtimes::{
1768/// #     TaskLocals,
1769/// #     generic::{JoinError, ContextExt, Runtime}
1770/// # };
1771/// #
1772/// # struct MyCustomJoinError;
1773/// #
1774/// # impl JoinError for MyCustomJoinError {
1775/// #     fn is_panic(&self) -> bool {
1776/// #         unreachable!()
1777/// #     }
1778/// #     fn into_panic(self) -> Box<(dyn Any + Send + 'static)> {
1779/// #         unreachable!()
1780/// #     }
1781/// # }
1782/// #
1783/// # struct MyCustomJoinHandle;
1784/// #
1785/// # impl Future for MyCustomJoinHandle {
1786/// #     type Output = Result<(), MyCustomJoinError>;
1787/// #
1788/// #     fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
1789/// #         unreachable!()
1790/// #     }
1791/// # }
1792/// #
1793/// # struct MyCustomRuntime;
1794/// #
1795/// # impl Runtime for MyCustomRuntime {
1796/// #     type JoinError = MyCustomJoinError;
1797/// #     type JoinHandle = MyCustomJoinHandle;
1798/// #
1799/// #     fn spawn<F>(fut: F) -> Self::JoinHandle
1800/// #     where
1801/// #         F: Future<Output = ()> + Send + 'static
1802/// #     {
1803/// #         unreachable!()
1804/// #     }
1805/// #
1806/// #     fn spawn_blocking<F>(f: F) -> Self::JoinHandle where F: FnOnce() + Send + 'static {
1807/// #         unreachable!()
1808/// #     }
1809/// # }
1810/// #
1811/// # impl ContextExt for MyCustomRuntime {
1812/// #     fn scope<F, R>(locals: TaskLocals, fut: F) -> Pin<Box<dyn Future<Output = R> + Send>>
1813/// #     where
1814/// #         F: Future<Output = R> + Send + 'static
1815/// #     {
1816/// #         unreachable!()
1817/// #     }
1818/// #     fn get_task_locals() -> Option<TaskLocals> {
1819/// #         unreachable!()
1820/// #     }
1821/// # }
1822///
1823/// use pyo3::prelude::*;
1824/// use futures_util::stream::{StreamExt, TryStreamExt};
1825/// use std::ffi::CString;
1826///
1827/// const TEST_MOD: &str = r#"
1828/// import asyncio
1829///
1830/// async def gen():
1831///     for i in range(10):
1832///         await asyncio.sleep(0.1)
1833///         yield i
1834/// "#;
1835///
1836/// # async fn test_async_gen() -> PyResult<()> {
1837/// let stream = Python::attach(|py| {
1838///     let test_mod = PyModule::from_code(
1839///         py,
1840///         &CString::new(TEST_MOD).unwrap(),
1841///         &CString::new("test_rust_coroutine/test_mod.py").unwrap(),
1842///         &CString::new("test_mod").unwrap(),
1843///     )?;
1844///
1845///     pyo3_async_runtimes::generic::into_stream_v2::<MyCustomRuntime>(test_mod.call_method0("gen")?)
1846/// })?;
1847///
1848/// let vals = stream
1849///     .map(|item| Python::attach(|py| -> PyResult<i32> { Ok(item.bind(py).extract()?) }))
1850///     .try_collect::<Vec<i32>>()
1851///     .await?;
1852///
1853/// assert_eq!((0..10).collect::<Vec<i32>>(), vals);
1854///
1855/// Ok(())
1856/// # }
1857/// ```
1858#[cfg(feature = "unstable-streams")]
1859pub fn into_stream_v2<R>(
1860    gen: Bound<'_, PyAny>,
1861) -> PyResult<impl futures_util::stream::Stream<Item = Py<PyAny>> + 'static>
1862where
1863    R: Runtime + ContextExt,
1864{
1865    into_stream_with_locals_v2::<R>(get_current_locals::<R>(gen.py())?, gen)
1866}