|
21 | 21 |
|
22 | 22 | This library can give spurious failures during finalization prior to PyO3 release `v0.13.2`. Make sure your PyO3 dependency is up-to-date!
|
23 | 23 |
|
| 24 | +## PyO3 Asyncio Primer |
| 25 | + |
| 26 | +If you are working with a Python library that makes use of async functions or wish to provide |
| 27 | +Python bindings for an async Rust library, [`pyo3-asyncio`](https://github.com/awestlake87/pyo3-asyncio) |
| 28 | +likely has the tools you need. It provides conversions between async functions in both Python and |
| 29 | +Rust and was designed with first-class support for popular Rust runtimes such as |
| 30 | +[`tokio`](https://tokio.rs/) and [`async-std`](https://async.rs/). In addition, all async Python |
| 31 | +code runs on the default `asyncio` event loop, so `pyo3-asyncio` should work just fine with existing |
| 32 | +Python libraries. |
| 33 | + |
| 34 | +In the following sections, we'll give a general overview of `pyo3-asyncio` explaining how to call |
| 35 | +async Python functions with PyO3, how to call async Rust functions from Python, and how to configure |
| 36 | +your codebase to manage the runtimes of both. |
| 37 | + |
24 | 38 | ## Quickstart
|
25 | 39 |
|
| 40 | +Here are some examples to get you started right away! A more detailed breakdown |
| 41 | +of the concepts in these examples can be found in the following sections. |
| 42 | + |
26 | 43 | ### Rust Applications
|
27 | 44 | Here we initialize the runtime, import Python's `asyncio` library and run the given future to completion using Python's default `EventLoop` and `async-std`. Inside the future, we convert `asyncio` sleep into a Rust future and await it.
|
28 | 45 |
|
@@ -177,31 +194,17 @@ Python 3.8.5 (default, Jan 27 2021, 15:41:15)
|
177 | 194 | [GCC 9.3.0] on linux
|
178 | 195 | Type "help", "copyright", "credits" or "license" for more information.
|
179 | 196 | >>> import asyncio
|
| 197 | +>>> |
180 | 198 | >>> from my_async_module import rust_sleep
|
181 | 199 | >>>
|
| 200 | +>>> async def main(): |
| 201 | +>>> await rust_sleep() |
| 202 | +>>> |
182 | 203 | >>> # should sleep for 1s
|
183 |
| ->>> asyncio.get_event_loop().run_until_complete(rust_sleep()) |
| 204 | +>>> asyncio.run(main()) |
184 | 205 | >>>
|
185 | 206 | ```
|
186 | 207 |
|
187 |
| -> Note that we are using `EventLoop.run_until_complete` here instead of the newer `asyncio.run`. That is because `asyncio.run` will set up its own internal event loop that `pyo3_asyncio` will not be aware of. For this reason, running `pyo3_asyncio` conversions through `asyncio.run` is not currently supported. |
188 |
| -> |
189 |
| -> This restriction may be lifted in a future release. |
190 |
| -
|
191 |
| -## PyO3 Asyncio Primer |
192 |
| - |
193 |
| -If you are working with a Python library that makes use of async functions or wish to provide |
194 |
| -Python bindings for an async Rust library, [`pyo3-asyncio`](https://github.com/awestlake87/pyo3-asyncio) |
195 |
| -likely has the tools you need. It provides conversions between async functions in both Python and |
196 |
| -Rust and was designed with first-class support for popular Rust runtimes such as |
197 |
| -[`tokio`](https://tokio.rs/) and [`async-std`](https://async.rs/). In addition, all async Python |
198 |
| -code runs on the default `asyncio` event loop, so `pyo3-asyncio` should work just fine with existing |
199 |
| -Python libraries. |
200 |
| - |
201 |
| -In the following sections, we'll give a general overview of `pyo3-asyncio` explaining how to call |
202 |
| -async Python functions with PyO3, how to call async Rust functions from Python, and how to configure |
203 |
| -your codebase to manage the runtimes of both. |
204 |
| - |
205 | 208 | ## Awaiting an Async Python Function in Rust
|
206 | 209 |
|
207 | 210 | Let's take a look at a dead simple async Python function:
|
@@ -361,6 +364,49 @@ async fn main() -> PyResult<()> {
|
361 | 364 | }
|
362 | 365 | ```
|
363 | 366 |
|
| 367 | +### A Note About `asyncio.run` |
| 368 | + |
| 369 | +In Python 3.7+, the recommended way to run a top-level coroutine with `asyncio` |
| 370 | +is with `asyncio.run`. In `v0.13` we recommended against using this function due to initialization issues, but in `v0.14` it's perfectly valid to use this function... with a caveat. |
| 371 | + |
| 372 | +Since our Rust <--> Python conversions require a reference to the Python event loop, this poses a problem. Imagine we have a PyO3 Asyncio module that defines |
| 373 | +a `rust_sleep` function like in previous examples. You might rightfully assume that you can call pass this directly into `asyncio.run` like this: |
| 374 | + |
| 375 | +```python |
| 376 | +import asyncio |
| 377 | + |
| 378 | +from my_async_module import rust_sleep |
| 379 | + |
| 380 | +asyncio.run(rust_sleep()) |
| 381 | +``` |
| 382 | + |
| 383 | +You might be surprised to find out that this throws an error: |
| 384 | +``` |
| 385 | +Traceback (most recent call last): |
| 386 | + File "<stdin>", line 1, in <module> |
| 387 | +RuntimeError: no running event loop |
| 388 | +``` |
| 389 | + |
| 390 | +What's happening here is that we are calling `rust_sleep` _before_ the future is |
| 391 | +actually running on the event loop created by `asyncio.run`. This is counter-intuitive, but expected behaviour, and unfortunately there doesn't seem to be a good way of solving this problem within PyO3 Asyncio itself. |
| 392 | + |
| 393 | +However, we can make this example work with a simple workaround: |
| 394 | + |
| 395 | +```python |
| 396 | +import asyncio |
| 397 | + |
| 398 | +from my_async_module import rust_sleep |
| 399 | + |
| 400 | +# Calling main will just construct the coroutine that later calls rust_sleep. |
| 401 | +# - This ensures that rust_sleep will be called when the event loop is running, |
| 402 | +# not before. |
| 403 | +async def main(): |
| 404 | + await rust_sleep() |
| 405 | + |
| 406 | +# Run the main() coroutine at the top-level instead |
| 407 | +asyncio.run(main()) |
| 408 | +``` |
| 409 | + |
364 | 410 | ### Non-standard Python Event Loops
|
365 | 411 |
|
366 | 412 | Python allows you to use alternatives to the default `asyncio` event loop. One
|
|
0 commit comments