Added `ToPyObject` and `IntoPy<PyObject>` impls for `PyBackedStr`. (#4205)
* Added `ToPyObject` and `Into<PyObject>` impls for `PyBackedStr`. * Create 4205.added.md * Added attributes limiting the `ToPyObject` and `IntoPy<PyObject>` impls to the case where `cfg(any(Py_3_10, not(Py_LIMITED_API)))`. When this cfg does not apply, the conversion is less trivial since the `storage` is actually `PyBytes`, not `PyString`. * Fixed imports format. * Updated the `ToPyObject` and `IntoPy<PyObject>` impls to support the `cfg(not(any(Py_3_10, not(Py_LIMITED_API))))` case by converting the `PyBytes` back to `PyString`. * Added `ToPyObject` and `IntoPy<PyObject>` impls for `PyBackedBytes`. * Added tests for the `PyBackedBytes` conversion impls. * Updated newsfragment entry to include the `PyBackedBytes` impls. * Changed the `IntoPy` and `ToPyObject` impls for `PyBackedBytes` to produce `PyBytes` regardless of the backing variant. Updated tests to demonstrate this. * retrigger checks * Updated `PyBackedStr` conversion tests to extract the result as a `PyBackedStr` instead of `&str` since the latter is not supported under some `cfg`'s. * Fixed `IntoPy<PyObject> for PyBackedBytes` impl to create `bytes` for both storage types as intended. Updated test to properly catch the error. --------- Co-authored-by: jrudolph <jrudolph@anl.gov>
This commit is contained in:
parent
a7a5c10b8a
commit
5d47c4ae4c
|
@ -0,0 +1 @@
|
||||||
|
Added `ToPyObject` and `IntoPy<PyObject>` impls for `PyBackedStr` and `PyBackedBytes`.
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
any::PyAnyMethods, bytearray::PyByteArrayMethods, bytes::PyBytesMethods,
|
any::PyAnyMethods, bytearray::PyByteArrayMethods, bytes::PyBytesMethods,
|
||||||
string::PyStringMethods, PyByteArray, PyBytes, PyString,
|
string::PyStringMethods, PyByteArray, PyBytes, PyString,
|
||||||
},
|
},
|
||||||
Bound, DowncastError, FromPyObject, Py, PyAny, PyErr, PyResult,
|
Bound, DowncastError, FromPyObject, IntoPy, Py, PyAny, PyErr, PyResult, Python, ToPyObject,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A wrapper around `str` where the storage is owned by a Python `bytes` or `str` object.
|
/// A wrapper around `str` where the storage is owned by a Python `bytes` or `str` object.
|
||||||
|
@ -85,6 +85,28 @@ impl FromPyObject<'_> for PyBackedStr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
|
||||||
|
fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
|
||||||
|
PyString::new_bound(py, self).into_any().unbind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
|
||||||
|
fn into_py(self, py: Python<'_>) -> Py<PyAny> {
|
||||||
|
PyString::new_bound(py, &self).into_any().unbind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A wrapper around `[u8]` where the storage is either owned by a Python `bytes` object, or a Rust `Box<[u8]>`.
|
/// 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.
|
/// This type gives access to the underlying data via a `Deref` implementation.
|
||||||
|
@ -181,6 +203,24 @@ impl FromPyObject<'_> for PyBackedBytes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToPyObject for PyBackedBytes {
|
||||||
|
fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
|
||||||
|
match &self.storage {
|
||||||
|
PyBackedBytesStorage::Python(bytes) => bytes.to_object(py),
|
||||||
|
PyBackedBytesStorage::Rust(bytes) => PyBytes::new_bound(py, bytes).into_any().unbind(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoPy<Py<PyAny>> for PyBackedBytes {
|
||||||
|
fn into_py(self, py: Python<'_>) -> Py<PyAny> {
|
||||||
|
match self.storage {
|
||||||
|
PyBackedBytesStorage::Python(bytes) => bytes.into_any(),
|
||||||
|
PyBackedBytesStorage::Rust(bytes) => PyBytes::new_bound(py, &bytes).into_any().unbind(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_traits {
|
macro_rules! impl_traits {
|
||||||
($slf:ty, $equiv:ty) => {
|
($slf:ty, $equiv:ty) => {
|
||||||
impl std::fmt::Debug for $slf {
|
impl std::fmt::Debug for $slf {
|
||||||
|
@ -288,6 +328,30 @@ mod test {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn py_backed_str_to_object() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let orig_str = PyString::new_bound(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");
|
||||||
|
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
|
||||||
|
assert!(new_str.is(&orig_str));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn py_backed_str_into_py() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let orig_str = PyString::new_bound(py, "hello");
|
||||||
|
let py_backed_str = orig_str.extract::<PyBackedStr>().unwrap();
|
||||||
|
let new_str = py_backed_str.into_py(py);
|
||||||
|
assert_eq!(new_str.extract::<PyBackedStr>(py).unwrap(), "hello");
|
||||||
|
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
|
||||||
|
assert!(new_str.is(&orig_str));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn py_backed_bytes_empty() {
|
fn py_backed_bytes_empty() {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
|
@ -324,6 +388,34 @@ mod test {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn py_backed_bytes_into_py() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let orig_bytes = PyBytes::new_bound(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_py(py).is(&orig_bytes));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rust_backed_bytes_into_py() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let orig_bytes = PyByteArray::new_bound(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);
|
||||||
|
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);
|
||||||
|
assert!(&into_py.is_exact_instance_of::<PyBytes>());
|
||||||
|
assert_eq!(&into_py.extract::<PyBackedBytes>().unwrap(), b"abcde");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_backed_types_send_sync() {
|
fn test_backed_types_send_sync() {
|
||||||
fn is_send<T: Send>() {}
|
fn is_send<T: Send>() {}
|
||||||
|
|
Loading…
Reference in New Issue