From 398ed3086d58b2c806dc577de0deaa44d39bb1db Mon Sep 17 00:00:00 2001 From: Andrew J Westlake Date: Fri, 10 Nov 2023 19:50:17 -0600 Subject: [PATCH] Bump PyO3 to 0.20 (#110) * tokio_test: add panic message into exception string * test adding module to export panic type * get compiling * fix macros build * Fix some stuff * fix build for async_std and tokio * Bump PyO3 to 0.20 * Fixed compilation errors in panic message change * Bumped MSRV * Updated docs --------- Co-authored-by: Chris Gregory Co-authored-by: Giovanni Barillari --- .github/workflows/ci.yml | 5 +-- Cargo.toml | 8 ++-- README.md | 26 +++++------ pyo3-asyncio-macros/Cargo.toml | 4 +- pyo3-asyncio-macros/src/lib.rs | 20 ++++++++- src/async_std.rs | 5 ++- src/generic.rs | 81 ++++++++++++++++++++++++++++------ src/lib.rs | 14 ++++-- src/tokio.rs | 5 ++- 9 files changed, 123 insertions(+), 45 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b97eafb..9838b91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,7 @@ jobs: platform: { os: "windows-latest", python-architecture: "x86" } include: # Test minimal supported Rust version - - rust: 1.62.0 + - rust: 1.63.0 python-version: "3.10" platform: { @@ -155,9 +155,6 @@ jobs: env: RUST_BACKTRACE: 1 RUSTFLAGS: "-D warnings" - # TODO: this is a hack to workaround compile_error! warnings about auto-initialize on PyPy - # Once cargo's `resolver = "2"` is stable (~ MSRV Rust 1.52), remove this. - PYO3_CI: 1 coverage: needs: [fmt] diff --git a/Cargo.toml b/Cargo.toml index 32c4ef6..7427320 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pyo3-asyncio" description = "PyO3 utilities for Python's Asyncio library" -version = "0.19.0" +version = "0.20.0" authors = ["Andrew J Westlake "] readme = "README.md" keywords = ["pyo3", "python", "ffi", "async", "asyncio"] @@ -116,11 +116,11 @@ futures = "0.3" inventory = { version = "0.3", optional = true } once_cell = "1.14" pin-project-lite = "0.2" -pyo3 = "0.19" -pyo3-asyncio-macros = { path = "pyo3-asyncio-macros", version = "=0.19.0", optional = true } +pyo3 = "0.20" +pyo3-asyncio-macros = { path = "pyo3-asyncio-macros", version = "=0.20.0", optional = true } [dev-dependencies] -pyo3 = { version = "0.19", features = ["macros"] } +pyo3 = { version = "0.20", features = ["macros"] } [dependencies.async-std] version = "1.12" diff --git a/README.md b/README.md index 8389021..cd6cdb3 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Actions Status](https://github.com/awestlake87/pyo3-asyncio/workflows/CI/badge.svg)](https://github.com/awestlake87/pyo3-asyncio/actions) [![codecov](https://codecov.io/gh/awestlake87/pyo3-asyncio/branch/master/graph/badge.svg)](https://codecov.io/gh/awestlake87/pyo3-asyncio) [![crates.io](https://img.shields.io/crates/v/pyo3-asyncio)](https://crates.io/crates/pyo3-asyncio) -[![minimum rustc 1.62](https://img.shields.io/badge/rustc-1.62+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) +[![minimum rustc 1.63](https://img.shields.io/badge/rustc-1.63+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) [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. @@ -52,8 +52,8 @@ Here we initialize the runtime, import Python's `asyncio` library and run the gi ```toml # Cargo.toml dependencies [dependencies] -pyo3 = { version = "0.19" } -pyo3-asyncio = { version = "0.19", features = ["attributes", "async-std-runtime"] } +pyo3 = { version = "0.20" } +pyo3-asyncio = { version = "0.20", features = ["attributes", "async-std-runtime"] } async-std = "1.9" ``` @@ -82,8 +82,8 @@ attribute. ```toml # Cargo.toml dependencies [dependencies] -pyo3 = { version = "0.19" } -pyo3-asyncio = { version = "0.19", features = ["attributes", "tokio-runtime"] } +pyo3 = { version = "0.20" } +pyo3-asyncio = { version = "0.20", features = ["attributes", "tokio-runtime"] } tokio = "1.9" ``` @@ -127,8 +127,8 @@ For `async-std`: ```toml [dependencies] -pyo3 = { version = "0.19", features = ["extension-module"] } -pyo3-asyncio = { version = "0.19", features = ["async-std-runtime"] } +pyo3 = { version = "0.20", features = ["extension-module"] } +pyo3-asyncio = { version = "0.20", features = ["async-std-runtime"] } async-std = "1.9" ``` @@ -136,8 +136,8 @@ For `tokio`: ```toml [dependencies] -pyo3 = { version = "0.19", features = ["extension-module"] } -pyo3-asyncio = { version = "0.19", features = ["tokio-runtime"] } +pyo3 = { version = "0.20", features = ["extension-module"] } +pyo3-asyncio = { version = "0.20", features = ["tokio-runtime"] } tokio = "1.9" ``` @@ -431,8 +431,8 @@ name = "my_async_module" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.19", features = ["extension-module"] } -pyo3-asyncio = { version = "0.19", features = ["tokio-runtime"] } +pyo3 = { version = "0.20", features = ["extension-module"] } +pyo3-asyncio = { version = "0.20", features = ["tokio-runtime"] } async-std = "1.9" tokio = "1.9" ``` @@ -491,8 +491,8 @@ event loop before we can install the `uvloop` policy. ```toml [dependencies] async-std = "1.9" -pyo3 = "0.19" -pyo3-asyncio = { version = "0.19", features = ["async-std-runtime"] } +pyo3 = "0.20" +pyo3-asyncio = { version = "0.20", features = ["async-std-runtime"] } ``` ```rust no_run diff --git a/pyo3-asyncio-macros/Cargo.toml b/pyo3-asyncio-macros/Cargo.toml index 367dae0..1398887 100644 --- a/pyo3-asyncio-macros/Cargo.toml +++ b/pyo3-asyncio-macros/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pyo3-asyncio-macros" description = "Proc Macro Attributes for PyO3 Asyncio" -version = "0.19.0" +version = "0.20.0" authors = ["Andrew J Westlake "] readme = "../README.md" keywords = ["pyo3", "python", "ffi", "async", "asyncio"] @@ -18,4 +18,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" quote = "1" -syn = { version = "1", features = ["full"] } \ No newline at end of file +syn = { version = "1", features = ["full"] } diff --git a/pyo3-asyncio-macros/src/lib.rs b/pyo3-asyncio-macros/src/lib.rs index 9574cf2..57b6c4a 100644 --- a/pyo3-asyncio-macros/src/lib.rs +++ b/pyo3-asyncio-macros/src/lib.rs @@ -250,7 +250,15 @@ pub fn tokio_test(_attr: TokenStream, item: TokenStream) -> TokenStream { Ok(result) => result, Err(e) => { assert!(e.is_panic()); - Err(pyo3::exceptions::PyException::new_err("rust future panicked")) + let panic = e.into_panic(); + let panic_message = if let Some(s) = panic.downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = panic.downcast_ref::() { + s.clone() + } else { + "unknown error".into() + }; + Err(pyo3_asyncio::err::RustPanic::new_err(format!("rust future panicked: {}", panic_message))) } } }) @@ -265,7 +273,15 @@ pub fn tokio_test(_attr: TokenStream, item: TokenStream) -> TokenStream { Ok(result) => result, Err(e) => { assert!(e.is_panic()); - Err(pyo3::exceptions::PyException::new_err("rust future panicked")) + let panic = e.into_panic(); + let panic_message = if let Some(s) = panic.downcast_ref::<&str>() { + s.to_string() + } else if let Some(s) = panic.downcast_ref::() { + s.clone() + } else { + "unknown error".into() + }; + Err(pyo3_asyncio::err::RustPanic::new_err(format!("rust future panicked: {}", panic_message))) } } }) diff --git a/src/async_std.rs b/src/async_std.rs index 4f47bd8..c8d0861 100644 --- a/src/async_std.rs +++ b/src/async_std.rs @@ -9,7 +9,7 @@ //! //! ```toml //! [dependencies.pyo3-asyncio] -//! version = "0.19" +//! version = "0.20" //! features = ["unstable-streams"] //! ``` @@ -48,6 +48,9 @@ impl JoinError for AsyncStdJoinErr { fn is_panic(&self) -> bool { true } + fn into_panic(self) -> Box { + self.0 + } } async_std::task_local! { diff --git a/src/generic.rs b/src/generic.rs index 1d5be01..3afc462 100644 --- a/src/generic.rs +++ b/src/generic.rs @@ -9,7 +9,7 @@ //! //! ```toml //! [dependencies.pyo3-asyncio] -//! version = "0.19" +//! version = "0.20" //! features = ["unstable-streams"] //! ``` @@ -38,6 +38,8 @@ use crate::{ pub trait JoinError { /// Check if the spawned task exited because of a panic fn is_panic(&self) -> bool; + /// Get the panic object associated with the error. Panics if `is_panic` is not true. + fn into_panic(self) -> Box; } /// Generic Rust async/await runtime @@ -120,7 +122,7 @@ where /// # Examples /// /// ```no_run -/// # use std::{task::{Context, Poll}, pin::Pin, future::Future}; +/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_asyncio::{ /// # TaskLocals, @@ -133,6 +135,9 @@ where /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; @@ -221,7 +226,7 @@ where /// # Examples /// /// ```no_run -/// # use std::{task::{Context, Poll}, pin::Pin, future::Future}; +/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_asyncio::{ /// # TaskLocals, @@ -234,6 +239,9 @@ where /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; @@ -350,7 +358,7 @@ fn set_result(event_loop: &PyAny, future: &PyAny, result: PyResult) -> /// # Examples /// /// ```no_run -/// # use std::{pin::Pin, future::Future, task::{Context, Poll}, time::Duration}; +/// # use std::{any::Any, pin::Pin, future::Future, task::{Context, Poll}, time::Duration}; /// # /// # use pyo3::prelude::*; /// # @@ -365,6 +373,9 @@ fn set_result(event_loop: &PyAny, future: &PyAny, result: PyResult) -> /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; @@ -474,7 +485,7 @@ where /// # Examples /// /// ```no_run -/// # use std::{task::{Context, Poll}, pin::Pin, future::Future}; +/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_asyncio::{ /// # TaskLocals, @@ -487,6 +498,9 @@ where /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; @@ -609,10 +623,14 @@ where return; } + let panic_message = format!( + "rust future panicked: {}", + get_panic_message(&e.into_panic()) + ); let _ = set_result( locals.event_loop.as_ref(py), future_tx2.as_ref(py), - Err(RustPanic::new_err("rust future panicked")), + Err(RustPanic::new_err(panic_message)), ) .map_err(dump_err(py)); }); @@ -623,6 +641,16 @@ where Ok(py_fut) } +fn get_panic_message(any: &dyn std::any::Any) -> &str { + if let Some(str_slice) = any.downcast_ref::<&str>() { + str_slice + } else if let Some(string) = any.downcast_ref::() { + string + } else { + "unknown error" + } +} + pin_project! { /// Future returned by [`timeout`](timeout) and [`timeout_at`](timeout_at). #[must_use = "futures do nothing unless you `.await` or poll them"] @@ -728,7 +756,7 @@ impl PyDoneCallback { /// # Examples /// /// ```no_run -/// # use std::{task::{Context, Poll}, pin::Pin, future::Future}; +/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_asyncio::{ /// # TaskLocals, @@ -741,6 +769,9 @@ impl PyDoneCallback { /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; @@ -834,7 +865,7 @@ where /// # Examples /// /// ```no_run -/// # use std::{task::{Context, Poll}, pin::Pin, future::Future}; +/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_asyncio::{ /// # TaskLocals, @@ -847,6 +878,9 @@ where /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; @@ -993,10 +1027,14 @@ where return; } + let panic_message = format!( + "rust future panicked: {}", + get_panic_message(&e.into_panic()) + ); let _ = set_result( locals.event_loop.as_ref(py), future_tx2.as_ref(py), - Err(RustPanic::new_err("Rust future panicked")), + Err(RustPanic::new_err(panic_message)), ) .map_err(dump_err(py)); }); @@ -1031,7 +1069,7 @@ where /// # Examples /// /// ```no_run -/// # use std::{task::{Context, Poll}, pin::Pin, future::Future}; +/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_asyncio::{ /// # TaskLocals, @@ -1044,6 +1082,9 @@ where /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; @@ -1148,7 +1189,7 @@ where /// /// # Examples /// ```no_run -/// # use std::{task::{Context, Poll}, pin::Pin, future::Future}; +/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_asyncio::{ /// # TaskLocals, @@ -1161,6 +1202,9 @@ where /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; @@ -1292,7 +1336,7 @@ where /// /// # Examples /// ```no_run -/// # use std::{task::{Context, Poll}, pin::Pin, future::Future}; +/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_asyncio::{ /// # TaskLocals, @@ -1305,6 +1349,9 @@ where /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; @@ -1496,7 +1543,7 @@ async def forward(gen, sender): /// /// # Examples /// ```no_run -/// # use std::{task::{Context, Poll}, pin::Pin, future::Future}; +/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_asyncio::{ /// # TaskLocals, @@ -1509,6 +1556,9 @@ async def forward(gen, sender): /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; @@ -1641,7 +1691,7 @@ where /// /// # Examples /// ```no_run -/// # use std::{task::{Context, Poll}, pin::Pin, future::Future}; +/// # use std::{any::Any, task::{Context, Poll}, pin::Pin, future::Future}; /// # /// # use pyo3_asyncio::{ /// # TaskLocals, @@ -1654,6 +1704,9 @@ where /// # fn is_panic(&self) -> bool { /// # unreachable!() /// # } +/// # fn into_panic(self) -> Box<(dyn Any + Send + 'static)> { +/// # unreachable!() +/// # } /// # } /// # /// # struct MyCustomJoinHandle; diff --git a/src/lib.rs b/src/lib.rs index 8b92ec6..1570522 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -300,7 +300,7 @@ //! //! ```toml //! [dependencies.pyo3-asyncio] -//! version = "0.19" +//! version = "0.20" //! features = ["attributes"] //! ``` //! @@ -313,7 +313,7 @@ //! //! ```toml //! [dependencies.pyo3-asyncio] -//! version = "0.19" +//! version = "0.20" //! features = ["async-std-runtime"] //! ``` //! @@ -326,7 +326,7 @@ //! //! ```toml //! [dependencies.pyo3-asyncio] -//! version = "0.19" +//! version = "0.20" //! features = ["tokio-runtime"] //! ``` //! @@ -339,7 +339,7 @@ //! //! ```toml //! [dependencies.pyo3-asyncio] -//! version = "0.19" +//! version = "0.20" //! features = ["testing"] //! ``` @@ -362,6 +362,12 @@ pub mod err; pub mod generic; +#[pymodule] +fn pyo3_asyncio(py: Python, m: &PyModule) -> PyResult<()> { + m.add("RustPanic", py.get_type::())?; + Ok(()) +} + /// Test README #[doc(hidden)] pub mod doc_test { diff --git a/src/tokio.rs b/src/tokio.rs index 915a2f4..804b2a6 100644 --- a/src/tokio.rs +++ b/src/tokio.rs @@ -9,7 +9,7 @@ //! //! ```toml //! [dependencies.pyo3-asyncio] -//! version = "0.19" +//! version = "0.20" //! features = ["unstable-streams"] //! ``` @@ -72,6 +72,9 @@ impl generic::JoinError for task::JoinError { fn is_panic(&self) -> bool { task::JoinError::is_panic(self) } + fn into_panic(self) -> Box { + task::JoinError::into_panic(self) + } } struct TokioRuntime;