From 4c94be51a7b6885ad91b7287a4a75dacb3278102 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Mon, 27 Nov 2023 13:50:54 +0000 Subject: [PATCH] add `PyBytes::new_bound` --- guide/src/conversions/traits.md | 2 +- pytests/src/buf_and_str.rs | 7 ++- src/conversions/anyhow.rs | 2 +- src/conversions/eyre.rs | 2 +- src/conversions/num_bigint.rs | 2 +- src/conversions/std/slice.rs | 4 +- src/tests/hygiene/pymethods.rs | 8 +-- src/types/bytes.rs | 94 ++++++++++++++++++++++++++++----- tests/test_bytes.rs | 6 +-- 9 files changed, 97 insertions(+), 30 deletions(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index f657fe07..4f3342f2 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -388,7 +388,7 @@ enum RustyEnum<'a> { # } # # { -# let thing = PyBytes::new(py, b"text"); +# let thing = PyBytes::new_bound(py, b"text"); # let rust_thing: RustyEnum<'_> = thing.extract()?; # # assert_eq!( diff --git a/pytests/src/buf_and_str.rs b/pytests/src/buf_and_str.rs index 7cddc686..196126f7 100644 --- a/pytests/src/buf_and_str.rs +++ b/pytests/src/buf_and_str.rs @@ -42,10 +42,9 @@ impl BytesExtractor { } #[pyfunction] -fn return_memoryview(py: Python<'_>) -> PyResult<&PyMemoryView> { - let bytes: &PyAny = PyBytes::new(py, b"hello world").into(); - let memoryview = TryInto::try_into(bytes)?; - Ok(memoryview) +fn return_memoryview(py: Python<'_>) -> PyResult> { + let bytes = PyBytes::new_bound(py, b"hello world"); + PyMemoryView::from_bound(&bytes) } #[pymodule] diff --git a/src/conversions/anyhow.rs b/src/conversions/anyhow.rs index b362faf4..3b5aa053 100644 --- a/src/conversions/anyhow.rs +++ b/src/conversions/anyhow.rs @@ -77,7 +77,7 @@ //! let res = Python::with_gil(|py| { //! let zlib = PyModule::import(py, "zlib")?; //! let decompress = zlib.getattr("decompress")?; -//! let bytes = PyBytes::new(py, bytes); +//! let bytes = PyBytes::new_bound(py, bytes); //! let value = decompress.call1((bytes,))?; //! value.extract::>() //! })?; diff --git a/src/conversions/eyre.rs b/src/conversions/eyre.rs index f881599e..b0559ad1 100644 --- a/src/conversions/eyre.rs +++ b/src/conversions/eyre.rs @@ -76,7 +76,7 @@ //! let res = Python::with_gil(|py| { //! let zlib = PyModule::import(py, "zlib")?; //! let decompress = zlib.getattr("decompress")?; -//! let bytes = PyBytes::new(py, bytes); +//! let bytes = PyBytes::new_bound(py, bytes); //! let value = decompress.call1((bytes,))?; //! value.extract::>() //! })?; diff --git a/src/conversions/num_bigint.rs b/src/conversions/num_bigint.rs index 5cc2157d..893d3b9d 100644 --- a/src/conversions/num_bigint.rs +++ b/src/conversions/num_bigint.rs @@ -78,7 +78,7 @@ macro_rules! bigint_conversion { #[cfg(Py_LIMITED_API)] fn to_object(&self, py: Python<'_>) -> PyObject { let bytes = $to_bytes(self); - let bytes_obj = PyBytes::new(py, &bytes); + let bytes_obj = PyBytes::new_bound(py, &bytes); let kwargs = if $is_signed > 0 { let kwargs = PyDict::new(py); kwargs.set_item(crate::intern!(py, "signed"), true).unwrap(); diff --git a/src/conversions/std/slice.rs b/src/conversions/std/slice.rs index fbe2d19e..3d9351f8 100644 --- a/src/conversions/std/slice.rs +++ b/src/conversions/std/slice.rs @@ -1,10 +1,10 @@ #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; -use crate::{types::PyBytes, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject}; +use crate::{types::PyBytes, FromPyObject, IntoPy, PyAny, PyObject, PyResult, Python}; impl<'a> IntoPy for &'a [u8] { fn into_py(self, py: Python<'_>) -> PyObject { - PyBytes::new(py, self).to_object(py) + PyBytes::new_bound(py, self).unbind().into() } #[cfg(feature = "experimental-inspect")] diff --git a/src/tests/hygiene/pymethods.rs b/src/tests/hygiene/pymethods.rs index 0e39634a..15ea6759 100644 --- a/src/tests/hygiene/pymethods.rs +++ b/src/tests/hygiene/pymethods.rs @@ -24,8 +24,8 @@ impl Dummy { "Dummy" } - fn __bytes__<'py>(&self, py: crate::Python<'py>) -> &'py crate::types::PyBytes { - crate::types::PyBytes::new(py, &[0]) + fn __bytes__<'py>(&self, py: crate::Python<'py>) -> crate::Bound<'py, crate::types::PyBytes> { + crate::types::PyBytes::new_bound(py, &[0]) } fn __format__(&self, format_spec: ::std::string::String) -> ::std::string::String { @@ -420,8 +420,8 @@ impl Dummy { "Dummy" } - fn __bytes__<'py>(&self, py: crate::Python<'py>) -> &'py crate::types::PyBytes { - crate::types::PyBytes::new(py, &[0]) + fn __bytes__<'py>(&self, py: crate::Python<'py>) -> crate::Bound<'py, crate::types::PyBytes> { + crate::types::PyBytes::new_bound(py, &[0]) } fn __format__(&self, format_spec: ::std::string::String) -> ::std::string::String { diff --git a/src/types/bytes.rs b/src/types/bytes.rs index d9d22dbb..296925dc 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -1,4 +1,6 @@ +use crate::ffi_ptr_ext::FfiPtrExt; use crate::instance::{Borrowed, Bound}; +use crate::types::any::PyAnyMethods; use crate::{ffi, FromPyObject, IntoPy, Py, PyAny, PyNativeType, PyResult, Python, ToPyObject}; use std::borrow::Cow; use std::ops::Index; @@ -17,14 +19,45 @@ pub struct PyBytes(PyAny); pyobject_native_type_core!(PyBytes, pyobject_native_static_type_object!(ffi::PyBytes_Type), #checkfunction=ffi::PyBytes_Check); impl PyBytes { + /// Deprecated form of [`PyBytes::new_bound`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyBytes::new` will be replaced by `PyBytes::new_bound` in a future PyO3 version" + ) + )] + pub fn new<'p>(py: Python<'p>, s: &[u8]) -> &'p PyBytes { + Self::new_bound(py, s).into_gil_ref() + } + /// Creates a new Python bytestring object. /// The bytestring is initialized by copying the data from the `&[u8]`. /// /// Panics if out of memory. - pub fn new<'p>(py: Python<'p>, s: &[u8]) -> &'p PyBytes { + pub fn new_bound<'p>(py: Python<'p>, s: &[u8]) -> Bound<'p, PyBytes> { let ptr = s.as_ptr() as *const c_char; let len = s.len() as ffi::Py_ssize_t; - unsafe { py.from_owned_ptr(ffi::PyBytes_FromStringAndSize(ptr, len)) } + unsafe { + ffi::PyBytes_FromStringAndSize(ptr, len) + .assume_owned(py) + .downcast_into_unchecked() + } + } + + /// Deprecated form of [`PyBytes::new_bound_with`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyBytes::new_with` will be replaced by `PyBytes::new_bound_with` in a future PyO3 version" + ) + )] + pub fn new_with(py: Python<'_>, len: usize, init: F) -> PyResult<&PyBytes> + where + F: FnOnce(&mut [u8]) -> PyResult<()>, + { + Self::new_bound_with(py, len, init).map(Bound::into_gil_ref) } /// Creates a new Python `bytes` object with an `init` closure to write its contents. @@ -41,34 +74,49 @@ impl PyBytes { /// /// # fn main() -> PyResult<()> { /// Python::with_gil(|py| -> PyResult<()> { - /// let py_bytes = PyBytes::new_with(py, 10, |bytes: &mut [u8]| { + /// let py_bytes = PyBytes::new_bound_with(py, 10, |bytes: &mut [u8]| { /// bytes.copy_from_slice(b"Hello Rust"); /// Ok(()) /// })?; - /// let bytes: &[u8] = FromPyObject::extract(py_bytes)?; + /// let bytes: &[u8] = py_bytes.extract()?; /// assert_eq!(bytes, b"Hello Rust"); /// Ok(()) /// }) /// # } /// ``` - pub fn new_with(py: Python<'_>, len: usize, init: F) -> PyResult<&PyBytes> + pub fn new_bound_with(py: Python<'_>, len: usize, init: F) -> PyResult> where F: FnOnce(&mut [u8]) -> PyResult<()>, { unsafe { let pyptr = ffi::PyBytes_FromStringAndSize(std::ptr::null(), len as ffi::Py_ssize_t); // Check for an allocation error and return it - let pypybytes: Py = Py::from_owned_ptr_or_err(py, pyptr)?; + let pybytes = pyptr.assume_owned_or_err(py)?.downcast_into_unchecked(); let buffer: *mut u8 = ffi::PyBytes_AsString(pyptr).cast(); debug_assert!(!buffer.is_null()); // Zero-initialise the uninitialised bytestring std::ptr::write_bytes(buffer, 0u8, len); // (Further) Initialise the bytestring in init // If init returns an Err, pypybytearray will automatically deallocate the buffer - init(std::slice::from_raw_parts_mut(buffer, len)).map(|_| pypybytes.into_ref(py)) + init(std::slice::from_raw_parts_mut(buffer, len)).map(|_| pybytes) } } + /// Deprecated form of [`PyBytes::bound_from_ptr`]. + /// + /// # Safety + /// See [`PyBytes::bound_from_ptr`]. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyBytes::from_ptr` will be replaced by `PyBytes::bound_from_ptr` in a future PyO3 version" + ) + )] + pub unsafe fn from_ptr(py: Python<'_>, ptr: *const u8, len: usize) -> &PyBytes { + Self::bound_from_ptr(py, ptr, len).into_gil_ref() + } + /// Creates a new Python byte string object from a raw pointer and length. /// /// Panics if out of memory. @@ -79,11 +127,10 @@ impl PyBytes { /// leading pointer of a slice of length `len`. [As with /// `std::slice::from_raw_parts`, this is /// unsafe](https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety). - pub unsafe fn from_ptr(py: Python<'_>, ptr: *const u8, len: usize) -> &PyBytes { - py.from_owned_ptr(ffi::PyBytes_FromStringAndSize( - ptr as *const _, - len as isize, - )) + pub unsafe fn bound_from_ptr(py: Python<'_>, ptr: *const u8, len: usize) -> Bound<'_, PyBytes> { + ffi::PyBytes_FromStringAndSize(ptr as *const _, len as isize) + .assume_owned(py) + .downcast_into_unchecked() } /// Gets the Python string as a byte slice. @@ -142,6 +189,15 @@ impl> Index for PyBytes { } } +/// This is the same way [Vec] is indexed. +impl> Index for Bound<'_, PyBytes> { + type Output = I::Output; + + fn index(&self, index: I) -> &Self::Output { + &self.as_bytes()[index] + } +} + /// Special-purpose trait impl to efficiently handle both `bytes` and `bytearray` /// /// If the source object is a `bytes` object, the `Cow` will be borrowed and @@ -160,7 +216,7 @@ impl<'source> FromPyObject<'source> for Cow<'source, [u8]> { impl ToPyObject for Cow<'_, [u8]> { fn to_object(&self, py: Python<'_>) -> Py { - PyBytes::new(py, self.as_ref()).into() + PyBytes::new_bound(py, self.as_ref()).into() } } @@ -171,6 +227,7 @@ impl IntoPy> for Cow<'_, [u8]> { } #[cfg(test)] +#[cfg_attr(not(feature = "gil-refs"), allow(deprecated))] mod tests { use super::*; @@ -182,6 +239,17 @@ mod tests { }); } + #[test] + fn test_bound_bytes_index() { + Python::with_gil(|py| { + let bytes = PyBytes::new_bound(py, b"Hello World"); + assert_eq!(bytes[1], b'e'); + + let bytes = &bytes; + assert_eq!(bytes[1], b'e'); + }); + } + #[test] fn test_bytes_new_with() -> super::PyResult<()> { Python::with_gil(|py| -> super::PyResult<()> { diff --git a/tests/test_bytes.rs b/tests/test_bytes.rs index 3ad13527..cdb4ec15 100644 --- a/tests/test_bytes.rs +++ b/tests/test_bytes.rs @@ -20,8 +20,8 @@ fn test_pybytes_bytes_conversion() { } #[pyfunction] -fn bytes_vec_conversion(py: Python<'_>, bytes: Vec) -> &PyBytes { - PyBytes::new(py, bytes.as_slice()) +fn bytes_vec_conversion(py: Python<'_>, bytes: Vec) -> Bound<'_, PyBytes> { + PyBytes::new_bound(py, bytes.as_slice()) } #[test] @@ -43,7 +43,7 @@ fn test_bytearray_vec_conversion() { #[test] fn test_py_as_bytes() { let pyobj: pyo3::Py = - Python::with_gil(|py| pyo3::types::PyBytes::new(py, b"abc").into_py(py)); + Python::with_gil(|py| pyo3::types::PyBytes::new_bound(py, b"abc").unbind()); let data = Python::with_gil(|py| pyobj.as_bytes(py));