Skip to content

Commit

Permalink
add missing IntoPyObject impls
Browse files Browse the repository at this point in the history
  • Loading branch information
Icxolu committed Oct 3, 2024
1 parent 26abde5 commit e01f764
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 24 deletions.
25 changes: 21 additions & 4 deletions src/conversions/std/osstr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ impl<'py> IntoPyObject<'py> for &OsStr {
}
}

impl<'py> IntoPyObject<'py> for &&OsStr {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}

// 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

Expand Down Expand Up @@ -208,8 +219,10 @@ impl<'py> IntoPyObject<'py> for &OsString {

#[cfg(test)]
mod tests {
use crate::prelude::IntoPyObject;
use crate::types::{PyAnyMethods, PyStringMethods};
use crate::{types::PyString, IntoPy, PyObject, Python, ToPyObject};
use crate::BoundObject;
use crate::{types::PyString, IntoPy, PyObject, Python};
use std::fmt::Debug;
use std::{
borrow::Cow,
Expand Down Expand Up @@ -239,9 +252,13 @@ mod tests {
#[test]
fn test_topyobject_roundtrip() {
Python::with_gil(|py| {
fn test_roundtrip<T: ToPyObject + AsRef<OsStr> + Debug>(py: Python<'_>, obj: T) {
let pyobject = obj.to_object(py);
let pystring = pyobject.downcast_bound::<PyString>(py).unwrap();
fn test_roundtrip<'py, T>(py: Python<'py>, obj: T)
where
T: IntoPyObject<'py> + AsRef<OsStr> + Debug + Clone,
T::Error: Debug,
{
let pyobject = obj.clone().into_pyobject(py).unwrap().into_any();
let pystring = pyobject.as_borrowed().downcast::<PyString>().unwrap();
assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy());
let roundtripped_obj: OsString = pystring.extract().unwrap();
assert_eq!(obj.as_ref(), roundtripped_obj.as_os_str());
Expand Down
25 changes: 21 additions & 4 deletions src/conversions/std/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ impl<'py> IntoPyObject<'py> for &Path {
}
}

impl<'py> IntoPyObject<'py> for &&Path {
type Target = PyString;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}

impl<'a> ToPyObject for Cow<'a, Path> {
#[inline]
fn to_object(&self, py: Python<'_>) -> PyObject {
Expand Down Expand Up @@ -125,8 +136,10 @@ impl<'py> IntoPyObject<'py> for &PathBuf {

#[cfg(test)]
mod tests {
use crate::prelude::IntoPyObject;
use crate::types::{PyAnyMethods, PyStringMethods};
use crate::{types::PyString, IntoPy, PyObject, Python, ToPyObject};
use crate::BoundObject;
use crate::{types::PyString, IntoPy, PyObject, Python};
use std::borrow::Cow;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -155,9 +168,13 @@ mod tests {
#[test]
fn test_topyobject_roundtrip() {
Python::with_gil(|py| {
fn test_roundtrip<T: ToPyObject + AsRef<Path> + Debug>(py: Python<'_>, obj: T) {
let pyobject = obj.to_object(py);
let pystring = pyobject.downcast_bound::<PyString>(py).unwrap();
fn test_roundtrip<'py, T>(py: Python<'py>, obj: T)
where
T: IntoPyObject<'py> + AsRef<Path> + Debug + Clone,
T::Error: Debug,
{
let pyobject = obj.clone().into_pyobject(py).unwrap().into_any();
let pystring = pyobject.as_borrowed().downcast::<PyString>().unwrap();
assert_eq!(pystring.to_string_lossy(), obj.as_ref().to_string_lossy());
let roundtripped_obj: PathBuf = pystring.extract().unwrap();
assert_eq!(obj.as_ref(), roundtripped_obj.as_path());
Expand Down
91 changes: 79 additions & 12 deletions src/pybacked.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//! Contains types for working with Python objects that own the underlying data.
use std::{ops::Deref, ptr::NonNull, sync::Arc};
use std::{convert::Infallible, ops::Deref, ptr::NonNull, sync::Arc};

#[allow(deprecated)]
use crate::ToPyObject;
use crate::{
prelude::IntoPyObject,
types::{
any::PyAnyMethods, bytearray::PyByteArrayMethods, bytes::PyBytesMethods,
string::PyStringMethods, PyByteArray, PyBytes, PyString,
},
Bound, DowncastError, FromPyObject, IntoPy, Py, PyAny, PyErr, PyResult, Python, ToPyObject,
Bound, DowncastError, FromPyObject, IntoPy, Py, PyAny, PyErr, PyResult, Python,
};

/// A wrapper around `str` where the storage is owned by a Python `bytes` or `str` object.
Expand Down Expand Up @@ -61,7 +64,7 @@ impl TryFrom<Bound<'_, PyString>> for PyBackedStr {
let s = py_string.to_str()?;
let data = NonNull::from(s);
Ok(Self {
storage: py_string.as_any().to_owned().unbind(),
storage: py_string.into_any().unbind(),
data,
})
}
Expand All @@ -85,10 +88,11 @@ impl FromPyObject<'_> for PyBackedStr {
}
}

#[allow(deprecated)]
impl ToPyObject for PyBackedStr {
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
self.storage.clone_ref(py)
self.storage.as_any().clone_ref(py)
}
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
Expand All @@ -99,14 +103,46 @@ impl ToPyObject for PyBackedStr {
impl IntoPy<Py<PyAny>> for PyBackedStr {
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
fn into_py(self, _py: Python<'_>) -> Py<PyAny> {
self.storage
self.storage.into_any()
}
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
fn into_py(self, py: Python<'_>) -> Py<PyAny> {
PyString::new(py, &self).into_any().unbind()
}
}

impl<'py> IntoPyObject<'py> for PyBackedStr {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.storage.into_bound(py))
}

#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyString::new(py, &self).into_any())
}
}

impl<'py> IntoPyObject<'py> for &PyBackedStr {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.storage.bind(py).to_owned())
}

#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PyString::new(py, self).into_any())
}
}

/// A wrapper around `[u8]` where the storage is either owned by a Python `bytes` object, or a Rust `Box<[u8]>`.
///
/// This type gives access to the underlying data via a `Deref` implementation.
Expand Down Expand Up @@ -203,6 +239,7 @@ impl FromPyObject<'_> for PyBackedBytes {
}
}

#[allow(deprecated)]
impl ToPyObject for PyBackedBytes {
fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
match &self.storage {
Expand All @@ -221,6 +258,32 @@ impl IntoPy<Py<PyAny>> for PyBackedBytes {
}
}

impl<'py> IntoPyObject<'py> for PyBackedBytes {
type Target = PyBytes;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match self.storage {
PyBackedBytesStorage::Python(bytes) => Ok(bytes.into_bound(py)),
PyBackedBytesStorage::Rust(bytes) => Ok(PyBytes::new(py, &bytes)),
}
}
}

impl<'py> IntoPyObject<'py> for &PyBackedBytes {
type Target = PyBytes;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
match &self.storage {
PyBackedBytesStorage::Python(bytes) => Ok(bytes.bind(py).clone()),
PyBackedBytesStorage::Rust(bytes) => Ok(PyBytes::new(py, bytes)),
}
}
}

macro_rules! impl_traits {
($slf:ty, $equiv:ty) => {
impl std::fmt::Debug for $slf {
Expand Down Expand Up @@ -297,6 +360,7 @@ use impl_traits;
#[cfg(test)]
mod test {
use super::*;
use crate::prelude::IntoPyObject;
use crate::Python;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
Expand Down Expand Up @@ -329,12 +393,12 @@ mod test {
}

#[test]
fn py_backed_str_to_object() {
fn py_backed_str_into_pyobject() {
Python::with_gil(|py| {
let orig_str = PyString::new(py, "hello");
let py_backed_str = orig_str.extract::<PyBackedStr>().unwrap();
let new_str = py_backed_str.to_object(py);
assert_eq!(new_str.extract::<PyBackedStr>(py).unwrap(), "hello");
let new_str = py_backed_str.into_pyobject(py).unwrap();
assert_eq!(new_str.extract::<PyBackedStr>().unwrap(), "hello");
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
assert!(new_str.is(&orig_str));
});
Expand Down Expand Up @@ -389,25 +453,28 @@ mod test {
}

#[test]
fn py_backed_bytes_into_py() {
fn py_backed_bytes_into_pyobject() {
Python::with_gil(|py| {
let orig_bytes = PyBytes::new(py, b"abcde");
let py_backed_bytes = PyBackedBytes::from(orig_bytes.clone());
assert!(py_backed_bytes.to_object(py).is(&orig_bytes));
assert!((&py_backed_bytes)
.into_pyobject(py)
.unwrap()
.is(&orig_bytes));
assert!(py_backed_bytes.into_py(py).is(&orig_bytes));
});
}

#[test]
fn rust_backed_bytes_into_py() {
fn rust_backed_bytes_into_pyobject() {
Python::with_gil(|py| {
let orig_bytes = PyByteArray::new(py, b"abcde");
let rust_backed_bytes = PyBackedBytes::from(orig_bytes);
assert!(matches!(
rust_backed_bytes.storage,
PyBackedBytesStorage::Rust(_)
));
let to_object = rust_backed_bytes.to_object(py).into_bound(py);
let to_object = (&rust_backed_bytes).into_pyobject(py).unwrap();
assert!(&to_object.is_exact_instance_of::<PyBytes>());
assert_eq!(&to_object.extract::<PyBackedBytes>().unwrap(), b"abcde");
let into_py = rust_backed_bytes.into_py(py).into_bound(py);
Expand Down
22 changes: 22 additions & 0 deletions src/types/slice.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::err::{PyErr, PyResult};
use crate::ffi;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::prelude::IntoPyObject;
use crate::types::any::PyAnyMethods;
use crate::{Bound, PyAny, PyObject, Python, ToPyObject};
use std::convert::Infallible;

/// Represents a Python `slice`.
///
Expand Down Expand Up @@ -139,6 +141,26 @@ impl ToPyObject for PySliceIndices {
}
}

impl<'py> IntoPyObject<'py> for PySliceIndices {
type Target = PySlice;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PySlice::new(py, self.start, self.stop, self.step))
}
}

impl<'py> IntoPyObject<'py> for &PySliceIndices {
type Target = PySlice;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;

fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(PySlice::new(py, self.start, self.stop, self.step))
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/invalid_property_args.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ error[E0277]: `PhantomData<i32>` cannot be converted to a Python object
= help: the trait `IntoPyObject<'_>` is not implemented for `PhantomData<i32>`, which is required by `for<'py> PhantomData<i32>: PyO3GetField<'py>`
= note: implement `IntoPyObject` for `&PhantomData<i32>` or `IntoPyObject + Clone` for `PhantomData<i32>` to define the conversion
= help: the following other types implement trait `IntoPyObject<'py>`:
&&OsStr
&&Path
&&str
&'a (T0, T1)
&'a (T0, T1, T2)
&'a (T0, T1, T2, T3)
&'a (T0, T1, T2, T3, T4)
&'a (T0, T1, T2, T3, T4, T5)
&'a (T0, T1, T2, T3, T4, T5, T6)
&'a (T0, T1, T2, T3, T4, T5, T6, T7)
and $N others
= note: required for `PhantomData<i32>` to implement `for<'py> PyO3GetField<'py>`
note: required by a bound in `PyClassGetterGenerator::<ClassT, FieldT, Offset, false, false, false, false, false>::generate`
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/missing_intopy.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ error[E0277]: `Blah` cannot be converted to a Python object
= note: if you do not wish to have a corresponding Python type, implement it manually
= note: if you do not own `Blah` you can perform a manual conversion to one of the types in `pyo3::types::*`
= help: the following other types implement trait `IntoPyObject<'py>`:
&&OsStr
&&Path
&&str
&'a (T0, T1)
&'a (T0, T1, T2)
&'a (T0, T1, T2, T3)
&'a (T0, T1, T2, T3, T4)
&'a (T0, T1, T2, T3, T4, T5)
&'a (T0, T1, T2, T3, T4, T5, T6)
&'a (T0, T1, T2, T3, T4, T5, T6, T7)
and $N others
note: required by a bound in `UnknownReturnType::<T>::wrap`
--> src/impl_/wrap.rs
Expand Down

0 comments on commit e01f764

Please sign in to comment.