1use crate::ffi_ptr_ext::FfiPtrExt;
2use crate::instance::{Borrowed, Bound};
3use crate::types::any::PyAnyMethods;
4#[cfg(feature = "gil-refs")]
5use crate::PyNativeType;
6use crate::{ffi, Py, PyAny, PyResult, Python};
7use std::ops::Index;
8use std::slice::SliceIndex;
9use std::str;
10
11#[repr(transparent)]
50pub struct PyBytes(PyAny);
51
52pyobject_native_type_core!(PyBytes, pyobject_native_static_type_object!(ffi::PyBytes_Type), #checkfunction=ffi::PyBytes_Check);
53
54impl PyBytes {
55 pub fn new_bound<'p>(py: Python<'p>, s: &[u8]) -> Bound<'p, PyBytes> {
60 let ptr = s.as_ptr().cast();
61 let len = s.len() as ffi::Py_ssize_t;
62 unsafe {
63 ffi::PyBytes_FromStringAndSize(ptr, len)
64 .assume_owned(py)
65 .downcast_into_unchecked()
66 }
67 }
68
69 pub fn new_bound_with<F>(py: Python<'_>, len: usize, init: F) -> PyResult<Bound<'_, PyBytes>>
94 where
95 F: FnOnce(&mut [u8]) -> PyResult<()>,
96 {
97 unsafe {
98 let pyptr = ffi::PyBytes_FromStringAndSize(std::ptr::null(), len as ffi::Py_ssize_t);
99 let pybytes = pyptr.assume_owned_or_err(py)?.downcast_into_unchecked();
101 let buffer: *mut u8 = ffi::PyBytes_AsString(pyptr).cast();
102 debug_assert!(!buffer.is_null());
103 std::ptr::write_bytes(buffer, 0u8, len);
105 init(std::slice::from_raw_parts_mut(buffer, len)).map(|_| pybytes)
108 }
109 }
110
111 pub unsafe fn bound_from_ptr(py: Python<'_>, ptr: *const u8, len: usize) -> Bound<'_, PyBytes> {
122 ffi::PyBytes_FromStringAndSize(ptr.cast(), len as isize)
123 .assume_owned(py)
124 .downcast_into_unchecked()
125 }
126}
127
128#[cfg(feature = "gil-refs")]
129impl PyBytes {
130 #[deprecated(
132 since = "0.21.0",
133 note = "`PyBytes::new` will be replaced by `PyBytes::new_bound` in a future PyO3 version"
134 )]
135 pub fn new<'p>(py: Python<'p>, s: &[u8]) -> &'p PyBytes {
136 Self::new_bound(py, s).into_gil_ref()
137 }
138
139 #[deprecated(
141 since = "0.21.0",
142 note = "`PyBytes::new_with` will be replaced by `PyBytes::new_bound_with` in a future PyO3 version"
143 )]
144 pub fn new_with<F>(py: Python<'_>, len: usize, init: F) -> PyResult<&PyBytes>
145 where
146 F: FnOnce(&mut [u8]) -> PyResult<()>,
147 {
148 Self::new_bound_with(py, len, init).map(Bound::into_gil_ref)
149 }
150
151 #[deprecated(
156 since = "0.21.0",
157 note = "`PyBytes::from_ptr` will be replaced by `PyBytes::bound_from_ptr` in a future PyO3 version"
158 )]
159 pub unsafe fn from_ptr(py: Python<'_>, ptr: *const u8, len: usize) -> &PyBytes {
160 Self::bound_from_ptr(py, ptr, len).into_gil_ref()
161 }
162
163 #[inline]
165 pub fn as_bytes(&self) -> &[u8] {
166 self.as_borrowed().as_bytes()
167 }
168}
169
170#[doc(alias = "PyBytes")]
176pub trait PyBytesMethods<'py>: crate::sealed::Sealed {
177 fn as_bytes(&self) -> &[u8];
179}
180
181impl<'py> PyBytesMethods<'py> for Bound<'py, PyBytes> {
182 #[inline]
183 fn as_bytes(&self) -> &[u8] {
184 self.as_borrowed().as_bytes()
185 }
186}
187
188impl<'a> Borrowed<'a, '_, PyBytes> {
189 #[allow(clippy::wrong_self_convention)]
191 pub(crate) fn as_bytes(self) -> &'a [u8] {
192 unsafe {
193 let buffer = ffi::PyBytes_AsString(self.as_ptr()) as *const u8;
194 let length = ffi::PyBytes_Size(self.as_ptr()) as usize;
195 debug_assert!(!buffer.is_null());
196 std::slice::from_raw_parts(buffer, length)
197 }
198 }
199}
200
201impl Py<PyBytes> {
202 pub fn as_bytes<'a>(&'a self, py: Python<'_>) -> &'a [u8] {
206 self.bind_borrowed(py).as_bytes()
207 }
208}
209
210#[cfg(feature = "gil-refs")]
212impl<I: SliceIndex<[u8]>> Index<I> for PyBytes {
213 type Output = I::Output;
214
215 fn index(&self, index: I) -> &Self::Output {
216 &self.as_bytes()[index]
217 }
218}
219
220impl<I: SliceIndex<[u8]>> Index<I> for Bound<'_, PyBytes> {
222 type Output = I::Output;
223
224 fn index(&self, index: I) -> &Self::Output {
225 &self.as_bytes()[index]
226 }
227}
228
229impl PartialEq<[u8]> for Bound<'_, PyBytes> {
233 #[inline]
234 fn eq(&self, other: &[u8]) -> bool {
235 self.as_borrowed() == *other
236 }
237}
238
239impl PartialEq<&'_ [u8]> for Bound<'_, PyBytes> {
243 #[inline]
244 fn eq(&self, other: &&[u8]) -> bool {
245 self.as_borrowed() == **other
246 }
247}
248
249impl PartialEq<Bound<'_, PyBytes>> for [u8] {
253 #[inline]
254 fn eq(&self, other: &Bound<'_, PyBytes>) -> bool {
255 *self == other.as_borrowed()
256 }
257}
258
259impl PartialEq<&'_ Bound<'_, PyBytes>> for [u8] {
263 #[inline]
264 fn eq(&self, other: &&Bound<'_, PyBytes>) -> bool {
265 *self == other.as_borrowed()
266 }
267}
268
269impl PartialEq<Bound<'_, PyBytes>> for &'_ [u8] {
273 #[inline]
274 fn eq(&self, other: &Bound<'_, PyBytes>) -> bool {
275 **self == other.as_borrowed()
276 }
277}
278
279impl PartialEq<[u8]> for &'_ Bound<'_, PyBytes> {
283 #[inline]
284 fn eq(&self, other: &[u8]) -> bool {
285 self.as_borrowed() == other
286 }
287}
288
289impl PartialEq<[u8]> for Borrowed<'_, '_, PyBytes> {
293 #[inline]
294 fn eq(&self, other: &[u8]) -> bool {
295 self.as_bytes() == other
296 }
297}
298
299impl PartialEq<&[u8]> for Borrowed<'_, '_, PyBytes> {
303 #[inline]
304 fn eq(&self, other: &&[u8]) -> bool {
305 *self == **other
306 }
307}
308
309impl PartialEq<Borrowed<'_, '_, PyBytes>> for [u8] {
313 #[inline]
314 fn eq(&self, other: &Borrowed<'_, '_, PyBytes>) -> bool {
315 other == self
316 }
317}
318
319impl PartialEq<Borrowed<'_, '_, PyBytes>> for &'_ [u8] {
323 #[inline]
324 fn eq(&self, other: &Borrowed<'_, '_, PyBytes>) -> bool {
325 other == self
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_bytes_index() {
335 Python::with_gil(|py| {
336 let bytes = PyBytes::new_bound(py, b"Hello World");
337 assert_eq!(bytes[1], b'e');
338 });
339 }
340
341 #[test]
342 fn test_bound_bytes_index() {
343 Python::with_gil(|py| {
344 let bytes = PyBytes::new_bound(py, b"Hello World");
345 assert_eq!(bytes[1], b'e');
346
347 let bytes = &bytes;
348 assert_eq!(bytes[1], b'e');
349 });
350 }
351
352 #[test]
353 fn test_bytes_new_with() -> super::PyResult<()> {
354 Python::with_gil(|py| -> super::PyResult<()> {
355 let py_bytes = PyBytes::new_bound_with(py, 10, |b: &mut [u8]| {
356 b.copy_from_slice(b"Hello Rust");
357 Ok(())
358 })?;
359 let bytes: &[u8] = py_bytes.extract()?;
360 assert_eq!(bytes, b"Hello Rust");
361 Ok(())
362 })
363 }
364
365 #[test]
366 fn test_bytes_new_with_zero_initialised() -> super::PyResult<()> {
367 Python::with_gil(|py| -> super::PyResult<()> {
368 let py_bytes = PyBytes::new_bound_with(py, 10, |_b: &mut [u8]| Ok(()))?;
369 let bytes: &[u8] = py_bytes.extract()?;
370 assert_eq!(bytes, &[0; 10]);
371 Ok(())
372 })
373 }
374
375 #[test]
376 fn test_bytes_new_with_error() {
377 use crate::exceptions::PyValueError;
378 Python::with_gil(|py| {
379 let py_bytes_result = PyBytes::new_bound_with(py, 10, |_b: &mut [u8]| {
380 Err(PyValueError::new_err("Hello Crustaceans!"))
381 });
382 assert!(py_bytes_result.is_err());
383 assert!(py_bytes_result
384 .err()
385 .unwrap()
386 .is_instance_of::<PyValueError>(py));
387 });
388 }
389
390 #[test]
391 fn test_comparisons() {
392 Python::with_gil(|py| {
393 let b = b"hello, world".as_slice();
394 let py_bytes = PyBytes::new_bound(py, b);
395
396 assert_eq!(py_bytes, b"hello, world".as_slice());
397
398 assert_eq!(py_bytes, b);
399 assert_eq!(&py_bytes, b);
400 assert_eq!(b, py_bytes);
401 assert_eq!(b, &py_bytes);
402
403 assert_eq!(py_bytes, *b);
404 assert_eq!(&py_bytes, *b);
405 assert_eq!(*b, py_bytes);
406 assert_eq!(*b, &py_bytes);
407
408 let py_string = py_bytes.as_borrowed();
409
410 assert_eq!(py_string, b);
411 assert_eq!(&py_string, b);
412 assert_eq!(b, py_string);
413 assert_eq!(b, &py_string);
414
415 assert_eq!(py_string, *b);
416 assert_eq!(*b, py_string);
417 })
418 }
419}