Skip to content

Commit 85ff67f

Browse files
authored
Merge pull request #3 from awestlake87/other-runtimes
Support for other runtimes (closes #2)
2 parents b1a6c0d + 4eac9ce commit 85ff67f

21 files changed

+1117
-434
lines changed

.githooks/pre-commit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#!/bin/bash
22

3-
cargo check --all-targets --features testing
3+
cargo check --all-targets --all-features

.githooks/pre-push

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#!/bin/bash
22

33
make clippy
4-
cargo test --features testing
4+
make test

.github/workflows/ci.yml

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -84,29 +84,16 @@ jobs:
8484
name: Prepare LD_LIBRARY_PATH (Ubuntu only)
8585
run: echo LD_LIBRARY_PATH=${pythonLocation}/lib >> $GITHUB_ENV
8686

87-
- name: Build docs
88-
run: cargo doc --no-default-features --features testing --verbose --target ${{ matrix.platform.rust-target }}
89-
9087
- name: Build (no features)
9188
run: cargo build --no-default-features --verbose --target ${{ matrix.platform.rust-target }}
9289

9390
- name: Build (all additive features)
94-
run: cargo build --no-default-features --features testing --verbose --target ${{ matrix.platform.rust-target }}
91+
run: cargo build --all-features --verbose --target ${{ matrix.platform.rust-target }}
9592

9693
# Run tests (except on PyPy, because no embedding API).
9794
- if: matrix.python-version != 'pypy-3.6'
9895
name: Test
99-
run: cargo test --no-default-features --features testing --target ${{ matrix.platform.rust-target }}
100-
101-
# Run tests again, but in abi3 mode
102-
- if: matrix.python-version != 'pypy-3.6'
103-
name: Test (abi3)
104-
run: cargo test --no-default-features --features testing --target ${{ matrix.platform.rust-target }}
105-
106-
# Run tests again, for abi3-py36 (the minimal Python version)
107-
- if: (matrix.python-version != 'pypy-3.6') && (matrix.python-version != '3.6')
108-
name: Test (abi3-py36)
109-
run: cargo test --no-default-features --features testing --target ${{ matrix.platform.rust-target }}
96+
run: cargo test --all-features --target ${{ matrix.platform.rust-target }}
11097

11198
- name: Install python test dependencies
11299
run: |
@@ -138,7 +125,7 @@ jobs:
138125
- uses: actions-rs/cargo@v1
139126
with:
140127
command: test
141-
args: --features testing
128+
args: --all-features
142129
env:
143130
CARGO_INCREMENTAL: 0
144131
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests"

.github/workflows/guide.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
# This adds the docs to gh-pages-build/doc
2222
- name: Build the doc
2323
run: |
24-
cargo doc --no-deps --features testing
24+
cargo doc --no-deps --all-features
2525
mkdir -p gh-pages-build
2626
cp -r target/doc gh-pages-build/doc
2727
echo "<meta http-equiv=refresh content=0;url=pyo3_asyncio/index.html>" > gh-pages-build/doc/index.html

Cargo.toml

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,74 @@
22
name = "pyo3-asyncio"
33
version = "0.13.0"
44
authors = ["Andrew J Westlake <[email protected]>"]
5+
readme = "README.md"
6+
keywords = ["pyo3", "pyo3-asyncio", "python", "cpython", "ffi", "async", "asyncio"]
7+
homepage = "https://github.com/awestlake87/pyo3-asyncio"
8+
repository = "https://github.com/awestlake87/pyo3-asyncio"
9+
documentation = "https://docs.rs/crate/pyo3-asyncio/"
10+
categories = ["api-bindings", "development-tools::ffi"]
11+
license = "Apache-2.0"
12+
exclude = ["/.gitignore", "/codecov.yml", "/Makefile"]
513
edition = "2018"
614

15+
716
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
817

918
[features]
19+
async-std-runtime = ["async-std"]
1020
testing = ["clap"]
21+
tokio-runtime = ["tokio"]
1122
default = []
1223

1324
[[test]]
14-
name = "test_asyncio"
15-
path = "pytests/test_asyncio.rs"
25+
name = "test_async_std_asyncio"
26+
path = "pytests/test_async_std_asyncio.rs"
27+
harness = false
28+
required-features = ["async-std-runtime", "testing"]
29+
30+
[[test]]
31+
name = "test_async_std_run_forever"
32+
path = "pytests/test_async_std_run_forever.rs"
1633
harness = false
17-
required-features = ["testing"]
34+
required-features = ["async-std-runtime", "testing"]
1835

1936
[[test]]
20-
name = "test_run_forever"
21-
path = "pytests/test_run_forever.rs"
37+
name = "test_tokio_current_thread_asyncio"
38+
path = "pytests/test_tokio_current_thread_asyncio.rs"
2239
harness = false
23-
required-features = ["testing"]
40+
required-features = ["tokio-runtime", "testing"]
41+
42+
[[test]]
43+
name = "test_tokio_current_thread_run_forever"
44+
path = "pytests/test_tokio_current_thread_run_forever.rs"
45+
harness = false
46+
required-features = ["tokio-runtime", "testing"]
47+
48+
[[test]]
49+
name = "test_tokio_multi_thread_asyncio"
50+
path = "pytests/test_tokio_multi_thread_asyncio.rs"
51+
harness = false
52+
required-features = ["tokio-runtime", "testing"]
53+
54+
[[test]]
55+
name = "test_tokio_multi_thread_run_forever"
56+
path = "pytests/test_tokio_multi_thread_run_forever.rs"
57+
harness = false
58+
required-features = ["tokio-runtime", "testing"]
2459

2560
[dependencies]
2661
clap = { version = "2.33", optional = true }
2762
futures = "0.3"
2863
lazy_static = "1.4"
2964
once_cell = "1.5"
3065
pyo3 = "0.13"
31-
tokio = { version = "1.0", features = ["full"] }
66+
67+
[dependencies.async-std]
68+
version = "1.9"
69+
features = ["unstable"]
70+
optional = true
71+
72+
[dependencies.tokio]
73+
version = "1.0"
74+
features = ["full"]
75+
optional = true

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ lint: fmt clippy
1212
@true
1313

1414
test: lint
15-
cargo test --features testing
15+
cargo test --all-features
1616

1717
publish: test
1818
cargo publish

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,18 @@
1313

1414
> PyO3 Asyncio is a _brand new_ part of the broader PyO3 ecosystem. Feel free to open any issues for feature requests or bugfixes for this crate.
1515
16+
## Known Problems
17+
18+
Currently, this library can give spurious failures during finalization. A solution should be released for PyO3 soon, but in the meantime you can add this patch to your `Cargo.toml` to use the master branch for PyO3
19+
20+
```toml
21+
[patch.crates-io]
22+
pyo3 = { git = "https://github.com/PyO3/pyo3" }
23+
```
24+
1625
## Quickstart
1726

18-
Here we initialize the runtime, import Python's `asyncio` library and run the given future to completion using Python's default `EventLoop` and Tokio. Inside the future, we convert `asyncio` sleep into a Rust future and await it.
27+
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.
1928

2029
More details on the usage of this library can be found in the [API docs](https://awestlake87.github.io/pyo3-asyncio/master/doc).
2130

@@ -29,7 +38,7 @@ fn main() {
2938
let asyncio: PyObject = py.import("asyncio")?.into();
3039

3140
// Run the event loop until the given future completes
32-
pyo3_asyncio::run_until_complete(py, async move {
41+
pyo3_asyncio::async_std::run_until_complete(py, async move {
3342
Python::with_gil(|py| {
3443
// convert asyncio.sleep into a Rust Future
3544
pyo3_asyncio::into_future(

pytests/common/mod.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use std::{future::Future, thread, time::Duration};
2+
3+
use pyo3::prelude::*;
4+
5+
pub(super) const TEST_MOD: &'static str = r#"
6+
import asyncio
7+
8+
async def py_sleep(duration):
9+
await asyncio.sleep(duration)
10+
11+
async def sleep_for_1s(sleep_for):
12+
await sleep_for(1)
13+
"#;
14+
15+
pub(super) fn test_into_future(
16+
py: Python,
17+
) -> PyResult<impl Future<Output = PyResult<()>> + Send + 'static> {
18+
let test_mod: PyObject =
19+
PyModule::from_code(py, TEST_MOD, "test_rust_coroutine/test_mod.py", "test_mod")?.into();
20+
21+
Ok(async move {
22+
Python::with_gil(|py| {
23+
pyo3_asyncio::into_future(
24+
py,
25+
test_mod
26+
.call_method1(py, "py_sleep", (1.into_py(py),))?
27+
.as_ref(py),
28+
)
29+
})?
30+
.await?;
31+
Ok(())
32+
})
33+
}
34+
35+
pub(super) fn test_blocking_sleep() {
36+
thread::sleep(Duration::from_secs(1));
37+
}

pytests/test_async_std_asyncio.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
mod common;
2+
3+
use std::{future::Future, time::Duration};
4+
5+
use async_std::task;
6+
use pyo3::{prelude::*, wrap_pyfunction};
7+
8+
use pyo3_asyncio::{
9+
async_std::testing::{new_sync_test, test_main},
10+
testing::Test,
11+
};
12+
13+
#[pyfunction]
14+
fn sleep_for(py: Python, secs: &PyAny) -> PyResult<PyObject> {
15+
let secs = secs.extract()?;
16+
17+
pyo3_asyncio::async_std::into_coroutine(py, async move {
18+
task::sleep(Duration::from_secs(secs)).await;
19+
Python::with_gil(|py| Ok(py.None()))
20+
})
21+
}
22+
23+
fn test_into_coroutine(
24+
py: Python,
25+
) -> PyResult<impl Future<Output = PyResult<()>> + Send + 'static> {
26+
let sleeper_mod: Py<PyModule> = PyModule::new(py, "rust_sleeper")?.into();
27+
28+
sleeper_mod
29+
.as_ref(py)
30+
.add_wrapped(wrap_pyfunction!(sleep_for))?;
31+
32+
let test_mod: PyObject = PyModule::from_code(
33+
py,
34+
common::TEST_MOD,
35+
"test_rust_coroutine/test_mod.py",
36+
"test_mod",
37+
)?
38+
.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_async_sleep<'p>(
55+
py: Python<'p>,
56+
) -> PyResult<impl Future<Output = PyResult<()>> + Send + 'static> {
57+
let asyncio = PyObject::from(py.import("asyncio")?);
58+
59+
Ok(async move {
60+
task::sleep(Duration::from_secs(1)).await;
61+
62+
Python::with_gil(|py| {
63+
pyo3_asyncio::into_future(py, asyncio.as_ref(py).call_method1("sleep", (1.0,))?)
64+
})?
65+
.await?;
66+
67+
Ok(())
68+
})
69+
}
70+
71+
fn main() {
72+
test_main(
73+
"PyO3 Asyncio Test Suite",
74+
vec![
75+
Test::new_async(
76+
"test_async_sleep".into(),
77+
Python::with_gil(|py| {
78+
test_async_sleep(py)
79+
.map_err(|e| {
80+
e.print_and_set_sys_last_vars(py);
81+
})
82+
.unwrap()
83+
}),
84+
),
85+
new_sync_test("test_blocking_sleep".into(), || {
86+
common::test_blocking_sleep();
87+
Ok(())
88+
}),
89+
Test::new_async(
90+
"test_into_coroutine".into(),
91+
Python::with_gil(|py| {
92+
test_into_coroutine(py)
93+
.map_err(|e| {
94+
e.print_and_set_sys_last_vars(py);
95+
})
96+
.unwrap()
97+
}),
98+
),
99+
Test::new_async(
100+
"test_into_future".into(),
101+
Python::with_gil(|py| {
102+
common::test_into_future(py)
103+
.map_err(|e| {
104+
e.print_and_set_sys_last_vars(py);
105+
})
106+
.unwrap()
107+
}),
108+
),
109+
],
110+
)
111+
}

pytests/test_async_std_run_forever.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use std::time::Duration;
2+
3+
use pyo3::prelude::*;
4+
5+
fn dump_err(py: Python<'_>) -> impl FnOnce(PyErr) + '_ {
6+
move |e| {
7+
// We can't display Python exceptions via std::fmt::Display,
8+
// so print the error here manually.
9+
e.print_and_set_sys_last_vars(py);
10+
}
11+
}
12+
13+
fn main() {
14+
Python::with_gil(|py| {
15+
pyo3_asyncio::with_runtime(py, || {
16+
async_std::task::spawn(async move {
17+
async_std::task::sleep(Duration::from_secs(1)).await;
18+
19+
Python::with_gil(|py| {
20+
let event_loop = pyo3_asyncio::get_event_loop(py);
21+
22+
event_loop
23+
.call_method1(
24+
"call_soon_threadsafe",
25+
(event_loop.getattr("stop").map_err(dump_err(py)).unwrap(),),
26+
)
27+
.map_err(dump_err(py))
28+
.unwrap();
29+
})
30+
});
31+
32+
pyo3_asyncio::run_forever(py)?;
33+
34+
println!("test test_run_forever ... ok");
35+
Ok(())
36+
})
37+
.map_err(dump_err(py))
38+
.unwrap();
39+
})
40+
}

0 commit comments

Comments
 (0)