Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialization fixes #30

Merged
merged 37 commits into from
Aug 8, 2021
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1dd3f63
Fixed async-std parity with tokio when the rust future panics
May 27, 2021
91896b8
Fixed several warnings after async-std panic parity fix
May 27, 2021
bc0f5e8
Changed conversions to accept any event loop, library no longer cache…
Jul 2, 2021
8a7aec8
Removed unnecessary lifetime on create_future
Jul 2, 2021
849fae1
Changed return type of local_future_into_py back to &PyAny
Jul 2, 2021
d4b57d2
Merged in async-std-panic-parity
Jul 2, 2021
b0972d9
Added with_loop suffix to functions with explicit event_loop parameters
Jul 2, 2021
5806195
Added implicit event loop parameter variants for the into_future conv…
Jul 2, 2021
9c4be34
Changed get_task_event_loop implementations to return None if called …
Jul 2, 2021
8c49ffd
Added new try_init/try_close get_cached_event_loop behaviour
Jul 3, 2021
536ac93
Changed into_coroutine back to using cached event loop, 0.14 equivale…
Jul 3, 2021
1306765
Added 0.13 into_future back in, makes use of get_cached_event_loop
Jul 3, 2021
2d315cf
Made tokio initialization lazy, current thread scheduler init is a li…
Jul 7, 2021
d16c75b
Added deprecation attributes to the 0.13 API calls, made event_loop p…
Jul 7, 2021
9ab2dce
Changed some names so that the 0.13 API is not broken and the naming …
Jul 7, 2021
f47b8f8
Deprecated run_forever, made run_forever tests work without deprecate…
Jul 7, 2021
8de1197
Got tests working with get_running_loop to ensure that the semantics …
Jul 7, 2021
4fffe01
Minor naming change, fixed some docs / generic impls, made async-std …
Jul 14, 2021
8b1b493
Changed get_running_loop to use asyncio.get_running_loop when availab…
Jul 14, 2021
c61ef22
Fixed crates.io badge
Jul 15, 2021
d800b90
Bumped pyo3 version to 0.14, fixed misc problems due to the auto-init…
Aug 3, 2021
ebbde13
Merged invalid-state-fix changes
Aug 6, 2021
1c4f520
Added test for running asyncio.run multiple times, found new issue th…
Aug 6, 2021
211b9d1
Merge branch 'master' of https://github.com/awestlake87/pyo3-asyncio …
Aug 6, 2021
0ffcea7
Merged in lazy tokio initialization
Aug 6, 2021
27ad604
Added PyO3 guide to README
Aug 7, 2021
ca89b5c
Added a section about Event Loop references and thread-awareness to t…
Aug 7, 2021
9daa361
Made some README changes after proofread on GitHub
Aug 7, 2021
390a785
Added example for non-standard Python event loops
Aug 7, 2021
cbeb41a
Merge branch 'master' of https://github.com/awestlake87/pyo3-asyncio …
Aug 7, 2021
4399932
Reordered README a bit to include the Quickstart in the Primer, docum…
Aug 7, 2021
c79cbc3
Forgot to add bash as language for traceback error to satisfy rustdoc
Aug 7, 2021
140db44
Added the migration guide to the README
Aug 8, 2021
dd6f2cc
Added a small note about supporting v0.13 API through v0.15
Aug 8, 2021
56eb606
Restructured the docs a bit, integrated changes from PyO3 guide PR
Aug 8, 2021
9244cc4
Changed Additional Information links to point to gh-pages docs instea…
Aug 8, 2021
f167f30
Added links to the migration guide on every deprecation note
Aug 8, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pyo3-asyncio"
description = "PyO3 utilities for Python's Asyncio library"
version = "0.13.5"
version = "0.14.0"
authors = ["Andrew J Westlake <awestlake87@yahoo.com>"]
readme = "README.md"
keywords = ["pyo3", "python", "ffi", "async", "asyncio"]
Expand Down Expand Up @@ -89,8 +89,11 @@ futures = "0.3"
inventory = "0.1"
lazy_static = "1.4"
once_cell = "1.5"
pyo3 = "0.13"
pyo3-asyncio-macros = { path = "pyo3-asyncio-macros", version = "=0.13.5", optional = true }
pyo3 = "0.14"
pyo3-asyncio-macros = { path = "pyo3-asyncio-macros", version = "=0.14.0", optional = true }

[dev-dependencies]
pyo3 = { version = "0.14", features = ["macros"] }

[dependencies.async-std]
version = "1.9"
Expand Down
517 changes: 492 additions & 25 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion examples/async_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ async fn main() -> PyResult<()> {
let asyncio = py.import("asyncio")?;

// convert asyncio.sleep into a Rust Future
pyo3_asyncio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
pyo3_asyncio::async_std::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
})?;

println!("sleeping for 1s");
Expand Down
2 changes: 1 addition & 1 deletion examples/tokio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ async fn main() -> PyResult<()> {
let asyncio = py.import("asyncio")?;

// convert asyncio.sleep into a Rust Future
pyo3_asyncio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
pyo3_asyncio::tokio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
})?;

println!("sleeping for 1s");
Expand Down
2 changes: 1 addition & 1 deletion examples/tokio_current_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ async fn main() -> PyResult<()> {
let asyncio = py.import("asyncio")?;

// convert asyncio.sleep into a Rust Future
pyo3_asyncio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
pyo3_asyncio::tokio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
})?;

println!("sleeping for 1s");
Expand Down
2 changes: 1 addition & 1 deletion examples/tokio_multi_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ async fn main() -> PyResult<()> {
let asyncio = py.import("asyncio")?;

// convert asyncio.sleep into a Rust Future
pyo3_asyncio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
pyo3_asyncio::tokio::into_future(asyncio.call_method1("sleep", (1.into_py(py),))?)
})?;

println!("sleeping for 1s");
Expand Down
2 changes: 1 addition & 1 deletion pyo3-asyncio-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "pyo3-asyncio-macros"
description = "Proc Macro Attributes for PyO3 Asyncio"
version = "0.13.5"
version = "0.14.0"
authors = ["Andrew J Westlake <awestlake87@yahoo.com>"]
readme = "../README.md"
keywords = ["pyo3", "python", "ffi", "async", "asyncio"]
Expand Down
90 changes: 70 additions & 20 deletions pyo3-asyncio-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,14 @@ pub fn async_std_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
#body
}

pyo3::Python::with_gil(|py| {
pyo3_asyncio::with_runtime(py, || {
pyo3_asyncio::async_std::run_until_complete(py, main())?;
pyo3::prepare_freethreaded_python();

Ok(())
})
.map_err(|e| {
e.print_and_set_sys_last_vars(py);
})
.unwrap();
pyo3::Python::with_gil(|py| {
pyo3_asyncio::async_std::run(py, main())
.map_err(|e| {
e.print_and_set_sys_last_vars(py);
})
.unwrap();
});
}
};
Expand Down Expand Up @@ -128,6 +126,13 @@ pub fn tokio_main(args: TokenStream, item: TokenStream) -> TokenStream {
/// thread::sleep(Duration::from_secs(1));
/// Ok(())
/// }
///
/// // blocking test functions can optionally accept an event_loop parameter
/// #[pyo3_asyncio::async_std::test]
/// fn test_blocking_sleep_with_event_loop(event_loop: PyObject) -> PyResult<()> {
/// thread::sleep(Duration::from_secs(1));
/// Ok(())
/// }
/// ```
#[cfg(not(test))] // NOTE: exporting main breaks tests, we should file an issue.
#[proc_macro_attribute]
Expand All @@ -140,15 +145,31 @@ pub fn async_std_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
let vis = &input.vis;

let fn_impl = if input.sig.asyncness.is_none() {
// Optionally pass an event_loop parameter to blocking tasks
let task = if sig.inputs.is_empty() {
quote! {
Box::pin(pyo3_asyncio::async_std::re_exports::spawn_blocking(move || {
#name()
}))
}
} else {
quote! {
let event_loop = Python::with_gil(|py| {
pyo3_asyncio::async_std::get_current_loop(py).unwrap().into()
});
Box::pin(pyo3_asyncio::async_std::re_exports::spawn_blocking(move || {
#name(event_loop)
}))
}
};

quote! {
#vis fn #name() -> std::pin::Pin<Box<dyn std::future::Future<Output = pyo3::PyResult<()>> + Send>> {
#sig {
#body
}

Box::pin(pyo3_asyncio::async_std::re_exports::spawn_blocking(move || {
#name()
}))
#task
}
}
} else {
Expand Down Expand Up @@ -204,6 +225,13 @@ pub fn async_std_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
/// thread::sleep(Duration::from_secs(1));
/// Ok(())
/// }
///
/// // blocking test functions can optionally accept an event_loop parameter
/// #[pyo3_asyncio::tokio::test]
/// fn test_blocking_sleep_with_event_loop(event_loop: PyObject) -> PyResult<()> {
/// thread::sleep(Duration::from_secs(1));
/// Ok(())
/// }
/// ```
#[cfg(not(test))] // NOTE: exporting main breaks tests, we should file an issue.
#[proc_macro_attribute]
Expand All @@ -216,14 +244,11 @@ pub fn tokio_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
let vis = &input.vis;

let fn_impl = if input.sig.asyncness.is_none() {
quote! {
#vis fn #name() -> std::pin::Pin<Box<dyn std::future::Future<Output = pyo3::PyResult<()>> + Send>> {
#sig {
#body
}

Box::pin(async {
match pyo3_asyncio::tokio::get_runtime().spawn_blocking(&#name).await {
// Optionally pass an event_loop parameter to blocking tasks
let task = if sig.inputs.is_empty() {
quote! {
Box::pin(async move {
match pyo3_asyncio::tokio::get_runtime().spawn_blocking(move || #name()).await {
Ok(result) => result,
Err(e) => {
assert!(e.is_panic());
Expand All @@ -232,6 +257,31 @@ pub fn tokio_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
}
})
}
} else {
quote! {
let event_loop = Python::with_gil(|py| {
pyo3_asyncio::tokio::get_current_loop(py).unwrap().into()
});
Box::pin(async move {
match pyo3_asyncio::tokio::get_runtime().spawn_blocking(move || #name(event_loop)).await {
Ok(result) => result,
Err(e) => {
assert!(e.is_panic());
Err(pyo3::exceptions::PyException::new_err("rust future panicked"))
}
}
})
}
};

quote! {
#vis fn #name() -> std::pin::Pin<Box<dyn std::future::Future<Output = pyo3::PyResult<()>> + Send>> {
#sig {
#body
}

#task
}
}
} else {
quote! {
Expand Down
37 changes: 20 additions & 17 deletions pyo3-asyncio-macros/src/tokio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,23 @@ fn parse_knobs(

let config = config.build()?;

let mut rt = match config.flavor {
let builder = match config.flavor {
RuntimeFlavor::CurrentThread => quote! {
pyo3_asyncio::tokio::re_exports::runtime::Builder::new_current_thread()
},
RuntimeFlavor::Threaded => quote! {
pyo3_asyncio::tokio::re_exports::runtime::Builder::new_multi_thread()
},
};

let mut builder_init = quote! {
builder.enable_all();
};
if let Some(v) = config.worker_threads {
rt = quote! { #rt.worker_threads(#v) };
builder_init = quote! {
builder.worker_threads(#v);
#builder_init;
};
}

let rt_init = match config.flavor {
Expand All @@ -247,25 +254,21 @@ fn parse_knobs(
#body
}

pyo3_asyncio::tokio::init(
#rt
.enable_all()
.build()
.unwrap()
);
pyo3::prepare_freethreaded_python();

let mut builder = #builder;
#builder_init;

pyo3_asyncio::tokio::init(builder);

#rt_init

pyo3::Python::with_gil(|py| {
pyo3_asyncio::with_runtime(py, || {
pyo3_asyncio::tokio::run_until_complete(py, main())?;

Ok(())
})
.map_err(|e| {
e.print_and_set_sys_last_vars(py);
})
.unwrap();
pyo3_asyncio::tokio::run(py, main())
.map_err(|e| {
e.print_and_set_sys_last_vars(py);
})
.unwrap();
});
}
};
Expand Down
33 changes: 21 additions & 12 deletions pytests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,24 @@ async def sleep_for_1s(sleep_for):
await sleep_for(1)
"#;

pub(super) async fn test_into_future() -> PyResult<()> {
pub(super) async fn test_into_future(event_loop: PyObject) -> PyResult<()> {
let fut = Python::with_gil(|py| {
let test_mod =
PyModule::from_code(py, TEST_MOD, "test_rust_coroutine/test_mod.py", "test_mod")?;

pyo3_asyncio::into_future_with_loop(
event_loop.as_ref(py),
test_mod.call_method1("py_sleep", (1.into_py(py),))?,
)
})?;

fut.await?;

Ok(())
}

#[allow(deprecated)]
pub(super) async fn test_into_future_0_13() -> PyResult<()> {
let fut = Python::with_gil(|py| {
let test_mod =
PyModule::from_code(py, TEST_MOD, "test_rust_coroutine/test_mod.py", "test_mod")?;
Expand All @@ -30,32 +47,24 @@ pub(super) fn test_blocking_sleep() -> PyResult<()> {
Ok(())
}

pub(super) async fn test_other_awaitables() -> PyResult<()> {
pub(super) async fn test_other_awaitables(event_loop: PyObject) -> PyResult<()> {
let fut = Python::with_gil(|py| {
let functools = py.import("functools")?;
let time = py.import("time")?;

// spawn a blocking sleep in the threadpool executor - returns a task, not a coroutine
let task = pyo3_asyncio::get_event_loop(py).call_method1(
let task = event_loop.as_ref(py).call_method1(
"run_in_executor",
(
py.None(),
functools.call_method1("partial", (time.getattr("sleep")?, 1))?,
),
)?;

pyo3_asyncio::into_future(task)
pyo3_asyncio::into_future_with_loop(event_loop.as_ref(py), task)
})?;

fut.await?;

Ok(())
}

pub(super) fn test_init_twice() -> PyResult<()> {
// try_init has already been called in test main - ensure a second call doesn't mess the other
// tests up
Python::with_gil(|py| pyo3_asyncio::try_init(py))?;

Ok(())
}
Loading