Skip to content

add IntoPyObjectExt trait #4708

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 44 additions & 12 deletions src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ pub trait IntoPy<T>: Sized {
///
/// It functions similarly to std's [`TryInto`] trait, but requires a [GIL token](Python)
/// as an argument.
///
/// The [`into_pyobject`][IntoPyObject::into_pyobject] method is designed for maximum flexibility and efficiency; it
/// - allows for a concrete Python type to be returned (the [`Target`][IntoPyObject::Target] associated type)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to finish this list, had to run from my pc just now

#[cfg_attr(
diagnostic_namespace,
diagnostic::on_unimplemented(
Expand Down Expand Up @@ -227,12 +230,7 @@ pub trait IntoPyObject<'py>: Sized {
I: IntoIterator<Item = Self> + AsRef<[Self]>,
I::IntoIter: ExactSizeIterator<Item = Self>,
{
let mut iter = iter.into_iter().map(|e| {
e.into_pyobject(py)
.map(BoundObject::into_any)
.map(BoundObject::into_bound)
.map_err(Into::into)
});
let mut iter = iter.into_iter().map(|e| e.into_py_any(py));
let list = crate::types::list::try_new_from_iter(py, &mut iter);
list.map(Bound::into_any)
}
Expand All @@ -250,12 +248,7 @@ pub trait IntoPyObject<'py>: Sized {
I: IntoIterator<Item = Self> + AsRef<[<Self as private::Reference>::BaseType]>,
I::IntoIter: ExactSizeIterator<Item = Self>,
{
let mut iter = iter.into_iter().map(|e| {
e.into_pyobject(py)
.map(BoundObject::into_any)
.map(BoundObject::into_bound)
.map_err(Into::into)
});
let mut iter = iter.into_iter().map(|e| e.into_py_any(py));
let list = crate::types::list::try_new_from_iter(py, &mut iter);
list.map(Bound::into_any)
}
Expand Down Expand Up @@ -347,6 +340,45 @@ where
}
}

mod into_pyobject_ext {
pub trait Sealed {}
impl<'py, T> Sealed for T where T: super::IntoPyObject<'py> {}
}

/// Convenience methods for common usages of [`IntoPyObject`]. Every type that implements
/// [`IntoPyObject`] also implements this trait.
///
/// These methods:
/// - Drop type information from the output, returning a `PyAny` object.
/// - Always convert the `Error` type to `PyErr`, which may incur a performance penalty but it
/// more convenient in contexts where the `?` operator would produce a `PyErr` anyway.
pub trait IntoPyObjectExt<'py>: IntoPyObject<'py> + into_pyobject_ext::Sealed {
/// Converts `self` into an owned Python object, dropping type information.
#[inline]
fn into_py_any(self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
self.into_pyobject(py)
.map(|obj| obj.into_any().into_bound())
.map_err(Into::into)
}

/// Converts `self` into a Python object, dropping type information.
///
/// This is typically only useful when the resulting output is going to be passed
/// to another function that only needs to borrow the output.
#[inline]
fn into_bound_object_py_any(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this method is open to bikeshedding, I think I'm happy with this but there's probably a better one.

self,
py: Python<'py>,
) -> PyResult<<Self::Output as BoundObject<'py, Self::Target>>::Any> {
match self.into_pyobject(py) {
Ok(obj) => Ok(obj.into_any()),
Err(err) => Err(err.into()),
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think into_unbound_py_any would also probably be useful (there's a customer in Coroutine::new it looks like).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems reasonable to me; it certainly is common to .unbind() in pydantic-core (so that data can be stored in structs, mostly).

}

impl<'py, T> IntoPyObjectExt<'py> for T where T: IntoPyObject<'py> {}

/// Extract a type from a Python object.
///
///
Expand Down
28 changes: 6 additions & 22 deletions src/conversions/either.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
#[cfg(feature = "experimental-inspect")]
use crate::inspect::types::TypeInfo;
use crate::{
conversion::IntoPyObject, exceptions::PyTypeError, types::any::PyAnyMethods, Bound,
BoundObject, FromPyObject, PyAny, PyErr, PyObject, PyResult, Python,
exceptions::PyTypeError, types::any::PyAnyMethods, Bound, BoundObject, FromPyObject,
IntoPyObject, IntoPyObjectExt, PyAny, PyErr, PyObject, PyResult, Python,
};
#[allow(deprecated)]
use crate::{IntoPy, ToPyObject};
Expand Down Expand Up @@ -82,16 +82,8 @@ where

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
Either::Left(l) => l
.into_pyobject(py)
.map(BoundObject::into_any)
.map(BoundObject::into_bound)
.map_err(Into::into),
Either::Right(r) => r
.into_pyobject(py)
.map(BoundObject::into_any)
.map(BoundObject::into_bound)
.map_err(Into::into),
Either::Left(l) => l.into_py_any(py),
Either::Right(r) => r.into_py_any(py),
}
}
}
Expand All @@ -108,16 +100,8 @@ where

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self {
Either::Left(l) => l
.into_pyobject(py)
.map(BoundObject::into_any)
.map(BoundObject::into_bound)
.map_err(Into::into),
Either::Right(r) => r
.into_pyobject(py)
.map(BoundObject::into_any)
.map(BoundObject::into_bound)
.map_err(Into::into),
Either::Left(l) => l.into_py_any(py),
Either::Right(r) => r.into_py_any(py),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@
#![doc = concat!("[Features chapter of the guide]: https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/features.html#features-reference \"Features Reference - PyO3 user guide\"")]
//! [`Ungil`]: crate::marker::Ungil
pub use crate::class::*;
pub use crate::conversion::{AsPyPointer, FromPyObject, IntoPyObject};
pub use crate::conversion::{AsPyPointer, FromPyObject, IntoPyObject, IntoPyObjectExt};
#[allow(deprecated)]
pub use crate::conversion::{IntoPy, ToPyObject};
pub use crate::err::{DowncastError, DowncastIntoError, PyErr, PyErrArguments, PyResult, ToPyErr};
Expand Down
59 changes: 9 additions & 50 deletions src/types/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,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, Borrowed, BoundObject, Python};
use crate::{err, ffi, Borrowed, BoundObject, IntoPyObjectExt, Python};
use std::cell::UnsafeCell;
use std::cmp::Ordering;
use std::os::raw::c_int;
Expand Down Expand Up @@ -1000,11 +1000,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
.into_pyobject(py)
.map_err(Into::into)?
.as_borrowed(),
value
.into_pyobject(py)
.map_err(Into::into)?
.into_any()
.as_borrowed(),
value.into_bound_object_py_any(py)?.as_borrowed(),
)
}

Expand Down Expand Up @@ -1055,14 +1051,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
}

let py = self.py();
inner(
self,
other
.into_pyobject(py)
.map_err(Into::into)?
.into_any()
.as_borrowed(),
)
inner(self, other.into_bound_object_py_any(py)?.as_borrowed())
}

fn rich_compare<O>(&self, other: O, compare_op: CompareOp) -> PyResult<Bound<'py, PyAny>>
Expand All @@ -1083,11 +1072,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
let py = self.py();
inner(
self,
other
.into_pyobject(py)
.map_err(Into::into)?
.into_any()
.as_borrowed(),
other.into_bound_object_py_any(py)?.as_borrowed(),
compare_op,
)
}
Expand Down Expand Up @@ -1196,14 +1181,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
}

let py = self.py();
inner(
self,
other
.into_pyobject(py)
.map_err(Into::into)?
.into_any()
.as_borrowed(),
)
inner(self, other.into_bound_object_py_any(py)?.as_borrowed())
}

/// Computes `self ** other % modulus` (`pow(self, other, modulus)`).
Expand All @@ -1227,16 +1205,8 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
let py = self.py();
inner(
self,
other
.into_pyobject(py)
.map_err(Into::into)?
.into_any()
.as_borrowed(),
modulus
.into_pyobject(py)
.map_err(Into::into)?
.into_any()
.as_borrowed(),
other.into_bound_object_py_any(py)?.as_borrowed(),
modulus.into_bound_object_py_any(py)?.as_borrowed(),
)
}

Expand Down Expand Up @@ -1383,11 +1353,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
.map_err(Into::into)?
.into_any()
.as_borrowed(),
value
.into_pyobject(py)
.map_err(Into::into)?
.into_any()
.as_borrowed(),
value.into_bound_object_py_any(py)?.as_borrowed(),
)
}

Expand Down Expand Up @@ -1572,14 +1538,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
}

let py = self.py();
inner(
self,
value
.into_pyobject(py)
.map_err(Into::into)?
.into_any()
.as_borrowed(),
)
inner(self, value.into_bound_object_py_any(py)?.as_borrowed())
}

#[cfg(not(any(PyPy, GraalPy)))]
Expand Down
Loading