Skip to content

Commit 6bb629c

Browse files
author
Andrew J Westlake
committed
Wrapped up the documentation for now
1 parent fe4de1c commit 6bb629c

File tree

3 files changed

+153
-25
lines changed

3 files changed

+153
-25
lines changed

README.md

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,52 @@
55
[![crates.io](http://meritbadge.herokuapp.com/pyo3-asyncio)](https://crates.io/crates/pyo3-asyncio)
66
[![minimum rustc 1.45](https://img.shields.io/badge/rustc-1.45+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html)
77

8-
[Rust](http://www.rust-lang.org/) bindings for [Python](https://www.python.org/). This includes running and interacting with Python code from a Rust binary, as well as writing native Python modules.
8+
[Rust](http://www.rust-lang.org/) bindings for [Python](https://www.python.org/)'s [Asyncio Library](https://docs.python.org/3/library/asyncio.html). This crate facilitates interactions between Rust Futures and Python Coroutines and manages the lifecycle of their corresponding event loops.
99

1010
* API Documentation: [stable](https://docs.rs/pyo3-asyncio/) | [master](https://awestlake87.github.io/pyo3-asyncio/master/doc)
1111

1212
* Contributing Notes: [github](https://github.com/awestlake87/pyo3-asyncio/blob/master/Contributing.md)
1313

14-
## Overview
15-
16-
This project contains an example of Python 3 asyncio and Tokio interop. It's
17-
designed to allow Python complete control over the main thread and facilitate
18-
interactions between rust and python coroutines.
19-
20-
In addition, the `test` module contains a primitive test harness that will run
21-
a series of rust tests. We cannot use the default test harness because it would
22-
not allow Python to have control over the main thread. Instead we convert our
23-
test harness to an `asyncio.Future` and tell the Python `asyncio` event loop to
24-
drive the future to completion.
25-
26-
Allowing Python to have control over the main thread also allows signals to work
27-
properly. CTRL-C will exit the event loop just as you would expect.
28-
29-
Rust futures can be converted into python coroutines and vice versa. When a
30-
python coroutine raises an exception, the error should be propagated back with
31-
a `PyResult` as expected. Panics in Rust are translated into python Exceptions
32-
to propagate through the Python layers. This allows panics in tests to exit the
33-
event loop like before, but may cause unexpected behaviour if a Rust future
34-
awaits a Python coroutine that awaits a Rust future that panics.
35-
36-
> These scenarios are currently untested in this crate.
14+
## Quickstart
15+
16+
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.
17+
18+
More details on the usage of this library can be found in the [API docs](https://awestlake87.github.io/pyo3-asyncio/master/doc).
19+
20+
```rust no_run
21+
use pyo3::prelude::*;
22+
23+
fn main() {
24+
Python::with_gil(|py| {
25+
// Initialize the runtime
26+
pyo3_asyncio::with_runtime(py, || {
27+
let asyncio: PyObject = py.import("asyncio")?.into();
28+
29+
// Run the event loop until the given future completes
30+
pyo3_asyncio::run_until_complete(py, async move {
31+
Python::with_gil(|py| {
32+
// convert asyncio.sleep into a Rust Future
33+
pyo3_asyncio::into_future(
34+
py,
35+
asyncio.call_method1(
36+
py,
37+
"sleep",
38+
(1.into_py(py),)
39+
)?
40+
.as_ref(py)
41+
)
42+
})?
43+
.await?;
44+
45+
Ok(())
46+
})?;
47+
48+
Ok(())
49+
})
50+
.map_err(|e| {
51+
e.print_and_set_sys_last_vars(py);
52+
})
53+
.unwrap();
54+
})
55+
}
56+
```

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ lazy_static! {
9898
};
9999
}
100100

101-
const EXPECT_INIT: &'static str = "PyO3 Asyncio has not been initialized";
101+
const EXPECT_INIT: &str = "PyO3 Asyncio has not been initialized";
102102

103103
static ASYNCIO: OnceCell<PyObject> = OnceCell::new();
104104
static EVENT_LOOP: OnceCell<PyObject> = OnceCell::new();

src/testing.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,83 @@
1+
//! # PyO3 Asyncio Testing Utilities
2+
//!
3+
//! This module provides some utilities for parsing test arguments as well as running and filtering
4+
//! a sequence of tests.
5+
//!
6+
//! As mentioned [here](crate#pythons-event-loop), PyO3 Asyncio tests cannot use the default test
7+
//! harness since it doesn't allow Python to gain control over the main thread. Instead, we have to
8+
//! provide our own test harness in order to create integration tests.
9+
//!
10+
//! ## Creating A PyO3 Asyncio Integration Test
11+
//!
12+
//! ### Main Test File
13+
//! First, we need to create the test's main file. Although these tests are considered integration
14+
//! tests, we cannot put them in the `tests` directory since that is a special directory owned by
15+
//! Cargo. Instead, we put our tests in a `pytests` directory, although the name `pytests` is just
16+
//! a convention.
17+
//!
18+
//! `pytests/test_example.rs`
19+
//! ```no_run
20+
//!
21+
//! fn main() {
22+
//!
23+
//! }
24+
//! ```
25+
//!
26+
//! ### Test Manifest Entry
27+
//! Next, we need to add our test file to the Cargo manifest. Add the following section to your
28+
//! `Cargo.toml`
29+
//!
30+
//! ```toml
31+
//! [[test]]
32+
//! name = "test_example"
33+
//! path = "pytests/test_example.rs"
34+
//! harness = false
35+
//! ```
36+
//!
37+
//! At this point you should be able to run the test via `cargo test`
38+
//!
39+
//! ### Using the PyO3 Asyncio Test Harness
40+
//! Now that we've got our test registered with `cargo test`, we can start using the PyO3 Asyncio
41+
//! test harness.
42+
//!
43+
//! In your `Cargo.toml` add the testing feature to `pyo3-asyncio`:
44+
//! ```toml
45+
//! pyo3-asyncio = { version = "0.13", features = ["testing"] }
46+
//! ```
47+
//!
48+
//! Now, in your test's main file, call [`testing::test_main`]:
49+
//!
50+
//! ```no_run
51+
//! fn main() {
52+
//! pyo3_asyncio::testing::test_main(vec![]);
53+
//! }
54+
//! ```
55+
//!
56+
//! ### Adding Tests to the PyO3 Asyncio Test Harness
57+
//!
58+
//! ```no_run
59+
//! use pyo3_asyncio::testing::Test;
60+
//!
61+
//! fn main() {
62+
//! pyo3_asyncio::testing::test_main(vec![
63+
//! Test::new_async(
64+
//! "test_async_sleep".into(),
65+
//! async move {
66+
//! // async test body
67+
//! Ok(())
68+
//! }
69+
//! ),
70+
//! Test::new_sync(
71+
//! "test_blocking_sleep".into(),
72+
//! || {
73+
//! // blocking test body
74+
//! Ok(())
75+
//! }
76+
//! ),
77+
//! ]);
78+
//! }
79+
//! ```
80+
181
use std::{future::Future, pin::Pin};
282

383
use clap::{App, Arg};
@@ -24,6 +104,34 @@ impl Default for Args {
24104
///
25105
/// This should be called at the start of your test harness to give the CLI some
26106
/// control over how our tests are run.
107+
///
108+
/// Ideally, we should mirror the default test harness's arguments exactly, but
109+
/// for the sake of simplicity, only filtering is supported for now. If you want
110+
/// more features, feel free to request them
111+
/// [here](https://github.com/awestlake87/pyo3-asyncio/issues).
112+
///
113+
/// # Examples
114+
///
115+
/// Running the following function:
116+
/// ```no_run
117+
/// # use pyo3_asyncio::testing::parse_args;
118+
/// let args = parse_args("PyO3 Asyncio Example Test Suite");
119+
/// ```
120+
///
121+
/// Produces the following usage string:
122+
///
123+
/// ```bash
124+
/// Pyo3 Asyncio Example Test Suite
125+
/// USAGE:
126+
/// test_example [TESTNAME]
127+
///
128+
/// FLAGS:
129+
/// -h, --help Prints help information
130+
/// -V, --version Prints version information
131+
///
132+
/// ARGS:
133+
/// <TESTNAME> If specified, only run tests containing this string in their names
134+
/// ```
27135
pub fn parse_args(suite_name: &str) -> Args {
28136
let matches = App::new(suite_name)
29137
.arg(

0 commit comments

Comments
 (0)