feat: support `async fn` in macros with coroutine implementation

This commit is contained in:
Joseph Perez 2023-10-23 14:43:12 +02:00 committed by Joseph Perez
parent abe518d164
commit 627841f1e2
No known key found for this signature in database
GPG Key ID: FE77882EF19365C5
20 changed files with 474 additions and 61 deletions

View File

@ -31,6 +31,9 @@ unindent = { version = "0.2.1", optional = true }
# support crate for multiple-pymethods feature
inventory = { version = "0.3.0", optional = true }
# coroutine implementation
futures-util = "0.3"
# crate integrations that can be added using the eponymous features
anyhow = { version = "1.0", optional = true }
chrono = { version = "0.4.25", default-features = false, optional = true }
@ -54,6 +57,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.61"
rayon = "1.6.1"
widestring = "0.5.1"
futures = "0.3.28"
[build-dependencies]
pyo3-build-config = { path = "pyo3-build-config", version = "0.21.0-dev", features = ["resolve-config"] }

View File

@ -19,6 +19,7 @@
- [Conversion traits](conversions/traits.md)]
- [Python exceptions](exception.md)
- [Calling Python from Rust](python_from_rust.md)
- [Using `async` and `await`](async-await.md)
- [GIL, mutability and object types](types.md)
- [Parallelism](parallelism.md)
- [Debugging](debugging.md)

78
guide/src/async-await.md Normal file
View File

@ -0,0 +1,78 @@
# Using `async` and `await`
*This feature is still in active development. See [the related issue](https://github.com/PyO3/pyo3/issues/1632).*
`#[pyfunction]` and `#[pymethods]` attributes also support `async fn`.
```rust
# #![allow(dead_code)]
use std::{thread, time::Duration};
use futures::channel::oneshot;
use pyo3::prelude::*;
#[pyfunction]
async fn sleep(seconds: f64, result: Option<PyObject>) -> Option<PyObject> {
let (tx, rx) = oneshot::channel();
thread::spawn(move || {
thread::sleep(Duration::from_secs_f64(seconds));
tx.send(()).unwrap();
});
rx.await.unwrap();
result
}
```
*Python awaitables instantiated with this method can only be awaited in *asyncio* context. Other Python async runtime may be supported in the future.*
## `Send + 'static` constraint
Resulting future of an `async fn` decorated by `#[pyfunction]` must be `Send + 'static` to be embedded in a Python object.
As a consequence, `async fn` parameters and return types must also be `Send + 'static`, so it is not possible to have a signature like `async fn does_not_compile(arg: &PyAny, py: Python<'_>) -> &PyAny`.
It also means that methods cannot use `&self`/`&mut self`, *but this restriction should be dropped in the future.*
## Implicit GIL holding
Even if it is not possible to pass a `py: Python<'_>` parameter to `async fn`, the GIL is still held during the execution of the future it's also the case for regular `fn` without `Python<'_>`/`&PyAny` parameter, yet the GIL is held.
It is still possible to get a `Python` marker using [`Python::with_gil`]({{#PYO3_DOCS_URL}}/pyo3/struct.Python.html#method.with_gil); because `with_gil` is reentrant and optimized, the cost will be negligible.
## Release the GIL across `.await`
There is currently no simple way to release the GIL when awaiting a future, *but solutions are currently in development*.
Here is the advised workaround for now:
```rust,ignore
use std::{future::Future, pin::{Pin, pin}, task::{Context, Poll}};
use pyo3::prelude::*;
struct AllowThreads<F>(F);
impl<F> Future for AllowThreads<F>
where
F: Future + Unpin + Send,
F::Output: Send,
{
type Output = F::Output;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let waker = cx.waker();
Python::with_gil(|gil| {
gil.allow_threads(|| pin!(&mut self.0).poll(&mut Context::from_waker(waker)))
})
}
}
```
## Cancellation
*To be implemented*
## The `Coroutine` type
To make a Rust future awaitable in Python, PyO3 defines a [`Coroutine`]({{#PYO3_DOCS_URL}}/pyo3/coroutine/struct.Coroutine.html) type, which implements the Python [coroutine protocol](https://docs.python.org/3/library/collections.abc.html#collections.abc.Coroutine). Each `coroutine.send` call is translated to `Future::poll` call, while `coroutine.throw` call reraise the exception *(this behavior will be configurable with cancellation support)*.
*The type does not yet have a public constructor until the design is finalized.*

View File

@ -1,5 +1,7 @@
# Using `async` and `await`
*`async`/`await` support is currently being integrated in PyO3. See the [dedicated documentation](../async-await.md)*
If you are working with a Python library that makes use of async functions or wish to provide
Python bindings for an async Rust library, [`pyo3-asyncio`](https://github.com/awestlake87/pyo3-asyncio)
likely has the tools you need. It provides conversions between async functions in both Python and

View File

@ -0,0 +1 @@
Support `async fn` in macros with coroutine implementation

View File

@ -228,6 +228,7 @@ pub struct FnSpec<'a> {
pub output: syn::Type,
pub convention: CallingConvention,
pub text_signature: Option<TextSignatureAttribute>,
pub asyncness: Option<syn::Token![async]>,
pub unsafety: Option<syn::Token![unsafe]>,
pub deprecations: Deprecations,
}
@ -317,6 +318,7 @@ impl<'a> FnSpec<'a> {
signature,
output: ty,
text_signature,
asyncness: sig.asyncness,
unsafety: sig.unsafety,
deprecations,
})
@ -445,7 +447,11 @@ impl<'a> FnSpec<'a> {
let func_name = &self.name;
let rust_call = |args: Vec<TokenStream>| {
quotes::map_result_into_ptr(quotes::ok_wrap(quote! { function(#self_arg #(#args),*) }))
let mut call = quote! { function(#self_arg #(#args),*) };
if self.asyncness.is_some() {
call = quote! { _pyo3::impl_::coroutine::wrap_future(#call) };
}
quotes::map_result_into_ptr(quotes::ok_wrap(call))
};
let rust_name = if let Some(cls) = cls {

View File

@ -6,7 +6,7 @@ use crate::{
deprecations::Deprecations,
method::{self, CallingConvention, FnArg},
pymethod::check_generic,
utils::{ensure_not_async_fn, get_pyo3_crate},
utils::get_pyo3_crate,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
@ -179,8 +179,6 @@ pub fn impl_wrap_pyfunction(
options: PyFunctionOptions,
) -> syn::Result<TokenStream> {
check_generic(&func.sig)?;
ensure_not_async_fn(&func.sig)?;
let PyFunctionOptions {
pass_module,
name,
@ -230,6 +228,7 @@ pub fn impl_wrap_pyfunction(
signature,
output: ty,
text_signature,
asyncness: func.sig.asyncness,
unsafety: func.sig.unsafety,
deprecations: Deprecations::new(),
};

View File

@ -2,7 +2,7 @@ use std::borrow::Cow;
use crate::attributes::{NameAttribute, RenamingRule};
use crate::method::{CallingConvention, ExtractErrorMode};
use crate::utils::{ensure_not_async_fn, PythonDoc};
use crate::utils::PythonDoc;
use crate::{
method::{FnArg, FnSpec, FnType, SelfType},
pyfunction::PyFunctionOptions,
@ -188,7 +188,6 @@ pub fn gen_py_method(
options: PyFunctionOptions,
) -> Result<GeneratedPyMethod> {
check_generic(sig)?;
ensure_not_async_fn(sig)?;
ensure_function_options_valid(&options)?;
let method = PyMethod::parse(sig, meth_attrs, options)?;
let spec = &method.spec;

View File

@ -1,6 +1,6 @@
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{punctuated::Punctuated, spanned::Spanned, Token};
use syn::{punctuated::Punctuated, Token};
use crate::attributes::{CrateAttribute, RenamingRule};
@ -137,17 +137,6 @@ impl quote::ToTokens for PythonDoc {
}
}
pub fn ensure_not_async_fn(sig: &syn::Signature) -> syn::Result<()> {
if let Some(asyncness) = &sig.asyncness {
bail_spanned!(
asyncness.span() => "`async fn` is not yet supported for Python functions.\n\n\
Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and \
Python. For more information, see https://github.com/PyO3/pyo3/issues/1632"
);
};
Ok(())
}
pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type {
while let syn::Type::Group(g) = ty {
ty = &*g.elem;

137
src/coroutine.rs Normal file
View File

@ -0,0 +1,137 @@
//! Python coroutine implementation, used notably when wrapping `async fn`
//! with `#[pyfunction]`/`#[pymethods]`.
use std::{
any::Any,
future::Future,
panic,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use futures_util::FutureExt;
use pyo3_macros::{pyclass, pymethods};
use crate::{
coroutine::waker::AsyncioWaker,
exceptions::{PyRuntimeError, PyStopIteration},
panic::PanicException,
pyclass::IterNextOutput,
types::PyIterator,
IntoPy, Py, PyAny, PyErr, PyObject, PyResult, Python,
};
mod waker;
const COROUTINE_REUSED_ERROR: &str = "cannot reuse already awaited coroutine";
type FutureOutput = Result<PyResult<PyObject>, Box<dyn Any + Send>>;
/// Python coroutine wrapping a [`Future`].
#[pyclass(crate = "crate")]
pub struct Coroutine {
future: Option<Pin<Box<dyn Future<Output = FutureOutput> + Send>>>,
waker: Option<Arc<AsyncioWaker>>,
}
impl Coroutine {
/// Wrap a future into a Python coroutine.
///
/// Coroutine `send` polls the wrapped future, ignoring the value passed
/// (should always be `None` anyway).
///
/// `Coroutine `throw` drop the wrapped future and reraise the exception passed
pub(crate) fn from_future<F, T, E>(future: F) -> Self
where
F: Future<Output = Result<T, E>> + Send + 'static,
T: IntoPy<PyObject>,
PyErr: From<E>,
{
let wrap = async move {
let obj = future.await?;
// SAFETY: GIL is acquired when future is polled (see `Coroutine::poll`)
Ok(obj.into_py(unsafe { Python::assume_gil_acquired() }))
};
Self {
future: Some(Box::pin(panic::AssertUnwindSafe(wrap).catch_unwind())),
waker: None,
}
}
fn poll(
&mut self,
py: Python<'_>,
throw: Option<PyObject>,
) -> PyResult<IterNextOutput<PyObject, PyObject>> {
// raise if the coroutine has already been run to completion
let future_rs = match self.future {
Some(ref mut fut) => fut,
None => return Err(PyRuntimeError::new_err(COROUTINE_REUSED_ERROR)),
};
// reraise thrown exception it
if let Some(exc) = throw {
self.close();
return Err(PyErr::from_value(exc.as_ref(py)));
}
// create a new waker, or try to reset it in place
if let Some(waker) = self.waker.as_mut().and_then(Arc::get_mut) {
waker.reset();
} else {
self.waker = Some(Arc::new(AsyncioWaker::new()));
}
let waker = futures_util::task::waker(self.waker.clone().unwrap());
// poll the Rust future and forward its results if ready
if let Poll::Ready(res) = future_rs.as_mut().poll(&mut Context::from_waker(&waker)) {
self.close();
return match res {
Ok(res) => Ok(IterNextOutput::Return(res?)),
Err(err) => Err(PanicException::from_panic_payload(err)),
};
}
// otherwise, initialize the waker `asyncio.Future`
if let Some(future) = self.waker.as_ref().unwrap().initialize_future(py)? {
// `asyncio.Future` must be awaited; fortunately, it implements `__iter__ = __await__`
// and will yield itself if its result has not been set in polling above
if let Some(future) = PyIterator::from_object(future).unwrap().next() {
// future has not been leaked into Python for now, and Rust code can only call
// `set_result(None)` in `ArcWake` implementation, so it's safe to unwrap
return Ok(IterNextOutput::Yield(future.unwrap().into()));
}
}
// if waker has been waken during future polling, this is roughly equivalent to
// `await asyncio.sleep(0)`, so just yield `None`.
Ok(IterNextOutput::Yield(py.None().into()))
}
}
pub(crate) fn iter_result(result: IterNextOutput<PyObject, PyObject>) -> PyResult<PyObject> {
match result {
IterNextOutput::Yield(ob) => Ok(ob),
IterNextOutput::Return(ob) => Err(PyStopIteration::new_err(ob)),
}
}
#[pymethods(crate = "crate")]
impl Coroutine {
fn send(&mut self, py: Python<'_>, _value: &PyAny) -> PyResult<PyObject> {
iter_result(self.poll(py, None)?)
}
fn throw(&mut self, py: Python<'_>, exc: PyObject) -> PyResult<PyObject> {
iter_result(self.poll(py, Some(exc))?)
}
fn close(&mut self) {
// the Rust future is dropped, and the field set to `None`
// to indicate the coroutine has been run to completion
drop(self.future.take());
}
fn __await__(self_: Py<Self>) -> Py<Self> {
self_
}
fn __next__(&mut self, py: Python<'_>) -> PyResult<IterNextOutput<PyObject, PyObject>> {
self.poll(py, None)
}
}

97
src/coroutine/waker.rs Normal file
View File

@ -0,0 +1,97 @@
use crate::sync::GILOnceCell;
use crate::types::PyCFunction;
use crate::{intern, wrap_pyfunction, Py, PyAny, PyObject, PyResult, Python};
use futures_util::task::ArcWake;
use pyo3_macros::pyfunction;
use std::sync::Arc;
/// Lazy `asyncio.Future` wrapper, implementing [`ArcWake`] by calling `Future.set_result`.
///
/// asyncio future is let uninitialized until [`initialize_future`][1] is called.
/// If [`wake`][2] is called before future initialization (during Rust future polling),
/// [`initialize_future`][1] will return `None` (it is roughly equivalent to `asyncio.sleep(0)`)
///
/// [1]: AsyncioWaker::initialize_future
/// [2]: AsyncioWaker::wake
pub struct AsyncioWaker(GILOnceCell<Option<LoopAndFuture>>);
impl AsyncioWaker {
pub(super) fn new() -> Self {
Self(GILOnceCell::new())
}
pub(super) fn reset(&mut self) {
self.0.take();
}
pub(super) fn initialize_future<'a>(&'a self, py: Python<'a>) -> PyResult<Option<&'a PyAny>> {
let init = || LoopAndFuture::new(py).map(Some);
let loop_and_future = self.0.get_or_try_init(py, init)?.as_ref();
Ok(loop_and_future.map(|LoopAndFuture { future, .. }| future.as_ref(py)))
}
}
impl ArcWake for AsyncioWaker {
fn wake_by_ref(arc_self: &Arc<Self>) {
Python::with_gil(|gil| {
if let Some(loop_and_future) = arc_self.0.get_or_init(gil, || None) {
loop_and_future
.set_result(gil)
.expect("unexpected error in coroutine waker");
}
});
}
}
struct LoopAndFuture {
event_loop: PyObject,
future: PyObject,
}
impl LoopAndFuture {
fn new(py: Python<'_>) -> PyResult<Self> {
static GET_RUNNING_LOOP: GILOnceCell<PyObject> = GILOnceCell::new();
let import = || -> PyResult<_> {
let module = py.import("asyncio")?;
Ok(module.getattr("get_running_loop")?.into())
};
let event_loop = GET_RUNNING_LOOP.get_or_try_init(py, import)?.call0(py)?;
let future = event_loop.call_method0(py, "create_future")?;
Ok(Self { event_loop, future })
}
fn set_result(&self, py: Python<'_>) -> PyResult<()> {
static RELEASE_WAITER: GILOnceCell<Py<PyCFunction>> = GILOnceCell::new();
let release_waiter = RELEASE_WAITER
.get_or_try_init(py, || wrap_pyfunction!(release_waiter, py).map(Into::into))?;
// `Future.set_result` must be called in event loop thread,
// so it requires `call_soon_threadsafe`
let call_soon_threadsafe = self.event_loop.call_method1(
py,
intern!(py, "call_soon_threadsafe"),
(release_waiter, self.future.as_ref(py)),
);
if let Err(err) = call_soon_threadsafe {
// `call_soon_threadsafe` will raise if the event loop is closed;
// instead of catching an unspecific `RuntimeError`, check directly if it's closed.
let is_closed = self.event_loop.call_method0(py, "is_closed")?;
if !is_closed.extract(py)? {
return Err(err);
}
}
Ok(())
}
}
/// Call `future.set_result` if the future is not done.
///
/// Future can be cancelled by the event loop before being waken.
/// See <https://github.com/python/cpython/blob/main/Lib/asyncio/tasks.py#L452C5-L452C5>
#[pyfunction(crate = "crate")]
fn release_waiter(future: &PyAny) -> PyResult<()> {
let done = future.call_method0(intern!(future.py(), "done"))?;
if !done.extract::<bool>()? {
future.call_method1(intern!(future.py(), "set_result"), (future.py().None(),))?;
}
Ok(())
}

View File

@ -6,6 +6,8 @@
//! APIs may may change at any time without documentation in the CHANGELOG and without
//! breaking semver guarantees.
#[cfg(feature = "macros")]
pub mod coroutine;
pub mod deprecations;
pub mod extract_argument;
pub mod freelist;

19
src/impl_/coroutine.rs Normal file
View File

@ -0,0 +1,19 @@
use crate::coroutine::Coroutine;
use crate::impl_::wrap::OkWrap;
use crate::{IntoPy, PyErr, PyObject, Python};
use std::future::Future;
/// Used to wrap the result of async `#[pyfunction]` and `#[pymethods]`.
pub fn wrap_future<F, R, T>(future: F) -> Coroutine
where
F: Future<Output = R> + Send + 'static,
R: OkWrap<T>,
T: IntoPy<PyObject>,
PyErr: From<R::Error>,
{
let future = async move {
// SAFETY: GIL is acquired when future is polled (see `Coroutine::poll`)
future.await.wrap(unsafe { Python::assume_gil_acquired() })
};
Coroutine::from_future(future)
}

View File

@ -397,6 +397,8 @@ pub mod buffer;
pub mod callback;
pub mod conversion;
mod conversions;
#[cfg(feature = "macros")]
pub mod coroutine;
#[macro_use]
#[doc(hidden)]
pub mod derive_utils;
@ -469,6 +471,7 @@ pub mod doc_test {
doctests! {
"README.md" => readme_md,
"guide/src/advanced.md" => guide_advanced_md,
"guide/src/async-await.md" => guide_async_await_md,
"guide/src/building_and_distribution.md" => guide_building_and_distribution_md,
"guide/src/building_and_distribution/multiple_python_versions.md" => guide_bnd_multiple_python_versions_md,
"guide/src/class.md" => guide_class_md,

98
tests/test_coroutine.rs Normal file
View File

@ -0,0 +1,98 @@
#![cfg(feature = "macros")]
#![cfg(not(target_arch = "wasm32"))]
use std::{task::Poll, thread, time::Duration};
use futures::{channel::oneshot, future::poll_fn};
use pyo3::{prelude::*, py_run};
#[path = "../src/tests/common.rs"]
mod common;
fn handle_windows(test: &str) -> String {
let set_event_loop_policy = r#"
import asyncio, sys
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
"#;
pyo3::unindent::unindent(set_event_loop_policy) + &pyo3::unindent::unindent(test)
}
#[test]
fn noop_coroutine() {
#[pyfunction]
async fn noop() -> usize {
42
}
Python::with_gil(|gil| {
let noop = wrap_pyfunction!(noop, gil).unwrap();
let test = "import asyncio; assert asyncio.run(noop()) == 42";
py_run!(gil, noop, &handle_windows(test));
})
}
#[test]
fn sleep_0_like_coroutine() {
#[pyfunction]
async fn sleep_0() -> usize {
let mut waken = false;
poll_fn(|cx| {
if !waken {
cx.waker().wake_by_ref();
waken = true;
return Poll::Pending;
}
Poll::Ready(42)
})
.await
}
Python::with_gil(|gil| {
let sleep_0 = wrap_pyfunction!(sleep_0, gil).unwrap();
let test = "import asyncio; assert asyncio.run(sleep_0()) == 42";
py_run!(gil, sleep_0, &handle_windows(test));
})
}
#[pyfunction]
async fn sleep(seconds: f64) -> usize {
let (tx, rx) = oneshot::channel();
thread::spawn(move || {
thread::sleep(Duration::from_secs_f64(seconds));
tx.send(42).unwrap();
});
rx.await.unwrap()
}
#[test]
fn sleep_coroutine() {
Python::with_gil(|gil| {
let sleep = wrap_pyfunction!(sleep, gil).unwrap();
let test = r#"import asyncio; assert asyncio.run(sleep(0.1)) == 42"#;
py_run!(gil, sleep, &handle_windows(test));
})
}
#[test]
fn cancelled_coroutine() {
Python::with_gil(|gil| {
let sleep = wrap_pyfunction!(sleep, gil).unwrap();
let test = r#"
import asyncio
async def main():
task = asyncio.create_task(sleep(1))
await asyncio.sleep(0)
task.cancel()
await task
asyncio.run(main())
"#;
let globals = gil.import("__main__").unwrap().dict();
globals.set_item("sleep", sleep).unwrap();
let err = gil
.run(
&pyo3::unindent::unindent(&handle_windows(test)),
Some(globals),
None,
)
.unwrap_err();
assert_eq!(err.value(gil).get_type().name().unwrap(), "CancelledError");
})
}

View File

@ -4,6 +4,8 @@ error[E0277]: the trait bound `PyDict: PyClass` is not satisfied
5 | #[pyclass(extends=PyDict)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PyClass` is not implemented for `PyDict`
|
= help: the trait `PyClass` is implemented for `TestClass`
= help: the following other types implement trait `PyClass`:
TestClass
Coroutine
= note: required for `PyDict` to implement `PyClassBaseType`
= note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -6,9 +6,6 @@ fn generic_function<T>(value: T) {}
#[pyfunction]
fn impl_trait_function(impl_trait: impl AsRef<PyAny>) {}
#[pyfunction]
async fn async_function() {}
#[pyfunction]
fn wildcard_argument(_: i32) {}

View File

@ -10,29 +10,21 @@ error: Python functions cannot have `impl Trait` arguments
7 | fn impl_trait_function(impl_trait: impl AsRef<PyAny>) {}
| ^^^^
error: `async fn` is not yet supported for Python functions.
Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and Python. For more information, see https://github.com/PyO3/pyo3/issues/1632
--> tests/ui/invalid_pyfunctions.rs:10:1
|
10 | async fn async_function() {}
| ^^^^^
error: wildcard argument names are not supported
--> tests/ui/invalid_pyfunctions.rs:13:22
--> tests/ui/invalid_pyfunctions.rs:10:22
|
13 | fn wildcard_argument(_: i32) {}
10 | fn wildcard_argument(_: i32) {}
| ^
error: destructuring in arguments is not supported
--> tests/ui/invalid_pyfunctions.rs:16:26
--> tests/ui/invalid_pyfunctions.rs:13:26
|
16 | fn destructured_argument((a, b): (i32, i32)) {}
13 | fn destructured_argument((a, b): (i32, i32)) {}
| ^^^^^^
error: required arguments after an `Option<_>` argument are ambiguous
= help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters
--> tests/ui/invalid_pyfunctions.rs:19:63
--> tests/ui/invalid_pyfunctions.rs:16:63
|
19 | fn function_with_required_after_option(_opt: Option<i32>, _x: i32) {}
16 | fn function_with_required_after_option(_opt: Option<i32>, _x: i32) {}
| ^^^

View File

@ -161,11 +161,6 @@ impl MyClass {
fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
}
#[pymethods]
impl MyClass {
async fn async_method(&self) {}
}
#[pymethods]
impl MyClass {
#[pyo3(pass_module)]

View File

@ -153,38 +153,30 @@ error: Python functions cannot have `impl Trait` arguments
161 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
| ^^^^
error: `async fn` is not yet supported for Python functions.
Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and Python. For more information, see https://github.com/PyO3/pyo3/issues/1632
--> tests/ui/invalid_pymethods.rs:166:5
|
166 | async fn async_method(&self) {}
| ^^^^^
error: `pass_module` cannot be used on Python methods
--> tests/ui/invalid_pymethods.rs:171:12
--> tests/ui/invalid_pymethods.rs:166:12
|
171 | #[pyo3(pass_module)]
166 | #[pyo3(pass_module)]
| ^^^^^^^^^^^
error: Python objects are shared, so 'self' cannot be moved out of the Python interpreter.
Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`.
--> tests/ui/invalid_pymethods.rs:177:29
--> tests/ui/invalid_pymethods.rs:172:29
|
177 | fn method_self_by_value(self) {}
172 | fn method_self_by_value(self) {}
| ^^^^
error: macros cannot be used as items in `#[pymethods]` impl blocks
= note: this was previously accepted and ignored
--> tests/ui/invalid_pymethods.rs:212:5
--> tests/ui/invalid_pymethods.rs:207:5
|
212 | macro_invocation!();
207 | macro_invocation!();
| ^^^^^^^^^^^^^^^^
error[E0119]: conflicting implementations of trait `pyo3::impl_::pyclass::PyClassNewTextSignature<TwoNew>` for type `pyo3::impl_::pyclass::PyClassImplCollector<TwoNew>`
--> tests/ui/invalid_pymethods.rs:182:1
--> tests/ui/invalid_pymethods.rs:177:1
|
182 | #[pymethods]
177 | #[pymethods]
| ^^^^^^^^^^^^
| |
| first implementation here
@ -193,9 +185,9 @@ error[E0119]: conflicting implementations of trait `pyo3::impl_::pyclass::PyClas
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0592]: duplicate definitions with name `__pymethod___new____`
--> tests/ui/invalid_pymethods.rs:182:1
--> tests/ui/invalid_pymethods.rs:177:1
|
182 | #[pymethods]
177 | #[pymethods]
| ^^^^^^^^^^^^
| |
| duplicate definitions for `__pymethod___new____`
@ -204,9 +196,9 @@ error[E0592]: duplicate definitions with name `__pymethod___new____`
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0592]: duplicate definitions with name `__pymethod_func__`
--> tests/ui/invalid_pymethods.rs:197:1
--> tests/ui/invalid_pymethods.rs:192:1
|
197 | #[pymethods]
192 | #[pymethods]
| ^^^^^^^^^^^^
| |
| duplicate definitions for `__pymethod_func__`