Skip to content

Commit 4399932

Browse files
author
Andrew J Westlake
committed
Reordered README a bit to include the Quickstart in the Primer, documented caveat for asyncio.run
1 parent cbeb41a commit 4399932

File tree

1 file changed

+65
-19
lines changed

1 file changed

+65
-19
lines changed

README.md

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,25 @@
2121

2222
This library can give spurious failures during finalization prior to PyO3 release `v0.13.2`. Make sure your PyO3 dependency is up-to-date!
2323

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+
2438
## Quickstart
2539

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+
2643
### Rust Applications
2744
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.
2845

@@ -177,31 +194,17 @@ Python 3.8.5 (default, Jan 27 2021, 15:41:15)
177194
[GCC 9.3.0] on linux
178195
Type "help", "copyright", "credits" or "license" for more information.
179196
>>> import asyncio
197+
>>>
180198
>>> from my_async_module import rust_sleep
181199
>>>
200+
>>> async def main():
201+
>>> await rust_sleep()
202+
>>>
182203
>>> # should sleep for 1s
183-
>>> asyncio.get_event_loop().run_until_complete(rust_sleep())
204+
>>> asyncio.run(main())
184205
>>>
185206
```
186207

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-
205208
## Awaiting an Async Python Function in Rust
206209

207210
Let's take a look at a dead simple async Python function:
@@ -361,6 +364,49 @@ async fn main() -> PyResult<()> {
361364
}
362365
```
363366
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+
364410
### Non-standard Python Event Loops
365411

366412
Python allows you to use alternatives to the default `asyncio` event loop. One

0 commit comments

Comments
 (0)