-
Notifications
You must be signed in to change notification settings - Fork 779
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
add PyBytes::new_bound
#3777
add PyBytes::new_bound
#3777
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<F>(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<F>(py: Python<'_>, len: usize, init: F) -> PyResult<&PyBytes> | ||
pub fn new_bound_with<F>(py: Python<'_>, len: usize, init: F) -> PyResult<Bound<'_, PyBytes>> | ||
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<PyBytes> = 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<I: SliceIndex<[u8]>> Index<I> for PyBytes { | |
} | ||
} | ||
|
||
/// This is the same way [Vec] is indexed. | ||
impl<I: SliceIndex<[u8]>> Index<I> for Bound<'_, PyBytes> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we could additionally implement it on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's not needed due to the way I've pushed an additional part to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perfect, that even better! 👍 |
||
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<PyAny> { | ||
PyBytes::new(py, self.as_ref()).into() | ||
PyBytes::new_bound(py, self.as_ref()).into() | ||
} | ||
} | ||
|
||
|
@@ -171,6 +227,7 @@ impl IntoPy<Py<PyAny>> 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<()> { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this returns a gil-ref, should we deprecate it as well, in favor of
bound_from_ptr()
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, my mistake to miss this; that's the downside of the commits I've been cherry-picking from #3606 - they were mostly trying to get a complete compile with a good range of the types rather than a complete implementation 🙈
I'll add here 👍