1use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::instance::{Borrowed, Bound};
5#[allow(unused_imports, reason = "used to build docs")]
6use crate::platform::prelude::*;
7use crate::{ffi, Py, PyAny, PyResult, Python};
8#[cfg(RustPython)]
9use crate::{
10 sync::PyOnceLock,
11 types::{PyType, PyTypeMethods},
12};
13use core::ops::Index;
14use core::slice::SliceIndex;
15use core::str;
16
17pub use self::writer::PyBytesWriter;
18
19mod writer;
20
21#[repr(transparent)]
61pub struct PyBytes(PyAny);
62
63#[cfg(not(RustPython))]
64pyobject_native_type_core!(PyBytes, pyobject_native_static_type_object!(ffi::PyBytes_Type), "builtins", "bytes", #checkfunction=ffi::PyBytes_Check);
65
66#[cfg(RustPython)]
67pyobject_native_type_core!(
68 PyBytes,
69 |py| {
70 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
71 TYPE.import(py, "builtins", "bytes").unwrap().as_type_ptr()
72 },
73 "builtins",
74 "bytes",
75 #checkfunction=ffi::PyBytes_Check
76);
77
78impl PyBytes {
79 pub fn new<'p>(py: Python<'p>, s: &[u8]) -> Bound<'p, PyBytes> {
84 let ptr = s.as_ptr().cast();
85 let len = s.len() as ffi::Py_ssize_t;
86 unsafe {
87 ffi::PyBytes_FromStringAndSize(ptr, len)
88 .assume_owned(py)
89 .cast_into_unchecked()
90 }
91 }
92
93 #[inline]
118 pub fn new_with<F>(py: Python<'_>, len: usize, init: F) -> PyResult<Bound<'_, PyBytes>>
119 where
120 F: FnOnce(&mut [u8]) -> PyResult<()>,
121 {
122 unsafe {
123 let pyptr = ffi::PyBytes_FromStringAndSize(core::ptr::null(), len as ffi::Py_ssize_t);
124 let pybytes = pyptr.assume_owned_or_err(py)?.cast_into_unchecked();
126 let buffer: *mut u8 = ffi::PyBytes_AsString(pyptr).cast();
127 debug_assert!(!buffer.is_null());
128 core::ptr::write_bytes(buffer, 0u8, len);
130 init(core::slice::from_raw_parts_mut(buffer, len)).map(|_| pybytes)
133 }
134 }
135
136 #[inline]
163 pub fn new_with_writer<'py, F>(
164 py: Python<'py>,
165 reserved_capacity: usize,
166 write: F,
167 ) -> PyResult<Bound<'py, PyBytes>>
168 where
169 F: FnOnce(&mut PyBytesWriter<'py>) -> PyResult<()>,
170 {
171 let mut writer = PyBytesWriter::with_capacity(py, reserved_capacity)?;
172 write(&mut writer)?;
173 writer.try_into()
174 }
175
176 pub unsafe fn from_ptr(py: Python<'_>, ptr: *const u8, len: usize) -> Bound<'_, PyBytes> {
187 unsafe {
188 ffi::PyBytes_FromStringAndSize(ptr.cast(), len as isize)
189 .assume_owned(py)
190 .cast_into_unchecked()
191 }
192 }
193}
194
195#[doc(alias = "PyBytes")]
201pub trait PyBytesMethods<'py>: crate::sealed::Sealed {
202 fn as_bytes(&self) -> &[u8];
204}
205
206impl<'py> PyBytesMethods<'py> for Bound<'py, PyBytes> {
207 #[inline]
208 fn as_bytes(&self) -> &[u8] {
209 self.as_borrowed().as_bytes()
210 }
211}
212
213impl<'a> Borrowed<'a, '_, PyBytes> {
214 #[allow(clippy::wrong_self_convention)]
216 pub(crate) fn as_bytes(self) -> &'a [u8] {
217 #[cfg(not(Py_LIMITED_API))]
218 unsafe {
219 let buffer = ffi::PyBytes_AS_STRING(self.as_ptr()).cast::<u8>();
220 let length = ffi::Py_SIZE(self.as_ptr()) as usize;
221 debug_assert!(!buffer.is_null());
222 core::slice::from_raw_parts(buffer, length)
223 }
224
225 #[cfg(Py_LIMITED_API)]
226 unsafe {
227 let buffer = ffi::PyBytes_AsString(self.as_ptr()) as *const u8;
228 let length = ffi::PyBytes_Size(self.as_ptr()) as usize;
229 debug_assert!(!buffer.is_null());
230 core::slice::from_raw_parts(buffer, length)
231 }
232 }
233}
234
235impl Py<PyBytes> {
236 pub fn as_bytes<'a>(&'a self, py: Python<'_>) -> &'a [u8] {
240 self.bind_borrowed(py).as_bytes()
241 }
242}
243
244impl<I: SliceIndex<[u8]>> Index<I> for Bound<'_, PyBytes> {
246 type Output = I::Output;
247
248 fn index(&self, index: I) -> &Self::Output {
249 &self.as_bytes()[index]
250 }
251}
252
253impl PartialEq<[u8]> for Bound<'_, PyBytes> {
257 #[inline]
258 fn eq(&self, other: &[u8]) -> bool {
259 self.as_borrowed() == *other
260 }
261}
262
263impl PartialEq<&'_ [u8]> for Bound<'_, PyBytes> {
267 #[inline]
268 fn eq(&self, other: &&[u8]) -> bool {
269 self.as_borrowed() == **other
270 }
271}
272
273impl PartialEq<Bound<'_, PyBytes>> for [u8] {
277 #[inline]
278 fn eq(&self, other: &Bound<'_, PyBytes>) -> bool {
279 *self == other.as_borrowed()
280 }
281}
282
283impl PartialEq<&'_ Bound<'_, PyBytes>> for [u8] {
287 #[inline]
288 fn eq(&self, other: &&Bound<'_, PyBytes>) -> bool {
289 *self == other.as_borrowed()
290 }
291}
292
293impl PartialEq<Bound<'_, PyBytes>> for &'_ [u8] {
297 #[inline]
298 fn eq(&self, other: &Bound<'_, PyBytes>) -> bool {
299 **self == other.as_borrowed()
300 }
301}
302
303impl PartialEq<[u8]> for &'_ Bound<'_, PyBytes> {
307 #[inline]
308 fn eq(&self, other: &[u8]) -> bool {
309 self.as_borrowed() == other
310 }
311}
312
313impl PartialEq<[u8]> for Borrowed<'_, '_, PyBytes> {
317 #[inline]
318 fn eq(&self, other: &[u8]) -> bool {
319 self.as_bytes() == other
320 }
321}
322
323impl PartialEq<&[u8]> for Borrowed<'_, '_, PyBytes> {
327 #[inline]
328 fn eq(&self, other: &&[u8]) -> bool {
329 *self == **other
330 }
331}
332
333impl PartialEq<Borrowed<'_, '_, PyBytes>> for [u8] {
337 #[inline]
338 fn eq(&self, other: &Borrowed<'_, '_, PyBytes>) -> bool {
339 other == self
340 }
341}
342
343impl PartialEq<Borrowed<'_, '_, PyBytes>> for &'_ [u8] {
347 #[inline]
348 fn eq(&self, other: &Borrowed<'_, '_, PyBytes>) -> bool {
349 other == self
350 }
351}
352
353impl<'a> AsRef<[u8]> for Borrowed<'a, '_, PyBytes> {
354 #[inline]
355 fn as_ref(&self) -> &'a [u8] {
356 self.as_bytes()
357 }
358}
359
360impl AsRef<[u8]> for Bound<'_, PyBytes> {
361 #[inline]
362 fn as_ref(&self) -> &[u8] {
363 self.as_bytes()
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370 use crate::types::PyAnyMethods as _;
371
372 #[test]
373 fn test_bytes_index() {
374 Python::attach(|py| {
375 let bytes = PyBytes::new(py, b"Hello World");
376 assert_eq!(bytes[1], b'e');
377 });
378 }
379
380 #[test]
381 fn test_bound_bytes_index() {
382 Python::attach(|py| {
383 let bytes = PyBytes::new(py, b"Hello World");
384 assert_eq!(bytes[1], b'e');
385
386 let bytes = &bytes;
387 assert_eq!(bytes[1], b'e');
388 });
389 }
390
391 #[test]
392 fn test_bytes_new_with() -> super::PyResult<()> {
393 Python::attach(|py| -> super::PyResult<()> {
394 let py_bytes = PyBytes::new_with(py, 10, |b: &mut [u8]| {
395 b.copy_from_slice(b"Hello Rust");
396 Ok(())
397 })?;
398 let bytes: &[u8] = py_bytes.extract()?;
399 assert_eq!(bytes, b"Hello Rust");
400 Ok(())
401 })
402 }
403
404 #[test]
405 fn test_bytes_new_with_zero_initialised() -> super::PyResult<()> {
406 Python::attach(|py| -> super::PyResult<()> {
407 let py_bytes = PyBytes::new_with(py, 10, |_b: &mut [u8]| Ok(()))?;
408 let bytes: &[u8] = py_bytes.extract()?;
409 assert_eq!(bytes, &[0; 10]);
410 Ok(())
411 })
412 }
413
414 #[test]
415 fn test_bytes_new_with_error() {
416 use crate::exceptions::PyValueError;
417 Python::attach(|py| {
418 let py_bytes_result = PyBytes::new_with(py, 10, |_b: &mut [u8]| {
419 Err(PyValueError::new_err("Hello Crustaceans!"))
420 });
421 assert!(py_bytes_result.is_err());
422 assert!(py_bytes_result
423 .err()
424 .unwrap()
425 .is_instance_of::<PyValueError>(py));
426 });
427 }
428
429 #[test]
430 fn test_comparisons() {
431 Python::attach(|py| {
432 let b = b"hello, world".as_slice();
433 let py_bytes = PyBytes::new(py, b);
434
435 assert_eq!(py_bytes, b"hello, world".as_slice());
436
437 assert_eq!(py_bytes, b);
438 assert_eq!(&py_bytes, b);
439 assert_eq!(b, py_bytes);
440 assert_eq!(b, &py_bytes);
441
442 assert_eq!(py_bytes, *b);
443 assert_eq!(&py_bytes, *b);
444 assert_eq!(*b, py_bytes);
445 assert_eq!(*b, &py_bytes);
446
447 let py_string = py_bytes.as_borrowed();
448
449 assert_eq!(py_string, b);
450 assert_eq!(&py_string, b);
451 assert_eq!(b, py_string);
452 assert_eq!(b, &py_string);
453
454 assert_eq!(py_string, *b);
455 assert_eq!(*b, py_string);
456 })
457 }
458
459 #[test]
460 #[cfg(not(Py_LIMITED_API))]
461 fn test_as_string() {
462 Python::attach(|py| {
463 let b = b"hello, world".as_slice();
464 let py_bytes = PyBytes::new(py, b);
465 unsafe {
466 assert_eq!(
467 ffi::PyBytes_AsString(py_bytes.as_ptr()) as *const core::ffi::c_char,
468 ffi::PyBytes_AS_STRING(py_bytes.as_ptr()) as *const core::ffi::c_char
469 );
470 }
471 })
472 }
473
474 #[test]
475 fn test_as_ref_slice() {
476 Python::attach(|py| {
477 let b = b"hello, world";
478 let py_bytes = PyBytes::new(py, b);
479 let ref_bound: &[u8] = py_bytes.as_ref();
480 assert_eq!(ref_bound, b);
481 let py_bytes_borrowed = py_bytes.as_borrowed();
482 let ref_borrowed: &[u8] = py_bytes_borrowed.as_ref();
483 assert_eq!(ref_borrowed, b);
484 })
485 }
486
487 #[test]
488 fn test_py_as_bytes() {
489 let pyobj: Py<PyBytes> = Python::attach(|py| PyBytes::new(py, b"abc").unbind());
490
491 let data = Python::attach(|py| pyobj.as_bytes(py));
492
493 assert_eq!(data, b"abc");
494
495 Python::attach(move |_py| drop(pyobj));
496 }
497
498 #[test]
499 fn test_with_writer() {
500 Python::attach(|py| {
501 let bytes = PyBytes::new_with_writer(py, 0, |writer| {
502 writer.write_bytes(b"hallo")?;
503 Ok(())
504 })
505 .unwrap();
506
507 assert_eq!(bytes.as_bytes(), b"hallo");
508 })
509 }
510}