Skip to content

Commit 5e3c0cb

Browse files
author
Andrew J Westlake
committed
Finished documenting primary public API functions
1 parent 5cc4a26 commit 5e3c0cb

File tree

2 files changed

+296
-3
lines changed

2 files changed

+296
-3
lines changed

pytests/test_asyncio.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,74 @@
11
use std::{future::Future, thread, time::Duration};
22

33
use futures::stream::{self};
4-
use pyo3::prelude::*;
4+
use pyo3::{prelude::*, wrap_pyfunction};
55

66
use pyo3_asyncio::testing::{test_main, Test};
77

8+
#[pyfunction]
9+
fn sleep_for(py: Python, secs: &PyAny) -> PyResult<PyObject> {
10+
let secs = secs.extract()?;
11+
12+
pyo3_asyncio::into_coroutine(py, async move {
13+
tokio::time::sleep(Duration::from_secs(secs)).await;
14+
Python::with_gil(|py| Ok(py.None()))
15+
})
16+
}
17+
18+
const TEST_MOD: &'static str = r#"
19+
import asyncio
20+
21+
async def py_sleep(duration):
22+
await asyncio.sleep(duration)
23+
24+
async def sleep_for_1s(sleep_for):
25+
await sleep_for(1)
26+
"#;
27+
28+
fn test_into_coroutine(
29+
py: Python,
30+
) -> PyResult<impl Future<Output = PyResult<()>> + Send + 'static> {
31+
let sleeper_mod: Py<PyModule> = PyModule::new(py, "rust_sleeper")?.into();
32+
33+
sleeper_mod
34+
.as_ref(py)
35+
.add_wrapped(wrap_pyfunction!(sleep_for))?;
36+
37+
let test_mod: PyObject =
38+
PyModule::from_code(py, TEST_MOD, "test_rust_coroutine/test_mod.py", "test_mod")?.into();
39+
40+
Ok(async move {
41+
Python::with_gil(|py| {
42+
pyo3_asyncio::into_future(
43+
py,
44+
test_mod
45+
.call_method1(py, "sleep_for_1s", (sleeper_mod.getattr(py, "sleep_for")?,))?
46+
.as_ref(py),
47+
)
48+
})?
49+
.await?;
50+
Ok(())
51+
})
52+
}
53+
54+
fn test_into_future(py: Python) -> PyResult<impl Future<Output = PyResult<()>> + Send + 'static> {
55+
let test_mod: PyObject =
56+
PyModule::from_code(py, TEST_MOD, "test_rust_coroutine/test_mod.py", "test_mod")?.into();
57+
58+
Ok(async move {
59+
Python::with_gil(|py| {
60+
pyo3_asyncio::into_future(
61+
py,
62+
test_mod
63+
.call_method1(py, "py_sleep", (1.into_py(py),))?
64+
.as_ref(py),
65+
)
66+
})?
67+
.await?;
68+
Ok(())
69+
})
70+
}
71+
872
fn test_async_sleep<'p>(
973
py: Python<'p>,
1074
) -> PyResult<impl Future<Output = PyResult<()>> + Send + 'static> {
@@ -42,5 +106,25 @@ fn main() {
42106
test_blocking_sleep();
43107
Ok(())
44108
}),
109+
Test::new_async(
110+
"test_into_coroutine".into(),
111+
Python::with_gil(|py| {
112+
test_into_coroutine(py)
113+
.map_err(|e| {
114+
e.print_and_set_sys_last_vars(py);
115+
})
116+
.unwrap()
117+
}),
118+
),
119+
Test::new_async(
120+
"test_into_future".into(),
121+
Python::with_gil(|py| {
122+
test_into_future(py)
123+
.map_err(|e| {
124+
e.print_and_set_sys_last_vars(py);
125+
})
126+
.unwrap()
127+
}),
128+
),
45129
]))
46130
}

src/lib.rs

Lines changed: 211 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,50 @@ pub fn get_event_loop() -> PyObject {
180180
}
181181

182182
/// Run the event loop forever
183+
///
184+
/// This can be called instead of [`run_until_complete`] to run the event loop
185+
/// until `stop` is called rather than driving a future to completion.
186+
///
187+
/// After this function returns, the event loop can be resumed with either [`run_until_complete`] or
188+
/// [`run_forever`]
189+
///
190+
/// # Arguments
191+
/// * `py` - The current PyO3 GIL guard
192+
///
193+
/// # Examples
194+
///
195+
/// ```no_run
196+
/// # use std::time::Duration;
197+
/// # use pyo3::prelude::*;
198+
/// # Python::with_gil(|py| {
199+
/// # pyo3_asyncio::with_runtime(py, || {
200+
/// // Wait 1 second, then stop the event loop
201+
/// pyo3_asyncio::spawn(async move {
202+
/// tokio::time::sleep(Duration::from_secs(1)).await;
203+
/// Python::with_gil(|py| {
204+
/// let event_loop = pyo3_asyncio::get_event_loop();
205+
///
206+
/// event_loop
207+
/// .call_method1(
208+
/// py,
209+
/// "call_soon_threadsafe",
210+
/// (event_loop
211+
/// .getattr(py, "stop")
212+
/// .map_err(|e| e.print_and_set_sys_last_vars(py))
213+
/// .unwrap(),),
214+
/// )
215+
/// .map_err(|e| e.print_and_set_sys_last_vars(py))
216+
/// .unwrap();
217+
/// })
218+
/// });
219+
///
220+
/// // block until stop is called
221+
/// pyo3_asyncio::run_forever(py)?;
222+
/// # Ok(())
223+
/// # })
224+
/// # .map_err(|e| e.print_and_set_sys_last_vars(py))
225+
/// # .unwrap();
226+
/// # })
183227
pub fn run_forever(py: Python) -> PyResult<()> {
184228
if let Err(e) = EVENT_LOOP.get().unwrap().call_method0(py, "run_forever") {
185229
if e.is_instance::<PyKeyboardInterrupt>(py) {
@@ -193,6 +237,37 @@ pub fn run_forever(py: Python) -> PyResult<()> {
193237
}
194238

195239
/// Run the event loop until the given Future completes
240+
///
241+
/// The event loop runs until the given future is complete.
242+
///
243+
/// After this function returns, the event loop can be resumed with either [`run_until_complete`] or
244+
/// [`run_forever`]
245+
///
246+
/// # Arguments
247+
/// * `py` - The current PyO3 GIL guard
248+
/// * `fut` - The future to drive to completion
249+
///
250+
/// # Examples
251+
///
252+
/// ```no_run
253+
/// # use std::time::Duration;
254+
/// # use pyo3::prelude::*;
255+
/// #
256+
/// # Python::with_gil(|py| {
257+
/// # pyo3_asyncio::with_runtime(py, || {
258+
/// pyo3_asyncio::run_until_complete(py, async move {
259+
/// tokio::time::sleep(Duration::from_secs(1)).await;
260+
/// Ok(())
261+
/// })?;
262+
/// # Ok(())
263+
/// # })
264+
/// # .map_err(|e| {
265+
/// # e.print_and_set_sys_last_vars(py);
266+
/// # })
267+
/// # .unwrap();
268+
/// # });
269+
/// ```
270+
///
196271
pub fn run_until_complete<F>(py: Python, fut: F) -> PyResult<()>
197272
where
198273
F: Future<Output = PyResult<()>> + Send + 'static,
@@ -220,7 +295,40 @@ fn try_close(py: Python) -> PyResult<()> {
220295
Ok(())
221296
}
222297

223-
/// Spawn a Future onto the executor
298+
/// Spawn a Future onto the Rust executor
299+
///
300+
/// This method should be used in place of [`tokio::spawn`] when it is called on a thread that is not
301+
/// owned by the `tokio` runtime. [`tokio::spawn`] should still work fine from inside a task on the
302+
/// event loop as the current event loop is stored in Thread-Local storage.
303+
///
304+
/// # Arguments
305+
/// * `fut` - The future to spawn on the Rust event loop
306+
///
307+
/// # Examples
308+
///
309+
/// ```no_run
310+
/// # use std::time::Duration;
311+
/// # use pyo3::prelude::*;
312+
/// #
313+
/// # Python::with_gil(|py| {
314+
/// # pyo3_asyncio::with_runtime(py, || {
315+
/// # pyo3_asyncio::run_until_complete(py, async move {
316+
/// #
317+
/// pyo3_asyncio::spawn(async move {
318+
/// tokio::time::sleep(Duration::from_secs(1)).await;
319+
/// })
320+
/// .await;
321+
/// #
322+
/// # Ok(())
323+
/// # })?;
324+
/// # Ok(())
325+
/// # })
326+
/// # .map_err(|e| {
327+
/// # e.print_and_set_sys_last_vars(py);
328+
/// # })
329+
/// # .unwrap();
330+
/// # });
331+
/// ```
224332
pub fn spawn<F>(fut: F) -> JoinHandle<F::Output>
225333
where
226334
F: Future + Send + 'static,
@@ -229,7 +337,41 @@ where
229337
CURRENT_THREAD_RUNTIME.spawn(fut)
230338
}
231339

232-
/// Spawn a blocking task onto the executor
340+
/// Spawn a blocking task onto Rust the executor
341+
///
342+
/// This method should be used in place of [`tokio::task::spawn_blocking`] when it is called on a
343+
/// thread that is not owned by the `tokio` runtime. [`tokio::task::spawn_blocking`] should still
344+
/// work fine from inside a task on the event loop as the current event loop is stored in
345+
/// Thread-Local storage.
346+
///
347+
/// # Arguments
348+
/// * `func` - The blocking task to spawn on the Rust event loop
349+
///
350+
/// # Examples
351+
///
352+
/// ```no_run
353+
/// # use std::time::Duration;
354+
/// # use pyo3::prelude::*;
355+
/// #
356+
/// # Python::with_gil(|py| {
357+
/// # pyo3_asyncio::with_runtime(py, || {
358+
/// # pyo3_asyncio::run_until_complete(py, async move {
359+
/// #
360+
/// pyo3_asyncio::spawn_blocking(|| {
361+
/// std::thread::sleep(Duration::from_secs(1))
362+
/// })
363+
/// .await;
364+
/// #
365+
/// # Ok(())
366+
/// # })?;
367+
/// # Ok(())
368+
/// # })
369+
/// # .map_err(|e| {
370+
/// # e.print_and_set_sys_last_vars(py);
371+
/// # })
372+
/// # .unwrap();
373+
/// # });
374+
/// ```
233375
pub fn spawn_blocking<F, R>(func: F) -> JoinHandle<R>
234376
where
235377
F: FnOnce() -> R + Send + 'static,
@@ -264,6 +406,50 @@ impl PyTaskCompleter {
264406
}
265407

266408
/// Convert a Python coroutine into a Rust Future
409+
///
410+
/// # Arguments
411+
/// * `py` - The current PyO3 GIL guard
412+
/// * `coro` - The Python coroutine to be converted
413+
///
414+
/// # Examples
415+
///
416+
/// ```no_run
417+
/// use std::time::Duration;
418+
///
419+
/// use pyo3::prelude::*;
420+
///
421+
/// const PYTHON_CODE: &'static str = r#"
422+
/// import asyncio
423+
///
424+
/// async def py_sleep(duration):
425+
/// await asyncio.sleep(duration)
426+
/// "#;
427+
///
428+
/// async fn py_sleep(seconds: f32) -> PyResult<()> {
429+
/// let test_mod = Python::with_gil(|py| -> PyResult<PyObject> {
430+
/// Ok(
431+
/// PyModule::from_code(
432+
/// py,
433+
/// PYTHON_CODE,
434+
/// "test_into_future/test_mod.py",
435+
/// "test_mod"
436+
/// )?
437+
/// .into()
438+
/// )
439+
/// })?;
440+
///
441+
/// Python::with_gil(|py| {
442+
/// pyo3_asyncio::into_future(
443+
/// py,
444+
/// test_mod
445+
/// .call_method1(py, "py_sleep", (seconds.into_py(py),))?
446+
/// .as_ref(py),
447+
/// )
448+
/// })?
449+
/// .await?;
450+
/// Ok(())
451+
/// }
452+
/// ```
267453
pub fn into_future(
268454
py: Python,
269455
coro: &PyAny,
@@ -305,6 +491,29 @@ fn dump_err(py: Python<'_>) -> impl FnOnce(PyErr) + '_ {
305491
}
306492

307493
/// Convert a Rust Future into a Python coroutine
494+
///
495+
/// # Arguments
496+
/// * `py` - The current PyO3 GIL guard
497+
/// * `fut` - The Rust future to be converted
498+
///
499+
/// # Examples
500+
///
501+
/// ```no_run
502+
/// use std::time::Duration;
503+
///
504+
/// use pyo3::prelude::*;
505+
///
506+
/// /// Awaitable sleep function
507+
/// #[pyfunction]
508+
/// fn sleep_for(py: Python, secs: &PyAny) -> PyResult<PyObject> {
509+
/// let secs = secs.extract()?;
510+
///
511+
/// pyo3_asyncio::into_coroutine(py, async move {
512+
/// tokio::time::sleep(Duration::from_secs(secs)).await;
513+
/// Python::with_gil(|py| Ok(py.None()))
514+
/// })
515+
/// }
516+
/// ```
308517
pub fn into_coroutine<F>(py: Python, fut: F) -> PyResult<PyObject>
309518
where
310519
F: Future<Output = PyResult<PyObject>> + Send + 'static,

0 commit comments

Comments
 (0)