Skip to content

Commit f6fc938

Browse files
author
Andrew J Westlake
committed
Added docs for PyO3 native modules
1 parent bf903ad commit f6fc938

File tree

3 files changed

+147
-3
lines changed

3 files changed

+147
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,4 @@ optional = true
100100
[dependencies.tokio]
101101
version = "1.4"
102102
features = ["full"]
103-
optional = true
103+
optional = true

README.md

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ This library can give spurious failures during finalization prior to PyO3 releas
1919

2020
## Quickstart
2121

22+
### Rust Applications
2223
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.
2324

2425
More details on the usage of this library can be found in the [API docs](https://awestlake87.github.io/pyo3-asyncio/master/doc).
@@ -39,4 +40,143 @@ async fn main() -> PyResult<()> {
3940

4041
Ok(())
4142
}
42-
```
43+
```
44+
45+
The same application can be written to use `tokio` instead using the `#[pyo3_asyncio::tokio::main]`
46+
attribute.
47+
48+
```rust
49+
use pyo3::prelude::*;
50+
51+
#[pyo3_asyncio::tokio::main]
52+
async fn main() -> PyResult<()> {
53+
let fut = Python::with_gil(|py| {
54+
let asyncio = py.import("asyncio")?;
55+
56+
// convert asyncio.sleep into a Rust Future
57+
pyo3_asyncio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
58+
})?;
59+
60+
fut.await?;
61+
62+
Ok(())
63+
}
64+
```
65+
66+
### PyO3 Native Rust Modules
67+
68+
PyO3 Asyncio can also be used to write native modules with async functions.
69+
70+
Add the `[lib]` section to `Cargo.toml` to make your library a `cdylib` that Python can import.
71+
```toml
72+
[lib]
73+
name = "my_async_module"
74+
crate-type = ["cdylib"]
75+
```
76+
77+
Make your project depend on `pyo3` with the `extension-module` feature enabled and select your
78+
`pyo3-asyncio` runtime:
79+
80+
For `async-std`:
81+
```toml
82+
[lib]
83+
name = "my_async_module"
84+
crate-type = ["cdylib"]
85+
86+
[dependencies]
87+
pyo3 = { version = "0.13", features = ["extension-module"] }
88+
pyo3-asyncio = { version = "0.13", features = ["async-std-runtime"] }
89+
async-std = "1.9"
90+
```
91+
92+
For `tokio`:
93+
```toml
94+
[lib]
95+
name = "my_async_module"
96+
crate-type = ["cdylib"]
97+
98+
[dependencies]
99+
pyo3 = { version = "0.13", features = ["extension-module"] }
100+
pyo3-asyncio = { version = "0.13", features = ["tokio-runtime"] }
101+
tokio = "1.4"
102+
```
103+
104+
Export an async function that makes use of `async-std`:
105+
106+
```rust
107+
//! lib.rs
108+
109+
use pyo3::{prelude::*, wrap_pyfunction};
110+
111+
#[pyfunction]
112+
fn rust_sleep(py: Python) -> PyResult<PyObject> {
113+
pyo3_asyncio::async_std::into_coroutine(py, async {
114+
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
115+
Ok(Python::with_gil(|py| py.None()))
116+
})
117+
}
118+
119+
#[pymodule]
120+
fn my_async_module(py: Python, m: &PyModule) -> PyResult<()> {
121+
pyo3_asyncio::try_init(py)?;
122+
123+
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
124+
125+
Ok(())
126+
}
127+
128+
```
129+
130+
If you want to use `tokio` instead, here's what your module should look like:
131+
132+
```rust
133+
//! lib.rs
134+
135+
use pyo3::{prelude::*, wrap_pyfunction};
136+
137+
#[pyfunction]
138+
fn rust_sleep(py: Python) -> PyResult<PyObject> {
139+
pyo3_asyncio::tokio::into_coroutine(py, async {
140+
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
141+
Ok(Python::with_gil(|py| py.None()))
142+
})
143+
}
144+
145+
#[pymodule]
146+
fn my_async_module(py: Python, m: &PyModule) -> PyResult<()> {
147+
pyo3_asyncio::try_init(py)?;
148+
// Tokio needs explicit initialization before any pyo3-asyncio conversions.
149+
// The module import is a prime place to do this.
150+
pyo3_asyncio::tokio::init_multi_thread_once();
151+
152+
m.add_function(wrap_pyfunction!(rust_sleep, m)?)?;
153+
154+
Ok(())
155+
}
156+
157+
```
158+
159+
Build your module and rename `libmy_async_module.so` to `my_async_module.so`
160+
```bash
161+
cargo build --release && mv target/release/libmy_async_module.so target/release/my_async_module.so
162+
```
163+
164+
Now, point your `PYTHONPATH` to the directory containing `my_async_module.so`, then you'll be able
165+
to import and use it:
166+
167+
```bash
168+
$ PYTHONPATH=target/release python3
169+
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
170+
[GCC 9.3.0] on linux
171+
Type "help", "copyright", "credits" or "license" for more information.
172+
>>> import asyncio
173+
>>> from my_async_module import rust_sleep
174+
>>>
175+
>>> # should sleep for 1s
176+
>>> asyncio.get_event_loop().run_until_complete(rust_sleep())
177+
>>>
178+
```
179+
180+
> 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.
181+
>
182+
> This restriction may be lifted in a future release.

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,11 @@ pub mod doc_test {
134134
};
135135
}
136136

137-
#[cfg(all(feature = "async-std-runtime", feature = "attributes"))]
137+
#[cfg(all(
138+
feature = "async-std-runtime",
139+
feature = "tokio-runtime",
140+
feature = "attributes"
141+
))]
138142
doctest!("../README.md", readme_md);
139143
}
140144

0 commit comments

Comments
 (0)