From 89e7801b3f0897d0e5a9893d9b3d6a63deb9bb1c Mon Sep 17 00:00:00 2001 From: Icxolu <10486322+Icxolu@users.noreply.github.com> Date: Sat, 24 Aug 2024 01:10:13 +0200 Subject: [PATCH 1/2] initial migration of the trait bounds to `IntoPyObject` --- guide/src/migration.md | 65 +++++++ src/conversions/chrono.rs | 17 +- src/conversions/std/num.rs | 10 + src/instance.rs | 61 +++--- src/prelude.rs | 2 +- src/types/any.rs | 387 ++++++++++++++++++++++++++----------- src/types/bytes.rs | 8 +- src/types/datetime.rs | 6 +- src/types/mapping.rs | 33 ++-- 9 files changed, 425 insertions(+), 164 deletions(-) diff --git a/guide/src/migration.md b/guide/src/migration.md index 8a6ffedc73c..b1dfc9e30c4 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -133,6 +133,71 @@ This is purely additional and should just extend the possible return types. +### Python API trait bounds changed +
+Click to expand + +PyO3 0.23 introduces a new unified `IntoPyObject` trait to convert Rust types into Python objects. +Notable features of this new trait: +- conversions can now return an error +- compared to `IntoPy` the generic `T` moved into an associated type, so + - there is now only one way to convert a given type + - the output type is stronger typed and may return any Python type instead of just `PyAny` +- byte collections are special handled and convert into `PyBytes` now, see [above](#macro-conversion-changed-for-byte-collections-vecu8-u8-n-and-smallvecu8-n) +- `()` (unit) is now only special handled in return position and otherwise converts into an empty `PyTuple` + +All PyO3 provided types as well as `#[pyclass]`es already implement `IntoPyObject`. Other types will +need to adapt an implementation of `IntoPyObject` to stay compatible with the Python APIs. + + +Before: +```rust +# use pyo3::prelude::*; +# #[allow(dead_code)] +struct MyPyObjectWrapper(PyObject); + +impl IntoPy for MyPyObjectWrapper { + fn into_py(self, py: Python<'_>) -> PyObject { + self.0 + } +} + +impl ToPyObject for MyPyObjectWrapper { + fn to_object(&self, py: Python<'_>) -> PyObject { + self.0.clone_ref(py) + } +} +``` + +After: +```rust +# use pyo3::prelude::*; +# #[allow(dead_code)] +# struct MyPyObjectWrapper(PyObject); + +impl<'py> IntoPyObject<'py> for MyPyObjectWrapper { + type Target = PyAny; // the Python type + type Output = Bound<'py, Self::Target>; // in most cases this will be `Bound` + type Error = std::convert::Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(self.0.into_bound(py)) + } +} + +// `ToPyObject` implementations should be converted to implementations on reference types +impl<'a, 'py> IntoPyObject<'py> for &'a MyPyObjectWrapper { + type Target = PyAny; + type Output = Borrowed<'a, 'py, Self::Target>; // `Borrowed` can be used to optimized reference counting + type Error = std::convert::Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(self.0.bind_borrowed(py)) + } +} +``` +
+ ## from 0.21.* to 0.22 ### Deprecation of `gil-refs` feature continues diff --git a/src/conversions/chrono.rs b/src/conversions/chrono.rs index 48246d827db..64ce4b23a4b 100644 --- a/src/conversions/chrono.rs +++ b/src/conversions/chrono.rs @@ -47,6 +47,7 @@ use crate::sync::GILOnceCell; use crate::types::any::PyAnyMethods; #[cfg(not(Py_LIMITED_API))] use crate::types::datetime::timezone_from_offset; +use crate::types::PyNone; #[cfg(not(Py_LIMITED_API))] use crate::types::{ timezone_utc, PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, @@ -551,12 +552,12 @@ impl FromPyObject<'_> for FixedOffset { #[cfg(Py_LIMITED_API)] check_type(ob, &DatetimeTypes::get(ob.py()).tzinfo, "PyTzInfo")?; - // Passing `()` (so Python's None) to the `utcoffset` function will only + // Passing Python's None to the `utcoffset` function will only // work for timezones defined as fixed offsets in Python. // Any other timezone would require a datetime as the parameter, and return // None if the datetime is not provided. // Trying to convert None to a PyDelta in the next line will then fail. - let py_timedelta = ob.call_method1("utcoffset", ((),))?; + let py_timedelta = ob.call_method1("utcoffset", (PyNone::get(ob.py()),))?; if py_timedelta.is_none() { return Err(PyTypeError::new_err(format!( "{:?} is not a fixed offset timezone", @@ -810,7 +811,7 @@ fn timezone_utc(py: Python<'_>) -> Bound<'_, PyAny> { #[cfg(test)] mod tests { use super::*; - use crate::{types::PyTuple, Py}; + use crate::types::PyTuple; use std::{cmp::Ordering, panic}; #[test] @@ -1318,11 +1319,11 @@ mod tests { }) } - fn new_py_datetime_ob<'py>( - py: Python<'py>, - name: &str, - args: impl IntoPy>, - ) -> Bound<'py, PyAny> { + fn new_py_datetime_ob<'py, A>(py: Python<'py>, name: &str, args: A) -> Bound<'py, PyAny> + where + A: IntoPyObject<'py, Target = PyTuple>, + A::Error: Into, + { py.import("datetime") .unwrap() .getattr(name) diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 954aebb14a3..80bc678fddf 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -46,6 +46,16 @@ macro_rules! int_fits_larger_int { } } + impl<'py> IntoPyObject<'py> for &$rust_type { + type Target = PyInt; + type Output = Bound<'py, Self::Target>; + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + (*self).into_pyobject(py) + } + } + impl FromPyObject<'_> for $rust_type { fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { let val: $larger_type = obj.extract()?; diff --git a/src/instance.rs b/src/instance.rs index 6e4e9ab23e7..ed0c133cc18 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,3 +1,4 @@ +use crate::conversion::IntoPyObject; use crate::err::{self, PyErr, PyResult}; use crate::impl_::pycell::PyClassObject; use crate::internal_tricks::ptr_from_ref; @@ -1426,9 +1427,10 @@ impl Py { /// # version(sys, py).unwrap(); /// # }); /// ``` - pub fn getattr(&self, py: Python<'_>, attr_name: N) -> PyResult + pub fn getattr<'py, N>(&self, py: Python<'py>, attr_name: N) -> PyResult where - N: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into, { self.bind(py).as_any().getattr(attr_name).map(Bound::unbind) } @@ -1455,32 +1457,40 @@ impl Py { /// # set_answer(ob, py).unwrap(); /// # }); /// ``` - pub fn setattr(&self, py: Python<'_>, attr_name: N, value: V) -> PyResult<()> + pub fn setattr<'py, N, V>(&self, py: Python<'py>, attr_name: N, value: V) -> PyResult<()> where - N: IntoPy>, - V: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + V: IntoPyObject<'py>, + N::Error: Into, + V::Error: Into, { - self.bind(py) - .as_any() - .setattr(attr_name, value.into_py(py).into_bound(py)) + self.bind(py).as_any().setattr(attr_name, value) } /// Calls the object. /// /// This is equivalent to the Python expression `self(*args, **kwargs)`. - pub fn call_bound( + pub fn call_bound<'py, N>( &self, - py: Python<'_>, - args: impl IntoPy>, - kwargs: Option<&Bound<'_, PyDict>>, - ) -> PyResult { + py: Python<'py>, + args: N, + kwargs: Option<&Bound<'py, PyDict>>, + ) -> PyResult + where + N: IntoPyObject<'py, Target = PyTuple>, + N::Error: Into, + { self.bind(py).as_any().call(args, kwargs).map(Bound::unbind) } /// Calls the object with only positional arguments. /// /// This is equivalent to the Python expression `self(*args)`. - pub fn call1(&self, py: Python<'_>, args: impl IntoPy>) -> PyResult { + pub fn call1<'py, N>(&self, py: Python<'py>, args: N) -> PyResult + where + N: IntoPyObject<'py, Target = PyTuple>, + N::Error: Into, + { self.bind(py).as_any().call1(args).map(Bound::unbind) } @@ -1497,16 +1507,18 @@ impl Py { /// /// To avoid repeated temporary allocations of Python strings, the [`intern!`](crate::intern) /// macro can be used to intern `name`. - pub fn call_method_bound( + pub fn call_method_bound<'py, N, A>( &self, - py: Python<'_>, + py: Python<'py>, name: N, args: A, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult where - N: IntoPy>, - A: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + A: IntoPyObject<'py, Target = PyTuple>, + N::Error: Into, + A::Error: Into, { self.bind(py) .as_any() @@ -1520,10 +1532,12 @@ impl Py { /// /// To avoid repeated temporary allocations of Python strings, the [`intern!`](crate::intern) /// macro can be used to intern `name`. - pub fn call_method1(&self, py: Python<'_>, name: N, args: A) -> PyResult + pub fn call_method1<'py, N, A>(&self, py: Python<'py>, name: N, args: A) -> PyResult where - N: IntoPy>, - A: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + A: IntoPyObject<'py, Target = PyTuple>, + N::Error: Into, + A::Error: Into, { self.bind(py) .as_any() @@ -1537,9 +1551,10 @@ impl Py { /// /// To avoid repeated temporary allocations of Python strings, the [`intern!`](crate::intern) /// macro can be used to intern `name`. - pub fn call_method0(&self, py: Python<'_>, name: N) -> PyResult + pub fn call_method0<'py, N>(&self, py: Python<'py>, name: N) -> PyResult where - N: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into, { self.bind(py).as_any().call_method0(name).map(Bound::unbind) } diff --git a/src/prelude.rs b/src/prelude.rs index 6182b21c2d1..b2b86c8449d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -8,7 +8,7 @@ //! use pyo3::prelude::*; //! ``` -pub use crate::conversion::{FromPyObject, IntoPy, ToPyObject}; +pub use crate::conversion::{FromPyObject, IntoPy, IntoPyObject, ToPyObject}; pub use crate::err::{PyErr, PyResult}; pub use crate::instance::{Borrowed, Bound, Py, PyObject}; pub use crate::marker::Python; diff --git a/src/types/any.rs b/src/types/any.rs index 6dff9a1264d..72030e6041a 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1,5 +1,5 @@ use crate::class::basic::CompareOp; -use crate::conversion::{AsPyPointer, FromPyObjectBound, IntoPy, ToPyObject}; +use crate::conversion::{AsPyPointer, FromPyObjectBound, IntoPyObject}; use crate::err::{DowncastError, DowncastIntoError, PyErr, PyResult}; use crate::exceptions::{PyAttributeError, PyTypeError}; use crate::ffi_ptr_ext::FfiPtrExt; @@ -10,7 +10,7 @@ use crate::type_object::{PyTypeCheck, PyTypeInfo}; #[cfg(not(any(PyPy, GraalPy)))] use crate::types::PySuper; use crate::types::{PyDict, PyIterator, PyList, PyString, PyTuple, PyType}; -use crate::{err, ffi, Py, Python}; +use crate::{err, ffi, BoundObject, Python}; use std::cell::UnsafeCell; use std::cmp::Ordering; use std::os::raw::c_int; @@ -81,7 +81,8 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// ``` fn hasattr(&self, attr_name: N) -> PyResult where - N: IntoPy>; + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into; /// Retrieves an attribute value. /// @@ -107,7 +108,8 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// ``` fn getattr(&self, attr_name: N) -> PyResult> where - N: IntoPy>; + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into; /// Sets an attribute value. /// @@ -133,8 +135,10 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// ``` fn setattr(&self, attr_name: N, value: V) -> PyResult<()> where - N: IntoPy>, - V: ToPyObject; + N: IntoPyObject<'py, Target = PyString>, + V: IntoPyObject<'py>, + N::Error: Into, + V::Error: Into; /// Deletes an attribute. /// @@ -144,7 +148,8 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// to intern `attr_name`. fn delattr(&self, attr_name: N) -> PyResult<()> where - N: IntoPy>; + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into; /// Returns an [`Ordering`] between `self` and `other`. /// @@ -194,7 +199,8 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// ``` fn compare(&self, other: O) -> PyResult where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Tests whether two Python objects obey a given [`CompareOp`]. /// @@ -232,7 +238,8 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// ``` fn rich_compare(&self, other: O, compare_op: CompareOp) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes the negative of self. /// @@ -257,114 +264,135 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// This is equivalent to the Python expression `self < other`. fn lt(&self, other: O) -> PyResult where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Tests whether this object is less than or equal to another. /// /// This is equivalent to the Python expression `self <= other`. fn le(&self, other: O) -> PyResult where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Tests whether this object is equal to another. /// /// This is equivalent to the Python expression `self == other`. fn eq(&self, other: O) -> PyResult where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Tests whether this object is not equal to another. /// /// This is equivalent to the Python expression `self != other`. fn ne(&self, other: O) -> PyResult where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Tests whether this object is greater than another. /// /// This is equivalent to the Python expression `self > other`. fn gt(&self, other: O) -> PyResult where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Tests whether this object is greater than or equal to another. /// /// This is equivalent to the Python expression `self >= other`. fn ge(&self, other: O) -> PyResult where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self + other`. fn add(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self - other`. fn sub(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self * other`. fn mul(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self @ other`. fn matmul(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self / other`. fn div(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self // other`. fn floor_div(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self % other`. fn rem(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `divmod(self, other)`. fn divmod(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self << other`. fn lshift(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self >> other`. fn rshift(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self ** other % modulus` (`pow(self, other, modulus)`). /// `py.None()` may be passed for the `modulus`. fn pow(&self, other: O1, modulus: O2) -> PyResult> where - O1: ToPyObject, - O2: ToPyObject; + O1: IntoPyObject<'py>, + O2: IntoPyObject<'py>, + O1::Error: Into, + O2::Error: Into; /// Computes `self & other`. fn bitand(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self | other`. fn bitor(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Computes `self ^ other`. fn bitxor(&self, other: O) -> PyResult> where - O: ToPyObject; + O: IntoPyObject<'py>, + O::Error: Into; /// Determines whether this object appears callable. /// @@ -427,11 +455,10 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// }) /// # } /// ``` - fn call( - &self, - args: impl IntoPy>, - kwargs: Option<&Bound<'_, PyDict>>, - ) -> PyResult>; + fn call(&self, args: A, kwargs: Option<&Bound<'_, PyDict>>) -> PyResult> + where + A: IntoPyObject<'py, Target = PyTuple>, + A::Error: Into; /// Calls the object without arguments. /// @@ -484,7 +511,10 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// }) /// # } /// ``` - fn call1(&self, args: impl IntoPy>) -> PyResult>; + fn call1(&self, args: A) -> PyResult> + where + A: IntoPyObject<'py, Target = PyTuple>, + A::Error: Into; /// Calls a method on the object. /// @@ -530,8 +560,10 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult> where - N: IntoPy>, - A: IntoPy>; + N: IntoPyObject<'py, Target = PyString>, + A: IntoPyObject<'py, Target = PyTuple>, + N::Error: Into, + A::Error: Into; /// Calls a method on the object without arguments. /// @@ -568,7 +600,8 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// ``` fn call_method0(&self, name: N) -> PyResult> where - N: IntoPy>; + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into; /// Calls a method on the object with only positional arguments. /// @@ -606,8 +639,10 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// ``` fn call_method1(&self, name: N, args: A) -> PyResult> where - N: IntoPy>, - A: IntoPy>; + N: IntoPyObject<'py, Target = PyString>, + A: IntoPyObject<'py, Target = PyTuple>, + N::Error: Into, + A::Error: Into; /// Returns whether the object is considered to be true. /// @@ -635,22 +670,26 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// This is equivalent to the Python expression `self[key]`. fn get_item(&self, key: K) -> PyResult> where - K: ToPyObject; + K: IntoPyObject<'py>, + K::Error: Into; /// Sets a collection item value. /// /// This is equivalent to the Python expression `self[key] = value`. fn set_item(&self, key: K, value: V) -> PyResult<()> where - K: ToPyObject, - V: ToPyObject; + K: IntoPyObject<'py>, + V: IntoPyObject<'py>, + K::Error: Into, + V::Error: Into; /// Deletes an item from the collection. /// /// This is equivalent to the Python expression `del self[key]`. fn del_item(&self, key: K) -> PyResult<()> where - K: ToPyObject; + K: IntoPyObject<'py>, + K::Error: Into; /// Takes an object and returns an iterator for it. /// @@ -862,7 +901,8 @@ pub trait PyAnyMethods<'py>: crate::sealed::Sealed { /// This is equivalent to the Python expression `value in self`. fn contains(&self, value: V) -> PyResult where - V: ToPyObject; + V: IntoPyObject<'py>, + V::Error: Into; /// Return a proxy object that delegates method calls to a parent or sibling class of type. /// @@ -876,17 +916,25 @@ macro_rules! implement_binop { #[doc = concat!("Computes `self ", $op, " other`.")] fn $name(&self, other: O) -> PyResult> where - O: ToPyObject, + O: IntoPyObject<'py>, + O::Error: Into, { fn inner<'py>( any: &Bound<'py, PyAny>, - other: Bound<'_, PyAny>, + other: &Bound<'_, PyAny>, ) -> PyResult> { unsafe { ffi::$c_api(any.as_ptr(), other.as_ptr()).assume_owned_or_err(any.py()) } } let py = self.py(); - inner(self, other.to_object(py).into_bound(py)) + inner( + self, + &other + .into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), + ) } }; } @@ -899,7 +947,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn hasattr(&self, attr_name: N) -> PyResult where - N: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into, { // PyObject_HasAttr suppresses all exceptions, which was the behaviour of `hasattr` in Python 2. // Use an implementation which suppresses only AttributeError, which is consistent with `hasattr` in Python 3. @@ -911,16 +960,17 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { } } - inner(self.py(), self.getattr(attr_name)) + inner(self.py(), self.getattr(attr_name).map_err(Into::into)) } fn getattr(&self, attr_name: N) -> PyResult> where - N: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into, { fn inner<'py>( any: &Bound<'py, PyAny>, - attr_name: Bound<'_, PyString>, + attr_name: &Bound<'_, PyString>, ) -> PyResult> { unsafe { ffi::PyObject_GetAttr(any.as_ptr(), attr_name.as_ptr()) @@ -928,19 +978,26 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { } } - let py = self.py(); - inner(self, attr_name.into_py(self.py()).into_bound(py)) + inner( + self, + &attr_name + .into_pyobject(self.py()) + .map_err(Into::into)? + .as_borrowed(), + ) } fn setattr(&self, attr_name: N, value: V) -> PyResult<()> where - N: IntoPy>, - V: ToPyObject, + N: IntoPyObject<'py, Target = PyString>, + V: IntoPyObject<'py>, + N::Error: Into, + V::Error: Into, { fn inner( any: &Bound<'_, PyAny>, - attr_name: Bound<'_, PyString>, - value: Bound<'_, PyAny>, + attr_name: &Bound<'_, PyString>, + value: &Bound<'_, PyAny>, ) -> PyResult<()> { err::error_on_minusone(any.py(), unsafe { ffi::PyObject_SetAttr(any.as_ptr(), attr_name.as_ptr(), value.as_ptr()) @@ -950,30 +1007,45 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - attr_name.into_py(py).into_bound(py), - value.to_object(py).into_bound(py), + &attr_name + .into_pyobject(py) + .map_err(Into::into)? + .as_borrowed(), + &value + .into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), ) } fn delattr(&self, attr_name: N) -> PyResult<()> where - N: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into, { - fn inner(any: &Bound<'_, PyAny>, attr_name: Bound<'_, PyString>) -> PyResult<()> { + fn inner(any: &Bound<'_, PyAny>, attr_name: &Bound<'_, PyString>) -> PyResult<()> { err::error_on_minusone(any.py(), unsafe { ffi::PyObject_DelAttr(any.as_ptr(), attr_name.as_ptr()) }) } let py = self.py(); - inner(self, attr_name.into_py(py).into_bound(py)) + inner( + self, + &attr_name + .into_pyobject(py) + .map_err(Into::into)? + .as_borrowed(), + ) } fn compare(&self, other: O) -> PyResult where - O: ToPyObject, + O: IntoPyObject<'py>, + O::Error: Into, { - fn inner(any: &Bound<'_, PyAny>, other: Bound<'_, PyAny>) -> PyResult { + fn inner(any: &Bound<'_, PyAny>, other: &Bound<'_, PyAny>) -> PyResult { let other = other.as_ptr(); // Almost the same as ffi::PyObject_RichCompareBool, but this one doesn't try self == other. // See https://github.com/PyO3/pyo3/issues/985 for more. @@ -996,16 +1068,24 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { } let py = self.py(); - inner(self, other.to_object(py).into_bound(py)) + inner( + self, + &other + .into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), + ) } fn rich_compare(&self, other: O, compare_op: CompareOp) -> PyResult> where - O: ToPyObject, + O: IntoPyObject<'py>, + O::Error: Into, { fn inner<'py>( any: &Bound<'py, PyAny>, - other: Bound<'_, PyAny>, + other: &Bound<'_, PyAny>, compare_op: CompareOp, ) -> PyResult> { unsafe { @@ -1015,7 +1095,15 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { } let py = self.py(); - inner(self, other.to_object(py).into_bound(py), compare_op) + inner( + self, + &other + .into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), + compare_op, + ) } fn neg(&self) -> PyResult> { @@ -1048,7 +1136,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn lt(&self, other: O) -> PyResult where - O: ToPyObject, + O: IntoPyObject<'py>, + O::Error: Into, { self.rich_compare(other, CompareOp::Lt) .and_then(|any| any.is_truthy()) @@ -1056,7 +1145,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn le(&self, other: O) -> PyResult where - O: ToPyObject, + O: IntoPyObject<'py>, + O::Error: Into, { self.rich_compare(other, CompareOp::Le) .and_then(|any| any.is_truthy()) @@ -1064,7 +1154,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn eq(&self, other: O) -> PyResult where - O: ToPyObject, + O: IntoPyObject<'py>, + O::Error: Into, { self.rich_compare(other, CompareOp::Eq) .and_then(|any| any.is_truthy()) @@ -1072,7 +1163,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn ne(&self, other: O) -> PyResult where - O: ToPyObject, + O: IntoPyObject<'py>, + O::Error: Into, { self.rich_compare(other, CompareOp::Ne) .and_then(|any| any.is_truthy()) @@ -1080,7 +1172,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn gt(&self, other: O) -> PyResult where - O: ToPyObject, + O: IntoPyObject<'py>, + O::Error: Into, { self.rich_compare(other, CompareOp::Gt) .and_then(|any| any.is_truthy()) @@ -1088,7 +1181,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn ge(&self, other: O) -> PyResult where - O: ToPyObject, + O: IntoPyObject<'py>, + O::Error: Into, { self.rich_compare(other, CompareOp::Ge) .and_then(|any| any.is_truthy()) @@ -1110,11 +1204,12 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { /// Computes `divmod(self, other)`. fn divmod(&self, other: O) -> PyResult> where - O: ToPyObject, + O: IntoPyObject<'py>, + O::Error: Into, { fn inner<'py>( any: &Bound<'py, PyAny>, - other: Bound<'_, PyAny>, + other: &Bound<'_, PyAny>, ) -> PyResult> { unsafe { ffi::PyNumber_Divmod(any.as_ptr(), other.as_ptr()).assume_owned_or_err(any.py()) @@ -1122,20 +1217,29 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { } let py = self.py(); - inner(self, other.to_object(py).into_bound(py)) + inner( + self, + &other + .into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), + ) } /// Computes `self ** other % modulus` (`pow(self, other, modulus)`). /// `py.None()` may be passed for the `modulus`. fn pow(&self, other: O1, modulus: O2) -> PyResult> where - O1: ToPyObject, - O2: ToPyObject, + O1: IntoPyObject<'py>, + O2: IntoPyObject<'py>, + O1::Error: Into, + O2::Error: Into, { fn inner<'py>( any: &Bound<'py, PyAny>, - other: Bound<'_, PyAny>, - modulus: Bound<'_, PyAny>, + other: &Bound<'_, PyAny>, + modulus: &Bound<'_, PyAny>, ) -> PyResult> { unsafe { ffi::PyNumber_Power(any.as_ptr(), other.as_ptr(), modulus.as_ptr()) @@ -1146,8 +1250,16 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - other.to_object(py).into_bound(py), - modulus.to_object(py).into_bound(py), + &other + .into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), + &modulus + .into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), ) } @@ -1155,14 +1267,14 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { unsafe { ffi::PyCallable_Check(self.as_ptr()) != 0 } } - fn call( - &self, - args: impl IntoPy>, - kwargs: Option<&Bound<'_, PyDict>>, - ) -> PyResult> { + fn call(&self, args: A, kwargs: Option<&Bound<'_, PyDict>>) -> PyResult> + where + A: IntoPyObject<'py, Target = PyTuple>, + A::Error: Into, + { fn inner<'py>( any: &Bound<'py, PyAny>, - args: Bound<'_, PyTuple>, + args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult> { unsafe { @@ -1176,14 +1288,22 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { } let py = self.py(); - inner(self, args.into_py(py).into_bound(py), kwargs) + inner( + self, + &args.into_pyobject(py).map_err(Into::into)?.as_borrowed(), + kwargs, + ) } fn call0(&self) -> PyResult> { unsafe { ffi::compat::PyObject_CallNoArgs(self.as_ptr()).assume_owned_or_err(self.py()) } } - fn call1(&self, args: impl IntoPy>) -> PyResult> { + fn call1(&self, args: A) -> PyResult> + where + A: IntoPyObject<'py, Target = PyTuple>, + A::Error: Into, + { self.call(args, None) } @@ -1194,8 +1314,10 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult> where - N: IntoPy>, - A: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + A: IntoPyObject<'py, Target = PyTuple>, + N::Error: Into, + A::Error: Into, { self.getattr(name) .and_then(|method| method.call(args, kwargs)) @@ -1203,10 +1325,11 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn call_method0(&self, name: N) -> PyResult> where - N: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into, { let py = self.py(); - let name = name.into_py(py).into_bound(py); + let name = name.into_pyobject(py).map_err(Into::into)?.into_bound(); unsafe { ffi::compat::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr()) .assume_owned_or_err(py) @@ -1215,8 +1338,10 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn call_method1(&self, name: N, args: A) -> PyResult> where - N: IntoPy>, - A: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + A: IntoPyObject<'py, Target = PyTuple>, + N::Error: Into, + A::Error: Into, { self.call_method(name, args, None) } @@ -1242,11 +1367,12 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn get_item(&self, key: K) -> PyResult> where - K: ToPyObject, + K: IntoPyObject<'py>, + K::Error: Into, { fn inner<'py>( any: &Bound<'py, PyAny>, - key: Bound<'_, PyAny>, + key: &Bound<'_, PyAny>, ) -> PyResult> { unsafe { ffi::PyObject_GetItem(any.as_ptr(), key.as_ptr()).assume_owned_or_err(any.py()) @@ -1254,18 +1380,26 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { } let py = self.py(); - inner(self, key.to_object(py).into_bound(py)) + inner( + self, + &key.into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), + ) } fn set_item(&self, key: K, value: V) -> PyResult<()> where - K: ToPyObject, - V: ToPyObject, + K: IntoPyObject<'py>, + V: IntoPyObject<'py>, + K::Error: Into, + V::Error: Into, { fn inner( any: &Bound<'_, PyAny>, - key: Bound<'_, PyAny>, - value: Bound<'_, PyAny>, + key: &Bound<'_, PyAny>, + value: &Bound<'_, PyAny>, ) -> PyResult<()> { err::error_on_minusone(any.py(), unsafe { ffi::PyObject_SetItem(any.as_ptr(), key.as_ptr(), value.as_ptr()) @@ -1275,23 +1409,37 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { let py = self.py(); inner( self, - key.to_object(py).into_bound(py), - value.to_object(py).into_bound(py), + &key.into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), + &value + .into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), ) } fn del_item(&self, key: K) -> PyResult<()> where - K: ToPyObject, + K: IntoPyObject<'py>, + K::Error: Into, { - fn inner(any: &Bound<'_, PyAny>, key: Bound<'_, PyAny>) -> PyResult<()> { + fn inner(any: &Bound<'_, PyAny>, key: &Bound<'_, PyAny>) -> PyResult<()> { err::error_on_minusone(any.py(), unsafe { ffi::PyObject_DelItem(any.as_ptr(), key.as_ptr()) }) } let py = self.py(); - inner(self, key.to_object(py).into_bound(py)) + inner( + self, + &key.into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), + ) } fn iter(&self) -> PyResult> { @@ -1440,9 +1588,10 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { fn contains(&self, value: V) -> PyResult where - V: ToPyObject, + V: IntoPyObject<'py>, + V::Error: Into, { - fn inner(any: &Bound<'_, PyAny>, value: Bound<'_, PyAny>) -> PyResult { + fn inner(any: &Bound<'_, PyAny>, value: &Bound<'_, PyAny>) -> PyResult { match unsafe { ffi::PySequence_Contains(any.as_ptr(), value.as_ptr()) } { 0 => Ok(false), 1 => Ok(true), @@ -1451,7 +1600,14 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> { } let py = self.py(); - inner(self, value.to_object(py).into_bound(py)) + inner( + self, + &value + .into_pyobject(py) + .map_err(Into::into)? + .into_any() + .as_borrowed(), + ) } #[cfg(not(any(PyPy, GraalPy)))] @@ -1474,7 +1630,8 @@ impl<'py> Bound<'py, PyAny> { #[allow(dead_code)] // Currently only used with num-complex+abi3, so dead without that. pub(crate) fn lookup_special(&self, attr_name: N) -> PyResult>> where - N: IntoPy>, + N: IntoPyObject<'py, Target = PyString>, + N::Error: Into, { let py = self.py(); let self_type = self.get_type(); diff --git a/src/types/bytes.rs b/src/types/bytes.rs index 397e2eb3848..96f2c43ada6 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -38,10 +38,10 @@ use std::str; /// let other = PyBytes::new(py, b"foo".as_slice()); /// assert!(py_bytes.as_any().eq(other).unwrap()); /// -/// // Note that `eq` will convert it's argument to Python using `ToPyObject`, -/// // so the following does not compare equal since the slice will convert into a -/// // `list`, not a `bytes` object. -/// assert!(!py_bytes.as_any().eq(b"foo".as_slice()).unwrap()); +/// // Note that `eq` will convert it's argument to Python using `IntoPyObject`, +/// // byte collections are specialized, so that the following slice will indeed +/// // convert into a `bytes` object and not a `list` +/// assert!(py_bytes.as_any().eq(b"foo".as_slice()).unwrap()); /// # }); /// ``` #[repr(transparent)] diff --git a/src/types/datetime.rs b/src/types/datetime.rs index b2463bedde0..77df943fd81 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -862,11 +862,13 @@ mod tests { #[cfg(all(feature = "macros", feature = "chrono"))] #[cfg_attr(target_arch = "wasm32", ignore)] // DateTime import fails on wasm for mysterious reasons fn test_timezone_from_offset() { + use crate::types::PyNone; + Python::with_gil(|py| { assert!( timezone_from_offset(&PyDelta::new(py, 0, -3600, 0, true).unwrap()) .unwrap() - .call_method1("utcoffset", ((),)) + .call_method1("utcoffset", (PyNone::get(py),)) .unwrap() .downcast_into::() .unwrap() @@ -877,7 +879,7 @@ mod tests { assert!( timezone_from_offset(&PyDelta::new(py, 0, 3600, 0, true).unwrap()) .unwrap() - .call_method1("utcoffset", ((),)) + .call_method1("utcoffset", (PyNone::get(py),)) .unwrap() .downcast_into::() .unwrap() diff --git a/src/types/mapping.rs b/src/types/mapping.rs index 67a7b9b4d3d..df1cc8dc155 100644 --- a/src/types/mapping.rs +++ b/src/types/mapping.rs @@ -1,3 +1,4 @@ +use crate::conversion::IntoPyObject; use crate::err::PyResult; use crate::ffi_ptr_ext::FfiPtrExt; use crate::instance::Bound; @@ -6,7 +7,7 @@ use crate::sync::GILOnceCell; use crate::type_object::PyTypeInfo; use crate::types::any::PyAnyMethods; use crate::types::{PyAny, PyDict, PySequence, PyType}; -use crate::{ffi, Py, PyTypeCheck, Python, ToPyObject}; +use crate::{ffi, Py, PyErr, PyTypeCheck, Python}; /// Represents a reference to a Python object supporting the mapping protocol. /// @@ -50,7 +51,8 @@ pub trait PyMappingMethods<'py>: crate::sealed::Sealed { /// This is equivalent to the Python expression `key in self`. fn contains(&self, key: K) -> PyResult where - K: ToPyObject; + K: IntoPyObject<'py>, + K::Error: Into; /// Gets the item in self with key `key`. /// @@ -59,22 +61,26 @@ pub trait PyMappingMethods<'py>: crate::sealed::Sealed { /// This is equivalent to the Python expression `self[key]`. fn get_item(&self, key: K) -> PyResult> where - K: ToPyObject; + K: IntoPyObject<'py>, + K::Error: Into; /// Sets the item in self with key `key`. /// /// This is equivalent to the Python expression `self[key] = value`. fn set_item(&self, key: K, value: V) -> PyResult<()> where - K: ToPyObject, - V: ToPyObject; + K: IntoPyObject<'py>, + V: IntoPyObject<'py>, + K::Error: Into, + V::Error: Into; /// Deletes the item with key `key`. /// /// This is equivalent to the Python statement `del self[key]`. fn del_item(&self, key: K) -> PyResult<()> where - K: ToPyObject; + K: IntoPyObject<'py>, + K::Error: Into; /// Returns a sequence containing all keys in the mapping. fn keys(&self) -> PyResult>; @@ -101,7 +107,8 @@ impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> { fn contains(&self, key: K) -> PyResult where - K: ToPyObject, + K: IntoPyObject<'py>, + K::Error: Into, { PyAnyMethods::contains(&**self, key) } @@ -109,7 +116,8 @@ impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> { #[inline] fn get_item(&self, key: K) -> PyResult> where - K: ToPyObject, + K: IntoPyObject<'py>, + K::Error: Into, { PyAnyMethods::get_item(&**self, key) } @@ -117,8 +125,10 @@ impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> { #[inline] fn set_item(&self, key: K, value: V) -> PyResult<()> where - K: ToPyObject, - V: ToPyObject, + K: IntoPyObject<'py>, + V: IntoPyObject<'py>, + K::Error: Into, + V::Error: Into, { PyAnyMethods::set_item(&**self, key, value) } @@ -126,7 +136,8 @@ impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> { #[inline] fn del_item(&self, key: K) -> PyResult<()> where - K: ToPyObject, + K: IntoPyObject<'py>, + K::Error: Into, { PyAnyMethods::del_item(&**self, key) } From 8ae4846c55b376e359cb276502a8942aa37dd9ba Mon Sep 17 00:00:00 2001 From: Icxolu <10486322+Icxolu@users.noreply.github.com> Date: Sat, 24 Aug 2024 20:04:41 +0200 Subject: [PATCH 2/2] improve `PyBytes` comment wording Co-authored-by: Lily Foote --- src/types/bytes.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/types/bytes.rs b/src/types/bytes.rs index 96f2c43ada6..77b1d2b735d 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -38,9 +38,9 @@ use std::str; /// let other = PyBytes::new(py, b"foo".as_slice()); /// assert!(py_bytes.as_any().eq(other).unwrap()); /// -/// // Note that `eq` will convert it's argument to Python using `IntoPyObject`, -/// // byte collections are specialized, so that the following slice will indeed -/// // convert into a `bytes` object and not a `list` +/// // Note that `eq` will convert its argument to Python using `IntoPyObject`. +/// // Byte collections are specialized, so that the following slice will indeed +/// // convert into a `bytes` object and not a `list`: /// assert!(py_bytes.as_any().eq(b"foo".as_slice()).unwrap()); /// # }); /// ```