diff --git a/guide/src/class.md b/guide/src/class.md index 911d1e7f72b..942effed147 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -1410,6 +1410,7 @@ impl pyo3::PyClass for MyClass { impl<'a, 'holder, 'py> pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'holder, 'py, false> for &'holder MyClass { type Holder = ::std::option::Option>; + type Error = pyo3::PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "MyClass"; @@ -1422,6 +1423,7 @@ impl<'a, 'holder, 'py> pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'ho impl<'a, 'holder, 'py> pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'holder, 'py, false> for &'holder mut MyClass { type Holder = ::std::option::Option>; + type Error = pyo3::PyErr; #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "MyClass"; diff --git a/guide/src/migration.md b/guide/src/migration.md index 8e7f2b6ecab..9985748d8b5 100644 --- a/guide/src/migration.md +++ b/guide/src/migration.md @@ -3,6 +3,136 @@ This guide can help you upgrade code through breaking changes from one PyO3 version to the next. For a detailed list of all changes, see the [CHANGELOG](changelog.md). +## from 0.26.* to 0.27 +### `FromPyObject` reworked for flexibility and efficiency +
+Click to expand + +With the removal of the `gil-ref` API in PyO3 0.23 it is now possible to fully split the Python lifetime +`'py` and the input lifetime `'a`. This allows borrowing from the input data without extending the +lifetime of being attached to the interpreter. + +`FromPyObject` now takes an additional lifetime `'a` describing the input lifetime. The argument +type of the `extract` method changed from `&Bound<'py, PyAny>` to `Borrowed<'a, 'py, PyAny>`. This was +done because `&'a Bound<'py, PyAny>` would have an implicit restriction `'py: 'a` due to the reference type. + +This new form was partly implemented already in 0.22 using the internal `FromPyObjectBound` trait and +is now extended to all types. + +Most implementations can just add an elided lifetime to migrate. + +Additionally `FromPyObject` gained an associated type `Error`. This is the error type that can be used +in case of a conversion error. During migration using `PyErr` is a good default, later a custom error +type can be introduced to prevent unneccessary creation of Python exception objects and improved type safety. + +Before: +```rust,ignore +impl<'py> FromPyObject<'py> for IpAddr { + fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { + ... + } +} +``` + +After +```rust,ignore +impl<'py> FromPyObject<'_, 'py> for IpAddr { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { + ... + // since `Borrowed` derefs to `&Bound`, the body often + // needs no changes, or adding an occasional `&` + } +} +``` + +Occasionally, more steps are necessary. For generic types, the bounds need to be adjusted. The +correct bound depends on how the type is used. + +For simple wrapper types usually it's possible to just forward the bound. + +Before: +```rust,ignore +struct MyWrapper(T); + +impl<'py, T> FromPyObject<'py> for MyWrapper +where + T: FromPyObject<'py> +{ + fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { + ob.extract().map(MyWrapper) + } +} +``` + +After: +```rust +# use pyo3::prelude::*; +# #[allow(dead_code)] +# pub struct MyWrapper(T); +impl<'a, 'py, T> FromPyObject<'a, 'py> for MyWrapper +where + T: FromPyObject<'a, 'py> +{ + type Error = T::Error; + + fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { + obj.extract().map(MyWrapper) + } +} +``` + +Container types that need to create temporary Python references during extraction, for example +extracing from a `PyList`, requires a stronger bound. For these the `FromPyObjectOwned` trait was +introduced. It is automatically implemented for any type that implements `FromPyObject` and does not +borrow from the input. It is intended to be used as a trait bound in these situations. + +Before: +```rust,ignore +struct MyVec(Vec); +impl<'py, T> FromPyObject<'py> for Vec +where + T: FromPyObject<'py>, +{ + fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { + let mut v = MyVec(Vec::new()); + for item in obj.try_iter()? { + v.0.push(item?.extract::()?); + } + Ok(v) + } +} +``` + +After: +```rust +# use pyo3::prelude::*; +# #[allow(dead_code)] +# pub struct MyVec(Vec); +impl<'py, T> FromPyObject<'_, 'py> for MyVec +where + T: FromPyObjectOwned<'py> // 👈 can only extract owned values, because each `item` below + // is a temporary short lived owned reference +{ + type Error = PyErr; + + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { + let mut v = MyVec(Vec::new()); + for item in obj.try_iter()? { + v.0.push(item?.extract::().map_err(Into::into)?); // `map_err` is needed because `?` uses `From`, not `Into` 🙁 + } + Ok(v) + } +} +``` + +This is very similar to `serde`s [`Deserialize`] and [`DeserializeOwned`] traits, see [here](https://serde.rs/lifetimes.html). + +[`Deserialize`]: https://docs.rs/serde/latest/serde/trait.Deserialize.html +[`DeserializeOwned`]: https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html +
+ ## from 0.25.* to 0.26 ### Rename of `Python::with_gil`, `Python::allow_threads`, and `pyo3::prepare_freethreaded_python`
diff --git a/newsfragments/4390.added.md b/newsfragments/4390.added.md new file mode 100644 index 00000000000..c234b0b6547 --- /dev/null +++ b/newsfragments/4390.added.md @@ -0,0 +1,2 @@ +added `FromPyObjectOwned` as more convenient trait bound +added `Borrowed::extract`, same as `PyAnyMethods::extract`, but does not restrict the lifetime by deref \ No newline at end of file diff --git a/newsfragments/4390.changed.md b/newsfragments/4390.changed.md new file mode 100644 index 00000000000..690bdaf17e5 --- /dev/null +++ b/newsfragments/4390.changed.md @@ -0,0 +1,2 @@ +added second lifetime to `FromPyObject` +reintroduced `extract` method \ No newline at end of file diff --git a/newsfragments/4390.removed.md b/newsfragments/4390.removed.md new file mode 100644 index 00000000000..a40ee2db268 --- /dev/null +++ b/newsfragments/4390.removed.md @@ -0,0 +1 @@ +removed `FromPyObjectBound` \ No newline at end of file diff --git a/pyo3-benches/benches/bench_dict.rs b/pyo3-benches/benches/bench_dict.rs index 7959a60d7ca..8ea38b7f67e 100644 --- a/pyo3-benches/benches/bench_dict.rs +++ b/pyo3-benches/benches/bench_dict.rs @@ -62,8 +62,9 @@ fn extract_hashmap(b: &mut Bencher<'_>) { let dict = (0..LEN as u64) .map(|i| (i, i * 2)) .into_py_dict(py) - .unwrap(); - b.iter(|| HashMap::::extract_bound(&dict)); + .unwrap() + .into_any(); + b.iter(|| HashMap::::extract(dict.as_borrowed())); }); } @@ -73,8 +74,9 @@ fn extract_btreemap(b: &mut Bencher<'_>) { let dict = (0..LEN as u64) .map(|i| (i, i * 2)) .into_py_dict(py) - .unwrap(); - b.iter(|| BTreeMap::::extract_bound(&dict)); + .unwrap() + .into_any(); + b.iter(|| BTreeMap::::extract(dict.as_borrowed())); }); } @@ -84,8 +86,9 @@ fn extract_hashbrown_map(b: &mut Bencher<'_>) { let dict = (0..LEN as u64) .map(|i| (i, i * 2)) .into_py_dict(py) - .unwrap(); - b.iter(|| hashbrown::HashMap::::extract_bound(&dict)); + .unwrap() + .into_any(); + b.iter(|| hashbrown::HashMap::::extract(dict.as_borrowed())); }); } diff --git a/pyo3-macros-backend/src/frompyobject.rs b/pyo3-macros-backend/src/frompyobject.rs index b674f4e530e..27b83673982 100644 --- a/pyo3-macros-backend/src/frompyobject.rs +++ b/pyo3-macros-backend/src/frompyobject.rs @@ -499,7 +499,7 @@ impl<'a> Container<'a> { let ty = ty.clone().elide_lifetimes(); let pyo3_crate_path = &ctx.pyo3_path; builder.push_tokens( - quote! { <#ty as #pyo3_crate_path::FromPyObject<'_>>::INPUT_TYPE.as_bytes() }, + quote! { <#ty as #pyo3_crate_path::FromPyObject<'_, '_>>::INPUT_TYPE.as_bytes() }, ) } } @@ -543,7 +543,7 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result { let gen_ident = ¶m.ident; where_clause .predicates - .push(parse_quote!(#gen_ident: #pyo3_path::FromPyObject<'py>)) + .push(parse_quote!(#gen_ident: #pyo3_path::conversion::FromPyObjectOwned<#lt_param>)) } let derives = match &tokens.data { @@ -605,8 +605,10 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result { let ident = &tokens.ident; Ok(quote!( #[automatically_derived] - impl #impl_generics #pyo3_path::FromPyObject<#lt_param> for #ident #ty_generics #where_clause { - fn extract_bound(obj: &#pyo3_path::Bound<#lt_param, #pyo3_path::PyAny>) -> #pyo3_path::PyResult { + impl #impl_generics #pyo3_path::FromPyObject<'_, #lt_param> for #ident #ty_generics #where_clause { + type Error = #pyo3_path::PyErr; + fn extract(obj: #pyo3_path::Borrowed<'_, #lt_param, #pyo3_path::PyAny>) -> ::std::result::Result { + let obj: &#pyo3_path::Bound<'_, _> = &*obj; #derives } #input_type diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index ee225399fe3..91f9117fc9e 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -2327,6 +2327,7 @@ impl<'a> PyClassImplsBuilder<'a> { impl<'a, 'holder, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'holder, 'py, false> for &'holder #cls { type Holder = ::std::option::Option<#pyo3_path::PyClassGuard<'a, #cls>>; + type Error = #pyo3_path::PyErr; #input_type @@ -2341,6 +2342,7 @@ impl<'a> PyClassImplsBuilder<'a> { impl<'a, 'holder, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'holder, 'py, false> for &'holder #cls { type Holder = ::std::option::Option<#pyo3_path::PyClassGuard<'a, #cls>>; + type Error = #pyo3_path::PyErr; #input_type @@ -2353,6 +2355,7 @@ impl<'a> PyClassImplsBuilder<'a> { impl<'a, 'holder, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'holder, 'py, false> for &'holder mut #cls { type Holder = ::std::option::Option<#pyo3_path::PyClassGuardMut<'a, #cls>>; + type Error =#pyo3_path::PyErr; #input_type diff --git a/src/buffer.rs b/src/buffer.rs index c3aad73e04d..bdd0ee214fe 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -18,8 +18,8 @@ // DEALINGS IN THE SOFTWARE. //! `PyBuffer` implementation -use crate::Bound; use crate::{err, exceptions::PyBufferError, ffi, FromPyObject, PyAny, PyResult, Python}; +use crate::{Borrowed, Bound, PyErr}; use std::ffi::{ c_char, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, c_ulonglong, c_ushort, c_void, @@ -185,9 +185,11 @@ pub unsafe trait Element: Copy { fn is_compatible_format(format: &CStr) -> bool; } -impl FromPyObject<'_> for PyBuffer { - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult> { - Self::get(obj) +impl FromPyObject<'_, '_> for PyBuffer { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result, Self::Error> { + Self::get(&obj) } } diff --git a/src/conversion.rs b/src/conversion.rs index 9d54124e29a..3fdb0c47060 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -247,7 +247,8 @@ impl<'py, T> IntoPyObjectExt<'py> for T where T: IntoPyObject<'py> {} /// Extract a type from a Python object. /// /// -/// Normal usage is through the `extract` methods on [`Bound`] and [`Py`], which forward to this trait. +/// Normal usage is through the `extract` methods on [`Bound`], [`Borrowed`] and [`Py`], which +/// forward to this trait. /// /// # Examples /// @@ -271,97 +272,114 @@ impl<'py, T> IntoPyObjectExt<'py> for T where T: IntoPyObject<'py> {} /// # } /// ``` /// -// /// FIXME: until `FromPyObject` can pick up a second lifetime, the below commentary is no longer -// /// true. Update and restore this documentation at that time. -// /// -// /// Note: depending on the implementation, the lifetime of the extracted result may -// /// depend on the lifetime of the `obj` or the `prepared` variable. -// /// -// /// For example, when extracting `&str` from a Python byte string, the resulting string slice will -// /// point to the existing string data (lifetime: `'py`). -// /// On the other hand, when extracting `&str` from a Python Unicode string, the preparation step -// /// will convert the string to UTF-8, and the resulting string slice will have lifetime `'prepared`. -// /// Since which case applies depends on the runtime type of the Python object, -// /// both the `obj` and `prepared` variables must outlive the resulting string slice. -/// -/// During the migration of PyO3 from the "GIL Refs" API to the `Bound` smart pointer, this trait -/// has two methods `extract` and `extract_bound` which are defaulted to call each other. To avoid -/// infinite recursion, implementors must implement at least one of these methods. The recommendation -/// is to implement `extract_bound` and leave `extract` as the default implementation. -pub trait FromPyObject<'py>: Sized { - /// Provides the type hint information for this type when it appears as an argument. - /// - /// For example, `Vec` would be `collections.abc.Sequence[int]`. - /// The default value is `typing.Any`, which is correct for any type. - #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: &'static str = "typing.Any"; - - /// Extracts `Self` from the bound smart pointer `obj`. - /// - /// Implementors are encouraged to implement this method and leave `extract` defaulted, as - /// this will be most compatible with PyO3's future API. - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult; - - /// Extracts the type hint information for this type when it appears as an argument. - /// - /// For example, `Vec` would return `Sequence[int]`. - /// The default implementation returns `Any`, which is correct for any type. - /// - /// For most types, the return value for this method will be identical to that of - /// [`IntoPyObject::type_output`]. It may be different for some types, such as `Dict`, - /// to allow duck-typing: functions return `Dict` but take `Mapping` as argument. - #[cfg(feature = "experimental-inspect")] - fn type_input() -> TypeInfo { - TypeInfo::Any - } -} - -mod from_py_object_bound_sealed { - use crate::{pyclass::boolean_struct::False, PyClass, PyClassGuard, PyClassGuardMut}; - - /// Private seal for the `FromPyObjectBound` trait. - /// - /// This prevents downstream types from implementing the trait before - /// PyO3 is ready to declare the trait as public API. - pub trait Sealed {} - - // This generic implementation is why the seal is separate from - // `crate::sealed::Sealed`. - impl<'py, T> Sealed for T where T: super::FromPyObject<'py> {} - impl Sealed for PyClassGuard<'_, T> where T: PyClass {} - impl Sealed for PyClassGuardMut<'_, T> where T: PyClass {} - impl Sealed for &'_ str {} - impl Sealed for std::borrow::Cow<'_, str> {} - impl Sealed for &'_ [u8] {} - impl Sealed for std::borrow::Cow<'_, [u8]> {} -} - -/// Expected form of [`FromPyObject`] to be used in a future PyO3 release. +/// Note: Depending on the Python version and implementation, some [`FromPyObject`] implementations +/// may produce a result that borrows into the Python type. This is described by the input lifetime +/// `'a` of `obj`. +/// +/// Types that must not borrow from the input can use [`FromPyObjectOwned`] as a restriction. This +/// is most often the case for collection types. See its documentation for more details. +/// +/// # How to implement [`FromPyObject`]? +/// ## `#[derive(FromPyObject)]` +/// The simplest way to implement [`FromPyObject`] for a custom type is to make use of our derive +/// macro. +/// ```rust,no_run +/// # #![allow(dead_code)] +/// use pyo3::prelude::*; +/// +/// #[derive(FromPyObject)] +/// struct MyObject { +/// msg: String, +/// list: Vec +/// } +/// # fn main() {} +/// ``` +/// By default this will try to extract each field from the Python object by attribute access, but +/// this can be customized. For more information about the derive macro, its configuration as well +/// as its working principle for other types, take a look at the [guide]. +/// +/// In case the derive macro is not sufficient or can not be used for some other reason, +/// [`FromPyObject`] can be implemented manually. In the following types without lifetime parameters +/// are handled first, because they are a little bit simpler. Types with lifetime parameters are +/// explained below. +/// +/// ## Manual implementation for types without lifetime +/// Types that do not contain lifetime parameters are unable to borrow from the Python object, so +/// the lifetimes of [`FromPyObject`] can be elided: +/// ```rust,no_run +/// # #![allow(dead_code)] +/// use pyo3::prelude::*; +/// +/// struct MyObject { +/// msg: String, +/// list: Vec +/// } /// -/// The difference between this and `FromPyObject` is that this trait takes an -/// additional lifetime `'a`, which is the lifetime of the input `Bound`. +/// impl FromPyObject<'_, '_> for MyObject { +/// type Error = PyErr; /// -/// This allows implementations for `&'a str` and `&'a [u8]`, which could not -/// be expressed by the existing `FromPyObject` trait once the GIL Refs API was -/// removed. +/// fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { +/// Ok(MyObject { +/// msg: obj.getattr("msg")?.extract()?, +/// list: obj.getattr("list")?.extract()?, +/// }) +/// } +/// } +/// +/// # fn main() {} +/// ``` +/// This is basically what the derive macro above expands to. /// -/// # Usage +/// ## Manual implementation for types with lifetime paramaters +/// For types that contain lifetimes, these lifetimes need to be bound to the corresponding +/// [`FromPyObject`] lifetime. This is roughly how the extraction of a typed [`Bound`] is +/// implemented within PyO3. +/// +/// ```rust,no_run +/// # #![allow(dead_code)] +/// use pyo3::prelude::*; +/// use pyo3::types::PyString; /// -/// Users are prevented from implementing this trait, instead they should implement -/// the normal `FromPyObject` trait. This trait has a blanket implementation -/// for `T: FromPyObject`. +/// struct MyObject<'py>(Bound<'py, PyString>); +/// +/// impl<'py> FromPyObject<'_, 'py> for MyObject<'py> { +/// type Error = PyErr; +/// +/// fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { +/// Ok(MyObject(obj.cast()?.to_owned())) +/// } +/// } +/// +/// # fn main() {} +/// ``` /// -/// The only case where this trait may have a use case to be implemented is when the -/// lifetime of the extracted value is tied to the lifetime `'a` of the input `Bound` -/// instead of the GIL lifetime `py`, as is the case for the `&'a str` implementation. +/// # Details +/// [`Cow<'a, str>`] is an example of an output type that may or may not borrow from the input +/// lifetime `'a`. Which variant will be produced depends on the runtime type of the Python object. +/// For a Python byte string, the existing string data can be borrowed for `'a` into a +/// [`Cow::Borrowed`]. For a Python Unicode string, the data may have to be reencoded to UTF-8, and +/// copied into a [`Cow::Owned`]. It does _not_ depend on the Python lifetime `'py`. /// -/// Please contact the PyO3 maintainers if you believe you have a use case for implementing -/// this trait before PyO3 is ready to change the main `FromPyObject` trait to take an -/// additional lifetime. +/// The output type may also depend on the Python lifetime `'py`. This allows the output type to +/// keep interacting with the Python interpreter. See also [`Bound<'py, T>`]. /// -/// Similarly, users should typically not call these trait methods and should instead -/// use this via the `extract` method on `Bound` and `Py`. -pub trait FromPyObjectBound<'a, 'py>: Sized + from_py_object_bound_sealed::Sealed { +/// [`Cow<'a, str>`]: std::borrow::Cow +/// [`Cow::Borrowed`]: std::borrow::Cow::Borrowed +/// [`Cow::Owned`]: std::borrow::Cow::Owned +/// [guide]: https://pyo3.rs/latest/conversions/traits.html#deriving-frompyobject +pub trait FromPyObject<'a, 'py>: Sized { + /// The type returned in the event of a conversion error. + /// + /// For most use cases defaulting to [PyErr] here is perfectly acceptable. Using a custom error + /// type can be used to avoid having to create a Python exception object in the case where that + /// exception never reaches Python. This may lead to slightly better performance under certain + /// conditions. + /// + /// # Note + /// Unfortunately `Try` and thus `?` is based on [`From`], not [`Into`], so implementations may + /// need to use `.map_err(Into::into)` sometimes to convert a generic `Error` into a [`PyErr`]. + type Error: Into; + /// Provides the type hint information for this type when it appears as an argument. /// /// For example, `Vec` would be `collections.abc.Sequence[int]`. @@ -373,7 +391,7 @@ pub trait FromPyObjectBound<'a, 'py>: Sized + from_py_object_bound_sealed::Seale /// /// Users are advised against calling this method directly: instead, use this via /// [`Bound<'_, PyAny>::extract`](crate::types::any::PyAnyMethods::extract) or [`Py::extract`]. - fn from_py_object_bound(ob: Borrowed<'a, 'py, PyAny>) -> PyResult; + fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result; /// Extracts the type hint information for this type when it appears as an argument. /// @@ -389,56 +407,98 @@ pub trait FromPyObjectBound<'a, 'py>: Sized + from_py_object_bound_sealed::Seale } } -impl<'py, T> FromPyObjectBound<'_, 'py> for T -where - T: FromPyObject<'py>, -{ - #[cfg(feature = "experimental-inspect")] - const INPUT_TYPE: &'static str = T::INPUT_TYPE; - - fn from_py_object_bound(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { - Self::extract_bound(&ob) - } - - #[cfg(feature = "experimental-inspect")] - fn type_input() -> TypeInfo { - ::type_input() - } -} +/// A data structure that can be extracted without borrowing any data from the input. +/// +/// This is primarily useful for trait bounds. For example a [`FromPyObject`] implementation of a +/// wrapper type may be able to borrow data from the input, but a [`FromPyObject`] implementation of +/// a collection type may only extract owned data. +/// +/// For example [`PyList`] will not hand out references tied to its own lifetime, but "owned" +/// references independent of it. (Similar to [`Vec>`] where you clone the [`Arc`] out). +/// This makes it impossible to collect borrowed types in a collection, since they would not borrow +/// from the original [`PyList`], but the much shorter lived element reference. See the example +/// below. +/// +/// ```,no_run +/// # use pyo3::prelude::*; +/// # #[allow(dead_code)] +/// pub struct MyWrapper(T); +/// +/// impl<'a, 'py, T> FromPyObject<'a, 'py> for MyWrapper +/// where +/// T: FromPyObject<'a, 'py> +/// { +/// type Error = T::Error; +/// +/// fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { +/// obj.extract().map(MyWrapper) +/// } +/// } +/// +/// # #[allow(dead_code)] +/// pub struct MyVec(Vec); +/// +/// impl<'py, T> FromPyObject<'_, 'py> for MyVec +/// where +/// T: FromPyObjectOwned<'py> // 👈 can only extract owned values, because each `item` below +/// // is a temporary short lived owned reference +/// { +/// type Error = PyErr; +/// +/// fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { +/// let mut v = MyVec(Vec::new()); +/// for item in obj.try_iter()? { +/// v.0.push(item?.extract::().map_err(Into::into)?); +/// } +/// Ok(v) +/// } +/// } +/// ``` +/// +/// [`PyList`]: crate::types::PyList +/// [`Arc`]: std::sync::Arc +pub trait FromPyObjectOwned<'py>: for<'a> FromPyObject<'a, 'py> {} +impl<'py, T> FromPyObjectOwned<'py> for T where T: for<'a> FromPyObject<'a, 'py> {} -impl FromPyObject<'_> for T +impl FromPyObject<'_, '_> for T where T: PyClass + Clone, { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = ::TYPE_NAME; - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let bound = obj.cast::()?; Ok(bound.try_borrow()?.clone()) } } -impl<'py, T> FromPyObject<'py> for PyRef<'py, T> +impl<'py, T> FromPyObject<'_, 'py> for PyRef<'py, T> where T: PyClass, { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = ::TYPE_NAME; - fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { obj.cast::()?.try_borrow().map_err(Into::into) } } -impl<'py, T> FromPyObject<'py> for PyRefMut<'py, T> +impl<'py, T> FromPyObject<'_, 'py> for PyRefMut<'py, T> where T: PyClass, { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = ::TYPE_NAME; - fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { obj.cast::()?.try_borrow_mut().map_err(Into::into) } } diff --git a/src/conversions/bigdecimal.rs b/src/conversions/bigdecimal.rs index af976431894..991c5567cce 100644 --- a/src/conversions/bigdecimal.rs +++ b/src/conversions/bigdecimal.rs @@ -56,7 +56,7 @@ use crate::{ exceptions::PyValueError, sync::PyOnceLock, types::{PyAnyMethods, PyStringMethods, PyType}, - Bound, FromPyObject, IntoPyObject, Py, PyAny, PyErr, PyResult, Python, + Borrowed, Bound, FromPyObject, IntoPyObject, Py, PyAny, PyErr, PyResult, Python, }; use bigdecimal::BigDecimal; use num_bigint::Sign; @@ -71,8 +71,10 @@ fn get_invalid_operation_error_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType INVALID_OPERATION_CLS.import(py, "decimal", "InvalidOperation") } -impl FromPyObject<'_> for BigDecimal { - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for BigDecimal { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, '_, PyAny>) -> PyResult { let py_str = &obj.str()?; let rs_str = &py_str.to_cow()?; BigDecimal::from_str(rs_str).map_err(|e| PyValueError::new_err(e.to_string())) diff --git a/src/conversions/bytes.rs b/src/conversions/bytes.rs index 9c615e84a5e..0aba7409578 100644 --- a/src/conversions/bytes.rs +++ b/src/conversions/bytes.rs @@ -67,13 +67,14 @@ use bytes::Bytes; use crate::conversion::IntoPyObject; use crate::instance::Bound; use crate::pybacked::PyBackedBytes; -use crate::types::any::PyAnyMethods; use crate::types::PyBytes; -use crate::{FromPyObject, PyAny, PyErr, PyResult, Python}; +use crate::{Borrowed, DowncastError, FromPyObject, PyAny, PyErr, Python}; -impl FromPyObject<'_> for Bytes { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { - Ok(Bytes::from_owner(ob.extract::()?)) +impl<'a, 'py> FromPyObject<'a, 'py> for Bytes { + type Error = DowncastError<'a, 'py>; + + fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { + Ok(Bytes::from_owner(obj.extract::()?)) } } @@ -100,7 +101,7 @@ impl<'py> IntoPyObject<'py> for &Bytes { #[cfg(test)] mod tests { use super::*; - use crate::types::{PyByteArray, PyByteArrayMethods, PyBytes}; + use crate::types::{PyAnyMethods, PyByteArray, PyByteArrayMethods, PyBytes}; use crate::Python; #[test] diff --git a/src/conversions/chrono.rs b/src/conversions/chrono.rs index bd97d3409fc..d250cfc03d5 100644 --- a/src/conversions/chrono.rs +++ b/src/conversions/chrono.rs @@ -41,7 +41,7 @@ //! } //! ``` -use crate::conversion::IntoPyObject; +use crate::conversion::{FromPyObjectOwned, IntoPyObject}; use crate::exceptions::{PyTypeError, PyUserWarning, PyValueError}; use crate::intern; use crate::types::any::PyAnyMethods; @@ -108,8 +108,10 @@ impl<'py> IntoPyObject<'py> for &Duration { } } -impl FromPyObject<'_> for Duration { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for Duration { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let delta = ob.cast::()?; // Python size are much lower than rust size so we do not need bound checks. // 0 <= microseconds < 1000000 @@ -162,9 +164,11 @@ impl<'py> IntoPyObject<'py> for &NaiveDate { } } -impl FromPyObject<'_> for NaiveDate { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { - let date = ob.cast::()?; +impl FromPyObject<'_, '_> for NaiveDate { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { + let date = &*ob.cast::()?; py_date_to_naive_date(date) } } @@ -204,9 +208,11 @@ impl<'py> IntoPyObject<'py> for &NaiveTime { } } -impl FromPyObject<'_> for NaiveTime { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { - let time = ob.cast::()?; +impl FromPyObject<'_, '_> for NaiveTime { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { + let time = &*ob.cast::()?; py_time_to_naive_time(time) } } @@ -247,9 +253,11 @@ impl<'py> IntoPyObject<'py> for &NaiveDateTime { } } -impl FromPyObject<'_> for NaiveDateTime { - fn extract_bound(dt: &Bound<'_, PyAny>) -> PyResult { - let dt = dt.cast::()?; +impl FromPyObject<'_, '_> for NaiveDateTime { + type Error = PyErr; + + fn extract(dt: Borrowed<'_, '_, PyAny>) -> Result { + let dt = &*dt.cast::()?; // If the user tries to convert a timezone aware datetime into a naive one, // we return a hard error. We could silently remove tzinfo, or assume local timezone @@ -324,13 +332,18 @@ where } } -impl FromPyObject<'py>> FromPyObject<'_> for DateTime { - fn extract_bound(dt: &Bound<'_, PyAny>) -> PyResult> { - let dt = dt.cast::()?; +impl<'py, Tz> FromPyObject<'_, 'py> for DateTime +where + Tz: TimeZone + FromPyObjectOwned<'py>, +{ + type Error = PyErr; + + fn extract(dt: Borrowed<'_, 'py, PyAny>) -> Result { + let dt = &*dt.cast::()?; let tzinfo = dt.get_tzinfo(); let tz = if let Some(tzinfo) = tzinfo { - tzinfo.extract()? + tzinfo.extract().map_err(Into::into)? } else { return Err(PyTypeError::new_err( "expected a datetime with non-None tzinfo", @@ -382,12 +395,14 @@ impl<'py> IntoPyObject<'py> for &FixedOffset { } } -impl FromPyObject<'_> for FixedOffset { +impl FromPyObject<'_, '_> for FixedOffset { + type Error = PyErr; + /// Convert python tzinfo to rust [`FixedOffset`]. /// /// Note that the conversion will result in precision lost in microseconds as chrono offset /// does not supports microseconds. - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let ob = ob.cast::()?; // Passing Python's None to the `utcoffset` function will only @@ -431,8 +446,10 @@ impl<'py> IntoPyObject<'py> for &Utc { } } -impl FromPyObject<'_> for Utc { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for Utc { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let py_utc = PyTzInfo::utc(ob.py())?; if ob.eq(py_utc)? { Ok(Utc) @@ -475,8 +492,10 @@ impl<'py> IntoPyObject<'py> for &Local { } #[cfg(feature = "chrono-local")] -impl FromPyObject<'_> for Local { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for Local { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, '_, PyAny>) -> PyResult { let local_tz = Local.into_pyobject(ob.py())?; if ob.eq(local_tz)? { Ok(Local) @@ -543,7 +562,9 @@ fn warn_truncated_leap_second(obj: &Bound<'_, PyAny>) { } #[cfg(not(Py_LIMITED_API))] -fn py_date_to_naive_date(py_date: &impl PyDateAccess) -> PyResult { +fn py_date_to_naive_date( + py_date: impl std::ops::Deref, +) -> PyResult { NaiveDate::from_ymd_opt( py_date.get_year(), py_date.get_month().into(), @@ -563,7 +584,9 @@ fn py_date_to_naive_date(py_date: &Bound<'_, PyAny>) -> PyResult { } #[cfg(not(Py_LIMITED_API))] -fn py_time_to_naive_time(py_time: &impl PyTimeAccess) -> PyResult { +fn py_time_to_naive_time( + py_time: impl std::ops::Deref, +) -> PyResult { NaiveTime::from_hms_micro_opt( py_time.get_hour().into(), py_time.get_minute().into(), diff --git a/src/conversions/chrono_tz.rs b/src/conversions/chrono_tz.rs index ff2b6b74d54..6926559c084 100644 --- a/src/conversions/chrono_tz.rs +++ b/src/conversions/chrono_tz.rs @@ -38,7 +38,7 @@ use crate::conversion::IntoPyObject; use crate::exceptions::PyValueError; use crate::pybacked::PyBackedStr; use crate::types::{any::PyAnyMethods, PyTzInfo}; -use crate::{intern, Bound, FromPyObject, PyAny, PyErr, PyResult, Python}; +use crate::{intern, Borrowed, Bound, FromPyObject, PyAny, PyErr, Python}; use chrono_tz::Tz; use std::str::FromStr; @@ -63,8 +63,10 @@ impl<'py> IntoPyObject<'py> for &Tz { } } -impl FromPyObject<'_> for Tz { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for Tz { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { Tz::from_str( &ob.getattr(intern!(ob.py(), "key"))? .extract::()?, @@ -78,6 +80,7 @@ mod tests { use super::*; use crate::prelude::PyAnyMethods; use crate::types::PyTzInfo; + use crate::Bound; use crate::Python; use chrono::{DateTime, Utc}; use chrono_tz::Tz; diff --git a/src/conversions/either.rs b/src/conversions/either.rs index d073f3e2706..6a5acebae75 100644 --- a/src/conversions/either.rs +++ b/src/conversions/either.rs @@ -47,8 +47,8 @@ #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::{ - exceptions::PyTypeError, types::any::PyAnyMethods, Bound, FromPyObject, IntoPyObject, - IntoPyObjectExt, PyAny, PyErr, PyResult, Python, + exceptions::PyTypeError, Borrowed, Bound, FromPyObject, IntoPyObject, IntoPyObjectExt, PyAny, + PyErr, Python, }; use either::Either; @@ -89,13 +89,15 @@ where } #[cfg_attr(docsrs, doc(cfg(feature = "either")))] -impl<'py, L, R> FromPyObject<'py> for Either +impl<'a, 'py, L, R> FromPyObject<'a, 'py> for Either where - L: FromPyObject<'py>, - R: FromPyObject<'py>, + L: FromPyObject<'a, 'py>, + R: FromPyObject<'a, 'py>, { + type Error = PyErr; + #[inline] - fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { + fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { if let Ok(l) = obj.extract::() { Ok(Either::Left(l)) } else if let Ok(r) = obj.extract::() { diff --git a/src/conversions/hashbrown.rs b/src/conversions/hashbrown.rs index 26fcc5c627e..85bc350999b 100644 --- a/src/conversions/hashbrown.rs +++ b/src/conversions/hashbrown.rs @@ -17,7 +17,7 @@ //! Note that you must use compatible versions of hashbrown and PyO3. //! The required hashbrown version may vary based on the version of PyO3. use crate::{ - conversion::IntoPyObject, + conversion::{FromPyObjectOwned, IntoPyObject}, types::{ any::PyAnyMethods, dict::PyDictMethods, @@ -25,7 +25,7 @@ use crate::{ set::{try_new_from_iter, PySetMethods}, PyDict, PyFrozenSet, PySet, }, - Bound, FromPyObject, PyAny, PyErr, PyResult, Python, + Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, Python, }; use std::{cmp, hash}; @@ -67,17 +67,22 @@ where } } -impl<'py, K, V, S> FromPyObject<'py> for hashbrown::HashMap +impl<'py, K, V, S> FromPyObject<'_, 'py> for hashbrown::HashMap where - K: FromPyObject<'py> + cmp::Eq + hash::Hash, - V: FromPyObject<'py>, + K: FromPyObjectOwned<'py> + cmp::Eq + hash::Hash, + V: FromPyObjectOwned<'py>, S: hash::BuildHasher + Default, { - fn extract_bound(ob: &Bound<'py, PyAny>) -> Result { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; let mut ret = hashbrown::HashMap::with_capacity_and_hasher(dict.len(), S::default()); - for (k, v) in dict { - ret.insert(k.extract()?, v.extract()?); + for (k, v) in dict.iter() { + ret.insert( + k.extract().map_err(Into::into)?, + v.extract().map_err(Into::into)?, + ); } Ok(ret) } @@ -111,17 +116,25 @@ where } } -impl<'py, K, S> FromPyObject<'py> for hashbrown::HashSet +impl<'py, K, S> FromPyObject<'_, 'py> for hashbrown::HashSet where - K: FromPyObject<'py> + cmp::Eq + hash::Hash, + K: FromPyObjectOwned<'py> + cmp::Eq + hash::Hash, S: hash::BuildHasher + Default, { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { match ob.cast::() { - Ok(set) => set.iter().map(|any| any.extract()).collect(), + Ok(set) => set + .iter() + .map(|any| any.extract().map_err(Into::into)) + .collect(), Err(err) => { if let Ok(frozen_set) = ob.cast::() { - frozen_set.iter().map(|any| any.extract()).collect() + frozen_set + .iter() + .map(|any| any.extract().map_err(Into::into)) + .collect() } else { Err(PyErr::from(err)) } diff --git a/src/conversions/indexmap.rs b/src/conversions/indexmap.rs index b3e8190d471..89ef8172961 100644 --- a/src/conversions/indexmap.rs +++ b/src/conversions/indexmap.rs @@ -87,9 +87,9 @@ //! # if another hash table was used, the order could be random //! ``` -use crate::conversion::IntoPyObject; +use crate::conversion::{FromPyObjectOwned, IntoPyObject}; use crate::types::*; -use crate::{Bound, FromPyObject, PyErr, Python}; +use crate::{Borrowed, Bound, FromPyObject, PyErr, Python}; use std::{cmp, hash}; impl<'py, K, V, H> IntoPyObject<'py> for indexmap::IndexMap @@ -130,17 +130,22 @@ where } } -impl<'py, K, V, S> FromPyObject<'py> for indexmap::IndexMap +impl<'py, K, V, S> FromPyObject<'_, 'py> for indexmap::IndexMap where - K: FromPyObject<'py> + cmp::Eq + hash::Hash, - V: FromPyObject<'py>, + K: FromPyObjectOwned<'py> + cmp::Eq + hash::Hash, + V: FromPyObjectOwned<'py>, S: hash::BuildHasher + Default, { - fn extract_bound(ob: &Bound<'py, PyAny>) -> Result { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; let mut ret = indexmap::IndexMap::with_capacity_and_hasher(dict.len(), S::default()); - for (k, v) in dict { - ret.insert(k.extract()?, v.extract()?); + for (k, v) in dict.iter() { + ret.insert( + k.extract().map_err(Into::into)?, + v.extract().map_err(Into::into)?, + ); } Ok(ret) } diff --git a/src/conversions/jiff.rs b/src/conversions/jiff.rs index fd2503646c0..7ad84516f47 100644 --- a/src/conversions/jiff.rs +++ b/src/conversions/jiff.rs @@ -51,7 +51,7 @@ use crate::types::{PyAnyMethods, PyNone}; use crate::types::{PyDate, PyDateTime, PyDelta, PyTime, PyTzInfo, PyTzInfoAccess}; #[cfg(not(Py_LIMITED_API))] use crate::types::{PyDateAccess, PyDeltaAccess, PyTimeAccess}; -use crate::{intern, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python}; +use crate::{intern, Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python}; use jiff::civil::{Date, DateTime, Time}; use jiff::tz::{Offset, TimeZone}; use jiff::{SignedDuration, Span, Timestamp, Zoned}; @@ -122,8 +122,10 @@ impl<'py> IntoPyObject<'py> for &Timestamp { } } -impl<'py> FromPyObject<'py> for Timestamp { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { +impl<'a, 'py> FromPyObject<'a, 'py> for Timestamp { + type Error = >::Error; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let zoned = ob.extract::()?; Ok(zoned.timestamp()) } @@ -154,8 +156,10 @@ impl<'py> IntoPyObject<'py> for &Date { } } -impl<'py> FromPyObject<'py> for Date { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { +impl<'py> FromPyObject<'_, 'py> for Date { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { let date = ob.cast::()?; #[cfg(not(Py_LIMITED_API))] @@ -206,11 +210,13 @@ impl<'py> IntoPyObject<'py> for &Time { } } -impl<'py> FromPyObject<'py> for Time { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { - let ob = ob.cast::()?; +impl<'py> FromPyObject<'_, 'py> for Time { + type Error = PyErr; - pytime_to_time(ob) + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { + let ob = ob.cast::()?; + #[allow(clippy::explicit_auto_deref)] + pytime_to_time(&*ob) } } @@ -234,8 +240,10 @@ impl<'py> IntoPyObject<'py> for &DateTime { } } -impl<'py> FromPyObject<'py> for DateTime { - fn extract_bound(dt: &Bound<'py, PyAny>) -> PyResult { +impl<'py> FromPyObject<'_, 'py> for DateTime { + type Error = PyErr; + + fn extract(dt: Borrowed<'_, 'py, PyAny>) -> PyResult { let dt = dt.cast::()?; let has_tzinfo = dt.get_tzinfo().is_some(); @@ -243,7 +251,8 @@ impl<'py> FromPyObject<'py> for DateTime { return Err(PyTypeError::new_err("expected a datetime without tzinfo")); } - Ok(DateTime::from_parts(dt.extract()?, pytime_to_time(dt)?)) + #[allow(clippy::explicit_auto_deref)] + Ok(DateTime::from_parts(dt.extract()?, pytime_to_time(&*dt)?)) } } @@ -283,8 +292,10 @@ impl<'py> IntoPyObject<'py> for &Zoned { } } -impl<'py> FromPyObject<'py> for Zoned { - fn extract_bound(dt: &Bound<'py, PyAny>) -> PyResult { +impl<'py> FromPyObject<'_, 'py> for Zoned { + type Error = PyErr; + + fn extract(dt: Borrowed<'_, 'py, PyAny>) -> PyResult { let dt = dt.cast::()?; let tz = dt @@ -295,7 +306,8 @@ impl<'py> FromPyObject<'py> for Zoned { "expected a datetime with non-None tzinfo", )) })?; - let datetime = DateTime::from_parts(dt.extract()?, pytime_to_time(dt)?); + #[allow(clippy::explicit_auto_deref)] + let datetime = DateTime::from_parts(dt.extract()?, pytime_to_time(&*dt)?); let zoned = tz.into_ambiguous_zoned(datetime); #[cfg(not(Py_LIMITED_API))] @@ -340,8 +352,10 @@ impl<'py> IntoPyObject<'py> for &TimeZone { } } -impl<'py> FromPyObject<'py> for TimeZone { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { +impl<'py> FromPyObject<'_, 'py> for TimeZone { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { let ob = ob.cast::()?; let attr = intern!(ob.py(), "key"); @@ -377,8 +391,10 @@ impl<'py> IntoPyObject<'py> for Offset { } } -impl<'py> FromPyObject<'py> for Offset { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { +impl<'py> FromPyObject<'_, 'py> for Offset { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { let py = ob.py(); let ob = ob.cast::()?; @@ -425,8 +441,10 @@ impl<'py> IntoPyObject<'py> for SignedDuration { } } -impl<'py> FromPyObject<'py> for SignedDuration { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { +impl<'py> FromPyObject<'_, 'py> for SignedDuration { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { let delta = ob.cast::()?; #[cfg(not(Py_LIMITED_API))] @@ -450,8 +468,10 @@ impl<'py> FromPyObject<'py> for SignedDuration { } } -impl<'py> FromPyObject<'py> for Span { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { +impl<'py> FromPyObject<'_, 'py> for Span { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> PyResult { let duration = ob.extract::()?; Ok(duration.try_into()?) } diff --git a/src/conversions/num_bigint.rs b/src/conversions/num_bigint.rs index 0f8924bbf12..4384231c801 100644 --- a/src/conversions/num_bigint.rs +++ b/src/conversions/num_bigint.rs @@ -50,7 +50,7 @@ #[cfg(Py_LIMITED_API)] use crate::types::{bytes::PyBytesMethods, PyBytes}; use crate::{ - conversion::IntoPyObject, ffi, instance::Bound, types::PyInt, FromPyObject, Py, PyAny, PyErr, + conversion::IntoPyObject, ffi, types::PyInt, Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, PyResult, Python, }; @@ -123,8 +123,10 @@ bigint_conversion!(BigUint, false, BigUint::to_bytes_le); bigint_conversion!(BigInt, true, BigInt::to_signed_bytes_le); #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))] -impl<'py> FromPyObject<'py> for BigInt { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { +impl<'py> FromPyObject<'_, 'py> for BigInt { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let py = ob.py(); // fast path - checking for subclass of `int` just checks a bit in the type object let num_owned: Py; @@ -132,11 +134,11 @@ impl<'py> FromPyObject<'py> for BigInt { long } else { num_owned = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? }; - num_owned.bind(py) + num_owned.bind_borrowed(py) }; #[cfg(not(Py_LIMITED_API))] { - let mut buffer = int_to_u32_vec::(num)?; + let mut buffer = int_to_u32_vec::(&num)?; let sign = if buffer.last().copied().is_some_and(|last| last >> 31 != 0) { // BigInt::new takes an unsigned array, so need to convert from two's complement // flip all bits, 'subtract' 1 (by adding one to the unsigned array) @@ -160,19 +162,21 @@ impl<'py> FromPyObject<'py> for BigInt { } #[cfg(Py_LIMITED_API)] { - let n_bits = int_n_bits(num)?; + let n_bits = int_n_bits(&num)?; if n_bits == 0 { return Ok(BigInt::from(0isize)); } - let bytes = int_to_py_bytes(num, (n_bits + 8) / 8, true)?; + let bytes = int_to_py_bytes(&num, (n_bits + 8) / 8, true)?; Ok(BigInt::from_signed_bytes_le(bytes.as_bytes())) } } } #[cfg_attr(docsrs, doc(cfg(feature = "num-bigint")))] -impl<'py> FromPyObject<'py> for BigUint { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { +impl<'py> FromPyObject<'_, 'py> for BigUint { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let py = ob.py(); // fast path - checking for subclass of `int` just checks a bit in the type object let num_owned: Py; @@ -180,20 +184,20 @@ impl<'py> FromPyObject<'py> for BigUint { long } else { num_owned = unsafe { Py::from_owned_ptr_or_err(py, ffi::PyNumber_Index(ob.as_ptr()))? }; - num_owned.bind(py) + num_owned.bind_borrowed(py) }; #[cfg(not(Py_LIMITED_API))] { - let buffer = int_to_u32_vec::(num)?; + let buffer = int_to_u32_vec::(&num)?; Ok(BigUint::new(buffer)) } #[cfg(Py_LIMITED_API)] { - let n_bits = int_n_bits(num)?; + let n_bits = int_n_bits(&num)?; if n_bits == 0 { return Ok(BigUint::from(0usize)); } - let bytes = int_to_py_bytes(num, n_bits.div_ceil(8), false)?; + let bytes = int_to_py_bytes(&num, n_bits.div_ceil(8), false)?; Ok(BigUint::from_bytes_le(bytes.as_bytes())) } } @@ -307,8 +311,8 @@ fn int_n_bits(long: &Bound<'_, PyInt>) -> PyResult { #[cfg(Py_LIMITED_API)] { - use crate::types::any::PyAnyMethods; // slow path + use crate::types::PyAnyMethods; long.call_method0(crate::intern!(py, "bit_length")) .and_then(|any| any.extract()) } diff --git a/src/conversions/num_complex.rs b/src/conversions/num_complex.rs index cdb7ad3fc42..778680153fe 100644 --- a/src/conversions/num_complex.rs +++ b/src/conversions/num_complex.rs @@ -94,7 +94,7 @@ //! assert result == [complex(1,-1), complex(-2,0)] //! ``` use crate::{ - ffi, ffi_ptr_ext::FfiPtrExt, types::PyComplex, Bound, FromPyObject, PyAny, PyErr, PyResult, + ffi, ffi_ptr_ext::FfiPtrExt, types::PyComplex, Borrowed, Bound, FromPyObject, PyAny, PyErr, Python, }; use num_complex::Complex; @@ -146,8 +146,10 @@ macro_rules! complex_conversion { } #[cfg_attr(docsrs, doc(cfg(feature = "num-complex")))] - impl FromPyObject<'_> for Complex<$float> { - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult> { + impl FromPyObject<'_, '_> for Complex<$float> { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result, Self::Error> { #[cfg(not(any(Py_LIMITED_API, PyPy)))] unsafe { let val = ffi::PyComplex_AsCComplex(obj.as_ptr()); @@ -169,7 +171,7 @@ macro_rules! complex_conversion { obj.lookup_special(crate::intern!(obj.py(), "__complex__"))? { complex = method.call0()?; - &complex + complex.as_borrowed() } else { // `obj` might still implement `__float__` or `__index__`, which will be // handled by `PyComplex_{Real,Imag}AsDouble`, including propagating any diff --git a/src/conversions/num_rational.rs b/src/conversions/num_rational.rs index 25aac24aa7a..c64a48323a2 100644 --- a/src/conversions/num_rational.rs +++ b/src/conversions/num_rational.rs @@ -48,7 +48,7 @@ use crate::ffi; use crate::sync::PyOnceLock; use crate::types::any::PyAnyMethods; use crate::types::PyType; -use crate::{Bound, FromPyObject, Py, PyAny, PyErr, PyResult, Python}; +use crate::{Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, PyResult, Python}; #[cfg(feature = "num-bigint")] use num_bigint::BigInt; @@ -62,8 +62,10 @@ fn get_fraction_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { macro_rules! rational_conversion { ($int: ty) => { - impl<'py> FromPyObject<'py> for Ratio<$int> { - fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { + impl<'py> FromPyObject<'_, 'py> for Ratio<$int> { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { let py = obj.py(); let py_numerator_obj = obj.getattr(crate::intern!(py, "numerator"))?; let py_denominator_obj = obj.getattr(crate::intern!(py, "denominator"))?; diff --git a/src/conversions/ordered_float.rs b/src/conversions/ordered_float.rs index a4da92b5b14..71bcd29bca7 100644 --- a/src/conversions/ordered_float.rs +++ b/src/conversions/ordered_float.rs @@ -53,15 +53,17 @@ use crate::conversion::IntoPyObject; use crate::exceptions::PyValueError; -use crate::types::{any::PyAnyMethods, PyFloat}; -use crate::{Bound, FromPyObject, PyAny, PyResult, Python}; +use crate::types::PyFloat; +use crate::{Borrowed, Bound, FromPyObject, PyAny, Python}; use ordered_float::{NotNan, OrderedFloat}; use std::convert::Infallible; macro_rules! float_conversions { ($wrapper:ident, $float_type:ty, $constructor:expr) => { - impl FromPyObject<'_> for $wrapper<$float_type> { - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { + impl<'a, 'py> FromPyObject<'a, 'py> for $wrapper<$float_type> { + type Error = <$float_type as FromPyObject<'a, 'py>>::Error; + + fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { let val: $float_type = obj.extract()?; $constructor(val) } @@ -101,6 +103,7 @@ mod test_ordered_float { use super::*; use crate::ffi::c_str; use crate::py_run; + use crate::types::PyAnyMethods; #[cfg(not(target_arch = "wasm32"))] use proptest::prelude::*; @@ -294,7 +297,7 @@ mod test_ordered_float { Python::attach(|py| { let nan_py = py.eval(c_str!("float('nan')"), None, None).unwrap(); - let nan_rs: PyResult> = nan_py.extract(); + let nan_rs: Result, _> = nan_py.extract(); assert!(nan_rs.is_err()); }) diff --git a/src/conversions/rust_decimal.rs b/src/conversions/rust_decimal.rs index b533f97c5a5..d67a076869b 100644 --- a/src/conversions/rust_decimal.rs +++ b/src/conversions/rust_decimal.rs @@ -55,12 +55,14 @@ use crate::sync::PyOnceLock; use crate::types::any::PyAnyMethods; use crate::types::string::PyStringMethods; use crate::types::PyType; -use crate::{Bound, FromPyObject, Py, PyAny, PyErr, PyResult, Python}; +use crate::{Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, PyResult, Python}; use rust_decimal::Decimal; use std::str::FromStr; -impl FromPyObject<'_> for Decimal { - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for Decimal { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { // use the string representation to not be lossy if let Ok(val) = obj.extract() { Ok(Decimal::new(val, 0)) diff --git a/src/conversions/smallvec.rs b/src/conversions/smallvec.rs index e6e7262590f..525a8845754 100644 --- a/src/conversions/smallvec.rs +++ b/src/conversions/smallvec.rs @@ -15,14 +15,15 @@ //! //! Note that you must use compatible versions of smallvec and PyO3. //! The required smallvec version may vary based on the version of PyO3. -use crate::conversion::IntoPyObject; +use crate::conversion::{FromPyObjectOwned, IntoPyObject}; use crate::exceptions::PyTypeError; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::types::any::PyAnyMethods; use crate::types::{PySequence, PyString}; -use crate::PyErr; -use crate::{err::DowncastError, ffi, Bound, FromPyObject, PyAny, PyResult, Python}; +use crate::{ + err::DowncastError, ffi, Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, Python, +}; use smallvec::{Array, SmallVec}; impl<'py, A> IntoPyObject<'py> for SmallVec @@ -70,12 +71,14 @@ where } } -impl<'py, A> FromPyObject<'py> for SmallVec +impl<'py, A> FromPyObject<'_, 'py> for SmallVec where A: Array, - A::Item: FromPyObject<'py>, + A::Item: FromPyObjectOwned<'py>, { - fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { if obj.is_instance_of::() { return Err(PyTypeError::new_err("Can't extract `str` to `SmallVec`")); } @@ -88,10 +91,10 @@ where } } -fn extract_sequence<'py, A>(obj: &Bound<'py, PyAny>) -> PyResult> +fn extract_sequence<'py, A>(obj: Borrowed<'_, 'py, PyAny>) -> PyResult> where A: Array, - A::Item: FromPyObject<'py>, + A::Item: FromPyObjectOwned<'py>, { // Types that pass `PySequence_Check` usually implement enough of the sequence protocol // to support this function and if not, we will only fail extraction safely. @@ -99,13 +102,13 @@ where if ffi::PySequence_Check(obj.as_ptr()) != 0 { obj.cast_unchecked::() } else { - return Err(DowncastError::new(obj, "Sequence").into()); + return Err(DowncastError::new_from_borrowed(obj, "Sequence").into()); } }; let mut sv = SmallVec::with_capacity(seq.len().unwrap_or(0)); for item in seq.try_iter()? { - sv.push(item?.extract::()?); + sv.push(item?.extract::().map_err(Into::into)?); } Ok(sv) } diff --git a/src/conversions/std/array.rs b/src/conversions/std/array.rs index a7f2d953284..7da9f13dc8b 100644 --- a/src/conversions/std/array.rs +++ b/src/conversions/std/array.rs @@ -1,9 +1,8 @@ -use crate::conversion::IntoPyObject; -use crate::instance::Bound; +use crate::conversion::{FromPyObjectOwned, IntoPyObject}; use crate::types::any::PyAnyMethods; use crate::types::PySequence; use crate::{err::DowncastError, ffi, FromPyObject, PyAny, PyResult, Python}; -use crate::{exceptions, PyErr}; +use crate::{exceptions, Borrowed, Bound, PyErr}; impl<'py, T, const N: usize> IntoPyObject<'py> for [T; N] where @@ -37,18 +36,20 @@ where } } -impl<'py, T, const N: usize> FromPyObject<'py> for [T; N] +impl<'py, T, const N: usize> FromPyObject<'_, 'py> for [T; N] where - T: FromPyObject<'py>, + T: FromPyObjectOwned<'py>, { - fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { create_array_from_obj(obj) } } -fn create_array_from_obj<'py, T, const N: usize>(obj: &Bound<'py, PyAny>) -> PyResult<[T; N]> +fn create_array_from_obj<'py, T, const N: usize>(obj: Borrowed<'_, 'py, PyAny>) -> PyResult<[T; N]> where - T: FromPyObject<'py>, + T: FromPyObjectOwned<'py>, { // Types that pass `PySequence_Check` usually implement enough of the sequence protocol // to support this function and if not, we will only fail extraction safely. @@ -56,14 +57,17 @@ where if ffi::PySequence_Check(obj.as_ptr()) != 0 { obj.cast_unchecked::() } else { - return Err(DowncastError::new(obj, "Sequence").into()); + return Err(DowncastError::new_from_borrowed(obj, "Sequence").into()); } }; let seq_len = seq.len()?; if seq_len != N { return Err(invalid_sequence_length(N, seq_len)); } - array_try_from_fn(|idx| seq.get_item(idx).and_then(|any| any.extract())) + array_try_from_fn(|idx| { + seq.get_item(idx) + .and_then(|any| any.extract().map_err(Into::into)) + }) } // TODO use std::array::try_from_fn, if that stabilises: diff --git a/src/conversions/std/cell.rs b/src/conversions/std/cell.rs index 17f29d37141..108df8031cd 100644 --- a/src/conversions/std/cell.rs +++ b/src/conversions/std/cell.rs @@ -1,9 +1,6 @@ use std::cell::Cell; -use crate::{ - conversion::IntoPyObject, types::any::PyAnyMethods, Bound, FromPyObject, PyAny, PyResult, - Python, -}; +use crate::{conversion::IntoPyObject, Borrowed, FromPyObject, PyAny, Python}; impl<'py, T: Copy + IntoPyObject<'py>> IntoPyObject<'py> for Cell { type Target = T::Target; @@ -33,11 +30,13 @@ impl<'py, T: Copy + IntoPyObject<'py>> IntoPyObject<'py> for &Cell { } } -impl<'py, T: FromPyObject<'py>> FromPyObject<'py> for Cell { +impl<'a, 'py, T: FromPyObject<'a, 'py>> FromPyObject<'a, 'py> for Cell { + type Error = T::Error; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = T::INPUT_TYPE; - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result { ob.extract().map(Cell::new) } } diff --git a/src/conversions/std/ipaddr.rs b/src/conversions/std/ipaddr.rs index 7e6453ed654..6fb1859d196 100644 --- a/src/conversions/std/ipaddr.rs +++ b/src/conversions/std/ipaddr.rs @@ -2,15 +2,16 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use crate::conversion::IntoPyObject; use crate::exceptions::PyValueError; -use crate::instance::Bound; use crate::sync::PyOnceLock; use crate::types::any::PyAnyMethods; use crate::types::string::PyStringMethods; use crate::types::PyType; -use crate::{intern, FromPyObject, Py, PyAny, PyErr, PyResult, Python}; +use crate::{intern, Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, Python}; -impl FromPyObject<'_> for IpAddr { - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for IpAddr { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { match obj.getattr(intern!(obj.py(), "packed")) { Ok(packed) => { if let Ok(packed) = packed.extract::<[u8; 4]>() { diff --git a/src/conversions/std/map.rs b/src/conversions/std/map.rs index fc96d211dc3..676351abf4a 100644 --- a/src/conversions/std/map.rs +++ b/src/conversions/std/map.rs @@ -3,10 +3,10 @@ use std::{cmp, collections, hash}; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::{ - conversion::IntoPyObject, + conversion::{FromPyObjectOwned, IntoPyObject}, instance::Bound, types::{any::PyAnyMethods, dict::PyDictMethods, PyDict}, - FromPyObject, PyAny, PyErr, Python, + Borrowed, FromPyObject, PyAny, PyErr, Python, }; impl<'py, K, V, H> IntoPyObject<'py> for collections::HashMap @@ -107,17 +107,22 @@ where } } -impl<'py, K, V, S> FromPyObject<'py> for collections::HashMap +impl<'py, K, V, S> FromPyObject<'_, 'py> for collections::HashMap where - K: FromPyObject<'py> + cmp::Eq + hash::Hash, - V: FromPyObject<'py>, + K: FromPyObjectOwned<'py> + cmp::Eq + hash::Hash, + V: FromPyObjectOwned<'py>, S: hash::BuildHasher + Default, { - fn extract_bound(ob: &Bound<'py, PyAny>) -> Result { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; let mut ret = collections::HashMap::with_capacity_and_hasher(dict.len(), S::default()); - for (k, v) in dict { - ret.insert(k.extract()?, v.extract()?); + for (k, v) in dict.iter() { + ret.insert( + k.extract().map_err(Into::into)?, + v.extract().map_err(Into::into)?, + ); } Ok(ret) } @@ -128,16 +133,21 @@ where } } -impl<'py, K, V> FromPyObject<'py> for collections::BTreeMap +impl<'py, K, V> FromPyObject<'_, 'py> for collections::BTreeMap where - K: FromPyObject<'py> + cmp::Ord, - V: FromPyObject<'py>, + K: FromPyObjectOwned<'py> + cmp::Ord, + V: FromPyObjectOwned<'py>, { - fn extract_bound(ob: &Bound<'py, PyAny>) -> Result { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { let dict = ob.cast::()?; let mut ret = collections::BTreeMap::new(); - for (k, v) in dict { - ret.insert(k.extract()?, v.extract()?); + for (k, v) in dict.iter() { + ret.insert( + k.extract().map_err(Into::into)?, + v.extract().map_err(Into::into)?, + ); } Ok(ret) } diff --git a/src/conversions/std/num.rs b/src/conversions/std/num.rs index 42a65a27929..986179ad6d3 100644 --- a/src/conversions/std/num.rs +++ b/src/conversions/std/num.rs @@ -3,9 +3,8 @@ use crate::conversion::IntoPyObject; use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; -use crate::types::any::PyAnyMethods; use crate::types::{PyBytes, PyInt}; -use crate::{exceptions, ffi, Bound, FromPyObject, PyAny, PyErr, PyResult, Python}; +use crate::{exceptions, ffi, Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult, Python}; use std::convert::Infallible; use std::ffi::c_long; use std::num::{ @@ -51,11 +50,13 @@ macro_rules! int_fits_larger_int { } } - impl FromPyObject<'_> for $rust_type { + impl FromPyObject<'_, '_> for $rust_type { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = <$larger_type>::INPUT_TYPE; - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let val: $larger_type = obj.extract()?; <$rust_type>::try_from(val) .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) @@ -129,17 +130,14 @@ macro_rules! int_convert_u64_or_i64 { fn into_pyobject(self, py: Python<'py>) -> Result { (*self).into_pyobject(py) } - - #[cfg(feature = "experimental-inspect")] - fn type_output() -> TypeInfo { - TypeInfo::builtin("int") - } } - impl FromPyObject<'_> for $rust_type { + impl FromPyObject<'_, '_> for $rust_type { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "int"; - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<$rust_type> { + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<$rust_type, Self::Error> { extract_int!(obj, !0, $pylong_as_ll_or_ull, $force_index_call) } @@ -194,11 +192,13 @@ macro_rules! int_fits_c_long { } } - impl<'py> FromPyObject<'py> for $rust_type { + impl<'py> FromPyObject<'_, 'py> for $rust_type { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "int"; - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { let val: c_long = extract_int!(obj, -1, ffi::PyLong_AsLong)?; <$rust_type>::try_from(val) .map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) @@ -277,11 +277,13 @@ impl<'py> IntoPyObject<'py> for &'_ u8 { } } -impl FromPyObject<'_> for u8 { +impl<'py> FromPyObject<'_, 'py> for u8 { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "int"; - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { + fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result { let val: c_long = extract_int!(obj, -1, ffi::PyLong_AsLong)?; u8::try_from(val).map_err(|e| exceptions::PyOverflowError::new_err(e.to_string())) } @@ -408,11 +410,13 @@ mod fast_128bit_int_conversion { } } - impl FromPyObject<'_> for $rust_type { + impl FromPyObject<'_, '_> for $rust_type { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "int"; - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<$rust_type> { + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<$rust_type, Self::Error> { let num = unsafe { ffi::PyNumber_Index(ob.as_ptr()).assume_owned_or_err(ob.py())? }; let mut buffer = [0u8; std::mem::size_of::<$rust_type>()]; @@ -474,6 +478,7 @@ mod fast_128bit_int_conversion { #[cfg(any(Py_LIMITED_API, GraalPy))] mod slow_128bit_int_conversion { use super::*; + use crate::types::any::PyAnyMethods as _; const SHIFT: usize = 64; // for 128bit Integers @@ -526,11 +531,13 @@ mod slow_128bit_int_conversion { } } - impl FromPyObject<'_> for $rust_type { + impl FromPyObject<'_, '_> for $rust_type { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "int"; - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<$rust_type> { + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result<$rust_type, Self::Error> { let py = ob.py(); unsafe { let lower = err_if_invalid_value( @@ -614,11 +621,13 @@ macro_rules! nonzero_int_impl { } } - impl FromPyObject<'_> for $nonzero_type { + impl FromPyObject<'_, '_> for $nonzero_type { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = <$primitive_type>::INPUT_TYPE; - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let val: $primitive_type = obj.extract()?; <$nonzero_type>::try_from(val) .map_err(|_| exceptions::PyValueError::new_err("invalid zero value")) @@ -648,6 +657,7 @@ nonzero_int_impl!(NonZeroUsize, usize); #[cfg(test)] mod test_128bit_integers { use super::*; + use crate::types::PyAnyMethods; #[cfg(not(target_arch = "wasm32"))] use crate::types::PyDict; diff --git a/src/conversions/std/option.rs b/src/conversions/std/option.rs index ae0ec441c61..8cec88b4e6b 100644 --- a/src/conversions/std/option.rs +++ b/src/conversions/std/option.rs @@ -1,7 +1,7 @@ use crate::{ - conversion::IntoPyObject, types::any::PyAnyMethods, Bound, BoundObject, FromPyObject, PyAny, - PyResult, Python, + conversion::IntoPyObject, types::any::PyAnyMethods, BoundObject, FromPyObject, PyAny, Python, }; +use crate::{Borrowed, Bound}; impl<'py, T> IntoPyObject<'py> for Option where @@ -37,11 +37,13 @@ where } } -impl<'py, T> FromPyObject<'py> for Option +impl<'a, 'py, T> FromPyObject<'a, 'py> for Option where - T: FromPyObject<'py>, + T: FromPyObject<'a, 'py>, { - fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult { + type Error = T::Error; + + fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { if obj.is_none() { Ok(None) } else { diff --git a/src/conversions/std/osstr.rs b/src/conversions/std/osstr.rs index acc6f2bab7f..3c480526d83 100644 --- a/src/conversions/std/osstr.rs +++ b/src/conversions/std/osstr.rs @@ -2,7 +2,7 @@ use crate::conversion::IntoPyObject; use crate::ffi_ptr_ext::FfiPtrExt; use crate::instance::Bound; use crate::types::PyString; -use crate::{ffi, FromPyObject, PyAny, PyResult, Python}; +use crate::{ffi, Borrowed, FromPyObject, PyAny, PyErr, Python}; use std::borrow::Cow; use std::convert::Infallible; use std::ffi::{OsStr, OsString}; @@ -70,8 +70,10 @@ impl<'py> IntoPyObject<'py> for &&OsStr { // There's no FromPyObject implementation for &OsStr because albeit possible on Unix, this would // be impossible to implement on Windows. Hence it's omitted entirely -impl FromPyObject<'_> for OsString { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for OsString { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let pystring = ob.cast::()?; #[cfg(not(windows))] @@ -97,8 +99,6 @@ impl FromPyObject<'_> for OsString { #[cfg(windows)] { - use crate::types::string::PyStringMethods; - // Take the quick and easy shortcut if UTF-8 if let Ok(utf8_string) = pystring.to_cow() { return Ok(utf8_string.into_owned().into()); @@ -169,7 +169,7 @@ impl<'py> IntoPyObject<'py> for &OsString { #[cfg(test)] mod tests { - use crate::types::{PyAnyMethods, PyString, PyStringMethods}; + use crate::types::{PyString, PyStringMethods}; use crate::{BoundObject, IntoPyObject, Python}; use std::fmt::Debug; use std::{ @@ -181,6 +181,7 @@ mod tests { #[cfg(not(windows))] fn test_non_utf8_conversion() { Python::attach(|py| { + use crate::types::PyAnyMethods; #[cfg(not(target_os = "wasi"))] use std::os::unix::ffi::OsStrExt; #[cfg(target_os = "wasi")] diff --git a/src/conversions/std/path.rs b/src/conversions/std/path.rs index 17cad8f2694..4d60facd186 100644 --- a/src/conversions/std/path.rs +++ b/src/conversions/std/path.rs @@ -1,17 +1,18 @@ use crate::conversion::IntoPyObject; use crate::ffi_ptr_ext::FfiPtrExt; -use crate::instance::Bound; use crate::sync::PyOnceLock; use crate::types::any::PyAnyMethods; -use crate::{ffi, FromPyObject, Py, PyAny, PyErr, PyResult, Python}; +use crate::{ffi, Borrowed, Bound, FromPyObject, Py, PyAny, PyErr, Python}; use std::borrow::Cow; use std::ffi::OsString; use std::path::{Path, PathBuf}; // See osstr.rs for why there's no FromPyObject impl for &Path -impl FromPyObject<'_> for PathBuf { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for PathBuf { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { // We use os.fspath to get the underlying path as bytes or str let path = unsafe { ffi::PyOS_FSPath(ob.as_ptr()).assume_owned_or_err(ob.py())? }; Ok(path.extract::()?.into()) @@ -99,6 +100,7 @@ mod tests { #[cfg(not(windows))] fn test_non_utf8_conversion() { Python::attach(|py| { + use crate::types::PyAnyMethods; use std::ffi::OsStr; #[cfg(not(target_os = "wasi"))] use std::os::unix::ffi::OsStrExt; diff --git a/src/conversions/std/set.rs b/src/conversions/std/set.rs index 4d4073101a0..58c38f6ca1c 100644 --- a/src/conversions/std/set.rs +++ b/src/conversions/std/set.rs @@ -3,15 +3,14 @@ use std::{cmp, collections, hash}; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::{ - conversion::IntoPyObject, - instance::Bound, + conversion::{FromPyObjectOwned, IntoPyObject}, types::{ any::PyAnyMethods, frozenset::PyFrozenSetMethods, set::{try_new_from_iter, PySetMethods}, PyFrozenSet, PySet, }, - FromPyObject, PyAny, PyErr, PyResult, Python, + Borrowed, Bound, FromPyObject, PyAny, PyErr, Python, }; impl<'py, K, S> IntoPyObject<'py> for collections::HashSet @@ -53,17 +52,25 @@ where } } -impl<'py, K, S> FromPyObject<'py> for collections::HashSet +impl<'py, K, S> FromPyObject<'_, 'py> for collections::HashSet where - K: FromPyObject<'py> + cmp::Eq + hash::Hash, + K: FromPyObjectOwned<'py> + cmp::Eq + hash::Hash, S: hash::BuildHasher + Default, { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { match ob.cast::() { - Ok(set) => set.iter().map(|any| any.extract()).collect(), + Ok(set) => set + .iter() + .map(|any| any.extract().map_err(Into::into)) + .collect(), Err(err) => { if let Ok(frozen_set) = ob.cast::() { - frozen_set.iter().map(|any| any.extract()).collect() + frozen_set + .iter() + .map(|any| any.extract().map_err(Into::into)) + .collect() } else { Err(PyErr::from(err)) } @@ -114,16 +121,24 @@ where } } -impl<'py, K> FromPyObject<'py> for collections::BTreeSet +impl<'py, K> FromPyObject<'_, 'py> for collections::BTreeSet where - K: FromPyObject<'py> + cmp::Ord, + K: FromPyObjectOwned<'py> + cmp::Ord, { - fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result { match ob.cast::() { - Ok(set) => set.iter().map(|any| any.extract()).collect(), + Ok(set) => set + .iter() + .map(|any| any.extract().map_err(Into::into)) + .collect(), Err(err) => { if let Ok(frozen_set) = ob.cast::() { - frozen_set.iter().map(|any| any.extract()).collect() + frozen_set + .iter() + .map(|any| any.extract().map_err(Into::into)) + .collect() } else { Err(PyErr::from(err)) } diff --git a/src/conversions/std/slice.rs b/src/conversions/std/slice.rs index dca72b8fdf7..00c072e588f 100644 --- a/src/conversions/std/slice.rs +++ b/src/conversions/std/slice.rs @@ -5,7 +5,7 @@ use crate::inspect::types::TypeInfo; use crate::{ conversion::IntoPyObject, types::{PyByteArray, PyByteArrayMethods, PyBytes}, - Bound, PyAny, PyErr, PyResult, Python, + Bound, DowncastError, PyAny, PyErr, Python, }; impl<'a, 'py, T> IntoPyObject<'py> for &'a [T] @@ -35,8 +35,10 @@ where } } -impl<'a> crate::conversion::FromPyObjectBound<'a, '_> for &'a [u8] { - fn from_py_object_bound(obj: crate::Borrowed<'a, '_, PyAny>) -> PyResult { +impl<'a, 'py> crate::conversion::FromPyObject<'a, 'py> for &'a [u8] { + type Error = DowncastError<'a, 'py>; + + fn extract(obj: crate::Borrowed<'a, 'py, PyAny>) -> Result { Ok(obj.cast::()?.as_bytes()) } @@ -51,8 +53,10 @@ impl<'a> crate::conversion::FromPyObjectBound<'a, '_> for &'a [u8] { /// If the source object is a `bytes` object, the `Cow` will be borrowed and /// pointing into the source object, and no copying or heap allocations will happen. /// If it is a `bytearray`, its contents will be copied to an owned `Cow`. -impl<'a> crate::conversion::FromPyObjectBound<'a, '_> for Cow<'a, [u8]> { - fn from_py_object_bound(ob: crate::Borrowed<'a, '_, PyAny>) -> PyResult { +impl<'a, 'py> crate::conversion::FromPyObject<'a, 'py> for Cow<'a, [u8]> { + type Error = DowncastError<'a, 'py>; + + fn extract(ob: crate::Borrowed<'a, 'py, PyAny>) -> Result { if let Ok(bytes) = ob.cast::() { return Ok(Cow::Borrowed(bytes.as_bytes())); } diff --git a/src/conversions/std/string.rs b/src/conversions/std/string.rs index e4c804facd2..3c6a300ecdf 100644 --- a/src/conversions/std/string.rs +++ b/src/conversions/std/string.rs @@ -3,10 +3,8 @@ use std::{borrow::Cow, convert::Infallible}; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; use crate::{ - conversion::IntoPyObject, - instance::Bound, - types::{string::PyStringMethods, PyString}, - FromPyObject, PyAny, PyResult, Python, + conversion::IntoPyObject, instance::Bound, types::PyString, Borrowed, FromPyObject, PyAny, + PyErr, Python, }; impl<'py> IntoPyObject<'py> for &str { @@ -161,11 +159,13 @@ impl<'py> IntoPyObject<'py> for &String { } #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] -impl<'a> crate::conversion::FromPyObjectBound<'a, '_> for &'a str { +impl<'a> crate::conversion::FromPyObject<'a, '_> for &'a str { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "str"; - fn from_py_object_bound(ob: crate::Borrowed<'a, '_, PyAny>) -> PyResult { + fn extract(ob: crate::Borrowed<'a, '_, PyAny>) -> Result { ob.cast::()?.to_str() } @@ -175,11 +175,13 @@ impl<'a> crate::conversion::FromPyObjectBound<'a, '_> for &'a str { } } -impl<'a> crate::conversion::FromPyObjectBound<'a, '_> for Cow<'a, str> { +impl<'a> crate::conversion::FromPyObject<'a, '_> for Cow<'a, str> { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "str"; - fn from_py_object_bound(ob: crate::Borrowed<'a, '_, PyAny>) -> PyResult { + fn extract(ob: crate::Borrowed<'a, '_, PyAny>) -> Result { ob.cast::()?.to_cow() } @@ -191,11 +193,13 @@ impl<'a> crate::conversion::FromPyObjectBound<'a, '_> for Cow<'a, str> { /// Allows extracting strings from Python objects. /// Accepts Python `str` and `unicode` objects. -impl FromPyObject<'_> for String { +impl FromPyObject<'_, '_> for String { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "str"; - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { obj.cast::()?.to_cow().map(Cow::into_owned) } @@ -205,11 +209,13 @@ impl FromPyObject<'_> for String { } } -impl FromPyObject<'_> for char { +impl FromPyObject<'_, '_> for char { + type Error = PyErr; + #[cfg(feature = "experimental-inspect")] const INPUT_TYPE: &'static str = "str"; - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let s = obj.cast::()?.to_cow()?; let mut iter = s.chars(); if let (Some(ch), None) = (iter.next(), iter.next()) { diff --git a/src/conversions/std/time.rs b/src/conversions/std/time.rs index d2e2ed7c237..18a7e7dec1c 100644 --- a/src/conversions/std/time.rs +++ b/src/conversions/std/time.rs @@ -12,8 +12,10 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; const SECONDS_PER_DAY: u64 = 24 * 60 * 60; -impl FromPyObject<'_> for Duration { - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for Duration { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let delta = obj.cast::()?; #[cfg(not(Py_LIMITED_API))] let (days, seconds, microseconds) = { @@ -87,8 +89,10 @@ impl<'py> IntoPyObject<'py> for &Duration { // // TODO: it might be nice to investigate using timestamps anyway, at least when the datetime is a safe range. -impl FromPyObject<'_> for SystemTime { - fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for SystemTime { + type Error = PyErr; + + fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result { let duration_since_unix_epoch: Duration = obj.sub(unix_epoch_py(obj.py())?)?.extract()?; UNIX_EPOCH .checked_add(duration_since_unix_epoch) diff --git a/src/conversions/time.rs b/src/conversions/time.rs index d9161f9553a..562eebd353e 100644 --- a/src/conversions/time.rs +++ b/src/conversions/time.rs @@ -58,7 +58,7 @@ use crate::types::datetime::{PyDateAccess, PyDeltaAccess}; use crate::types::{PyAnyMethods, PyDate, PyDateTime, PyDelta, PyNone, PyTime, PyTzInfo}; #[cfg(not(Py_LIMITED_API))] use crate::types::{PyTimeAccess, PyTzInfoAccess}; -use crate::{Bound, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python}; +use crate::{Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, PyResult, Python}; use time::{ Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcDateTime, UtcOffset, }; @@ -180,8 +180,10 @@ impl<'py> IntoPyObject<'py> for Duration { } } -impl FromPyObject<'_> for Duration { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for Duration { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { #[cfg(not(Py_LIMITED_API))] let (days, seconds, microseconds) = { let delta = ob.cast::()?; @@ -223,8 +225,10 @@ impl<'py> IntoPyObject<'py> for Date { } } -impl FromPyObject<'_> for Date { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { +impl FromPyObject<'_, '_> for Date { + type Error = PyErr; + + fn extract(ob: Borrowed<'_, '_, PyAny>) -> Result { let (year, month, day) = { #[cfg(not(Py_LIMITED_API))] { @@ -264,8 +268,10 @@ impl<'py> IntoPyObject<'py> for Time { } } -impl FromPyObject<'_> for Time { - fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult