Skip to main content

pyo3/
buffer.rs

1#![cfg(any(not(Py_LIMITED_API), Py_3_11))]
2// Copyright (c) 2017 Daniel Grunwald
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy of this
5// software and associated documentation files (the "Software"), to deal in the Software
6// without restriction, including without limitation the rights to use, copy, modify, merge,
7// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8// to whom the Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all copies or
11// substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18// DEALINGS IN THE SOFTWARE.
19
20//! `PyBuffer` implementation
21#[cfg(feature = "experimental-inspect")]
22use crate::inspect::{type_hint_identifier, PyStaticExpr};
23use crate::{err, exceptions::PyBufferError, ffi, FromPyObject, PyAny, PyResult, Python};
24use crate::{Borrowed, Bound, PyErr};
25use std::ffi::{
26    c_char, c_int, c_long, c_longlong, c_schar, c_short, c_uchar, c_uint, c_ulong, c_ulonglong,
27    c_ushort, c_void,
28};
29use std::marker::{PhantomData, PhantomPinned};
30use std::pin::Pin;
31use std::ptr::NonNull;
32use std::{cell, mem, ptr, slice};
33use std::{ffi::CStr, fmt::Debug};
34
35/// A typed form of [`PyUntypedBuffer`].
36#[repr(transparent)]
37pub struct PyBuffer<T>(PyUntypedBuffer, PhantomData<[T]>);
38
39/// Allows access to the underlying buffer used by a python object such as `bytes`, `bytearray` or `array.array`.
40#[repr(transparent)]
41pub struct PyUntypedBuffer(
42    // It is common for exporters filling `Py_buffer` struct to make it self-referential, e.g. see
43    // implementation of
44    // [`PyBuffer_FillInfo`](https://github.com/python/cpython/blob/2fd43a1ffe4ff1f6c46f6045bc327d6085c40fbf/Objects/abstract.c#L798-L802).
45    //
46    // Therefore we use `Pin<Box<...>>` to document for ourselves that the memory address of the `Py_buffer` is expected to be stable
47    Pin<Box<RawBuffer>>,
48);
49
50/// Wrapper around `ffi::Py_buffer` to be `!Unpin`.
51#[repr(transparent)]
52struct RawBuffer(ffi::Py_buffer, PhantomPinned);
53
54// PyBuffer send & sync guarantees are upheld by Python.
55unsafe impl Send for PyUntypedBuffer {}
56unsafe impl Sync for PyUntypedBuffer {}
57
58impl<T> Debug for PyBuffer<T> {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        debug_buffer("PyBuffer", &self.0, f)
61    }
62}
63
64impl Debug for PyUntypedBuffer {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        debug_buffer("PyUntypedBuffer", self, f)
67    }
68}
69
70fn debug_buffer(
71    name: &str,
72    b: &PyUntypedBuffer,
73    f: &mut std::fmt::Formatter<'_>,
74) -> std::fmt::Result {
75    let raw = b.raw();
76    f.debug_struct(name)
77        .field("buf", &raw.buf)
78        .field("obj", &raw.obj)
79        .field("len", &raw.len)
80        .field("itemsize", &raw.itemsize)
81        .field("readonly", &raw.readonly)
82        .field("ndim", &raw.ndim)
83        .field("format", &b.format())
84        .field("shape", &b.shape())
85        .field("strides", &b.strides())
86        .field("suboffsets", &b.suboffsets())
87        .field("internal", &raw.internal)
88        .finish()
89}
90
91/// Represents the type of a Python buffer element.
92#[derive(Copy, Clone, Debug, Eq, PartialEq)]
93pub enum ElementType {
94    /// A signed integer type.
95    SignedInteger {
96        /// The width of the signed integer in bytes.
97        bytes: usize,
98    },
99    /// An unsigned integer type.
100    UnsignedInteger {
101        /// The width of the unsigned integer in bytes.
102        bytes: usize,
103    },
104    /// A boolean type.
105    Bool,
106    /// A float type.
107    Float {
108        /// The width of the float in bytes.
109        bytes: usize,
110    },
111    /// An unknown type. This may occur when parsing has failed.
112    Unknown,
113}
114
115impl ElementType {
116    /// Determines the `ElementType` from a Python `struct` module format string.
117    ///
118    /// See <https://docs.python.org/3/library/struct.html#format-strings> for more information
119    /// about struct format strings.
120    pub fn from_format(format: &CStr) -> ElementType {
121        match format.to_bytes() {
122            [size] | [b'@', size] => native_element_type_from_type_char(*size),
123            [b'=' | b'<' | b'>' | b'!', size] => standard_element_type_from_type_char(*size),
124            _ => ElementType::Unknown,
125        }
126    }
127}
128
129fn native_element_type_from_type_char(type_char: u8) -> ElementType {
130    use self::ElementType::*;
131    match type_char {
132        b'c' => UnsignedInteger {
133            bytes: mem::size_of::<c_char>(),
134        },
135        b'b' => SignedInteger {
136            bytes: mem::size_of::<c_schar>(),
137        },
138        b'B' => UnsignedInteger {
139            bytes: mem::size_of::<c_uchar>(),
140        },
141        b'?' => Bool,
142        b'h' => SignedInteger {
143            bytes: mem::size_of::<c_short>(),
144        },
145        b'H' => UnsignedInteger {
146            bytes: mem::size_of::<c_ushort>(),
147        },
148        b'i' => SignedInteger {
149            bytes: mem::size_of::<c_int>(),
150        },
151        b'I' => UnsignedInteger {
152            bytes: mem::size_of::<c_uint>(),
153        },
154        b'l' => SignedInteger {
155            bytes: mem::size_of::<c_long>(),
156        },
157        b'L' => UnsignedInteger {
158            bytes: mem::size_of::<c_ulong>(),
159        },
160        b'q' => SignedInteger {
161            bytes: mem::size_of::<c_longlong>(),
162        },
163        b'Q' => UnsignedInteger {
164            bytes: mem::size_of::<c_ulonglong>(),
165        },
166        b'n' => SignedInteger {
167            bytes: mem::size_of::<libc::ssize_t>(),
168        },
169        b'N' => UnsignedInteger {
170            bytes: mem::size_of::<libc::size_t>(),
171        },
172        b'e' => Float { bytes: 2 },
173        b'f' => Float { bytes: 4 },
174        b'd' => Float { bytes: 8 },
175        _ => Unknown,
176    }
177}
178
179fn standard_element_type_from_type_char(type_char: u8) -> ElementType {
180    use self::ElementType::*;
181    match type_char {
182        b'c' | b'B' => UnsignedInteger { bytes: 1 },
183        b'b' => SignedInteger { bytes: 1 },
184        b'?' => Bool,
185        b'h' => SignedInteger { bytes: 2 },
186        b'H' => UnsignedInteger { bytes: 2 },
187        b'i' | b'l' => SignedInteger { bytes: 4 },
188        b'I' | b'L' => UnsignedInteger { bytes: 4 },
189        b'q' => SignedInteger { bytes: 8 },
190        b'Q' => UnsignedInteger { bytes: 8 },
191        b'e' => Float { bytes: 2 },
192        b'f' => Float { bytes: 4 },
193        b'd' => Float { bytes: 8 },
194        _ => Unknown,
195    }
196}
197
198#[cfg(target_endian = "little")]
199fn is_matching_endian(c: u8) -> bool {
200    c == b'@' || c == b'=' || c == b'>'
201}
202
203#[cfg(target_endian = "big")]
204fn is_matching_endian(c: u8) -> bool {
205    c == b'@' || c == b'=' || c == b'>' || c == b'!'
206}
207
208/// Trait implemented for possible element types of `PyBuffer`.
209///
210/// # Safety
211///
212/// This trait must only be implemented for types which represent valid elements of Python buffers.
213pub unsafe trait Element: Copy {
214    /// Gets whether the element specified in the format string is potentially compatible.
215    /// Alignment and size are checked separately from this function.
216    fn is_compatible_format(format: &CStr) -> bool;
217}
218
219impl<T: Element> FromPyObject<'_, '_> for PyBuffer<T> {
220    type Error = PyErr;
221
222    #[cfg(feature = "experimental-inspect")]
223    const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("collections.abc", "Buffer");
224
225    fn extract(obj: Borrowed<'_, '_, PyAny>) -> Result<PyBuffer<T>, Self::Error> {
226        Self::get(&obj)
227    }
228}
229
230impl<T: Element> PyBuffer<T> {
231    /// Gets the underlying buffer from the specified python object.
232    pub fn get(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
233        PyUntypedBuffer::get(obj)?.into_typed()
234    }
235
236    /// Gets the buffer memory as a slice.
237    ///
238    /// This function succeeds if:
239    /// * the buffer format is compatible with `T`
240    /// * alignment and size of buffer elements is matching the expectations for type `T`
241    /// * the buffer is C-style contiguous
242    ///
243    /// The returned slice uses type `Cell<T>` because it's theoretically possible for any call into the Python runtime
244    /// to modify the values in the slice.
245    pub fn as_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [ReadOnlyCell<T>]> {
246        if self.is_c_contiguous() {
247            unsafe {
248                Some(slice::from_raw_parts(
249                    self.raw().buf.cast(),
250                    self.item_count(),
251                ))
252            }
253        } else {
254            None
255        }
256    }
257
258    /// Gets the buffer memory as a slice.
259    ///
260    /// This function succeeds if:
261    /// * the buffer is not read-only
262    /// * the buffer format is compatible with `T`
263    /// * alignment and size of buffer elements is matching the expectations for type `T`
264    /// * the buffer is C-style contiguous
265    ///
266    /// The returned slice uses type `Cell<T>` because it's theoretically possible for any call into the Python runtime
267    /// to modify the values in the slice.
268    pub fn as_mut_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [cell::Cell<T>]> {
269        if !self.readonly() && self.is_c_contiguous() {
270            unsafe {
271                Some(slice::from_raw_parts(
272                    self.raw().buf.cast(),
273                    self.item_count(),
274                ))
275            }
276        } else {
277            None
278        }
279    }
280
281    /// Gets the buffer memory as a slice.
282    ///
283    /// This function succeeds if:
284    /// * the buffer format is compatible with `T`
285    /// * alignment and size of buffer elements is matching the expectations for type `T`
286    /// * the buffer is Fortran-style contiguous
287    ///
288    /// The returned slice uses type `Cell<T>` because it's theoretically possible for any call into the Python runtime
289    /// to modify the values in the slice.
290    pub fn as_fortran_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [ReadOnlyCell<T>]> {
291        if mem::size_of::<T>() == self.item_size() && self.is_fortran_contiguous() {
292            unsafe {
293                Some(slice::from_raw_parts(
294                    self.raw().buf.cast(),
295                    self.item_count(),
296                ))
297            }
298        } else {
299            None
300        }
301    }
302
303    /// Gets the buffer memory as a slice.
304    ///
305    /// This function succeeds if:
306    /// * the buffer is not read-only
307    /// * the buffer format is compatible with `T`
308    /// * alignment and size of buffer elements is matching the expectations for type `T`
309    /// * the buffer is Fortran-style contiguous
310    ///
311    /// The returned slice uses type `Cell<T>` because it's theoretically possible for any call into the Python runtime
312    /// to modify the values in the slice.
313    pub fn as_fortran_mut_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [cell::Cell<T>]> {
314        if !self.readonly() && self.is_fortran_contiguous() {
315            unsafe {
316                Some(slice::from_raw_parts(
317                    self.raw().buf.cast(),
318                    self.item_count(),
319                ))
320            }
321        } else {
322            None
323        }
324    }
325
326    /// Copies the buffer elements to the specified slice.
327    /// If the buffer is multi-dimensional, the elements are written in C-style order.
328    ///
329    ///  * Fails if the slice does not have the correct length (`buf.item_count()`).
330    ///  * Fails if the buffer format is not compatible with type `T`.
331    ///
332    /// To check whether the buffer format is compatible before calling this method,
333    /// you can use `<T as buffer::Element>::is_compatible_format(buf.format())`.
334    /// Alternatively, `match buffer::ElementType::from_format(buf.format())`.
335    pub fn copy_to_slice(&self, py: Python<'_>, target: &mut [T]) -> PyResult<()> {
336        self._copy_to_slice(py, target, b'C')
337    }
338
339    /// Copies the buffer elements to the specified slice.
340    /// If the buffer is multi-dimensional, the elements are written in Fortran-style order.
341    ///
342    ///  * Fails if the slice does not have the correct length (`buf.item_count()`).
343    ///  * Fails if the buffer format is not compatible with type `T`.
344    ///
345    /// To check whether the buffer format is compatible before calling this method,
346    /// you can use `<T as buffer::Element>::is_compatible_format(buf.format())`.
347    /// Alternatively, `match buffer::ElementType::from_format(buf.format())`.
348    pub fn copy_to_fortran_slice(&self, py: Python<'_>, target: &mut [T]) -> PyResult<()> {
349        self._copy_to_slice(py, target, b'F')
350    }
351
352    fn _copy_to_slice(&self, py: Python<'_>, target: &mut [T], fort: u8) -> PyResult<()> {
353        if mem::size_of_val(target) != self.len_bytes() {
354            return Err(PyBufferError::new_err(format!(
355                "slice to copy to (of length {}) does not match buffer length of {}",
356                target.len(),
357                self.item_count()
358            )));
359        }
360
361        err::error_on_minusone(py, unsafe {
362            ffi::PyBuffer_ToContiguous(
363                target.as_mut_ptr().cast(),
364                #[cfg(Py_3_11)]
365                self.raw(),
366                #[cfg(not(Py_3_11))]
367                ptr::from_ref(self.raw()).cast_mut(),
368                self.raw().len,
369                fort as std::ffi::c_char,
370            )
371        })
372    }
373
374    /// Copies the buffer elements to a newly allocated vector.
375    /// If the buffer is multi-dimensional, the elements are written in C-style order.
376    ///
377    /// Fails if the buffer format is not compatible with type `T`.
378    pub fn to_vec(&self, py: Python<'_>) -> PyResult<Vec<T>> {
379        self._to_vec(py, b'C')
380    }
381
382    /// Copies the buffer elements to a newly allocated vector.
383    /// If the buffer is multi-dimensional, the elements are written in Fortran-style order.
384    ///
385    /// Fails if the buffer format is not compatible with type `T`.
386    pub fn to_fortran_vec(&self, py: Python<'_>) -> PyResult<Vec<T>> {
387        self._to_vec(py, b'F')
388    }
389
390    fn _to_vec(&self, py: Python<'_>, fort: u8) -> PyResult<Vec<T>> {
391        let item_count = self.item_count();
392        let mut vec: Vec<T> = Vec::with_capacity(item_count);
393
394        // Copy the buffer into the uninitialized space in the vector.
395        // Due to T:Copy, we don't need to be concerned with Drop impls.
396        err::error_on_minusone(py, unsafe {
397            ffi::PyBuffer_ToContiguous(
398                vec.as_mut_ptr().cast(),
399                #[cfg(Py_3_11)]
400                self.raw(),
401                #[cfg(not(Py_3_11))]
402                ptr::from_ref(self.raw()).cast_mut(),
403                self.raw().len,
404                fort as std::ffi::c_char,
405            )
406        })?;
407        // set vector length to mark the now-initialized space as usable
408        unsafe { vec.set_len(item_count) };
409        Ok(vec)
410    }
411
412    /// Copies the specified slice into the buffer.
413    /// If the buffer is multi-dimensional, the elements in the slice are expected to be in C-style order.
414    ///
415    ///  * Fails if the buffer is read-only.
416    ///  * Fails if the slice does not have the correct length (`buf.item_count()`).
417    ///  * Fails if the buffer format is not compatible with type `T`.
418    ///
419    /// To check whether the buffer format is compatible before calling this method,
420    /// use `<T as buffer::Element>::is_compatible_format(buf.format())`.
421    /// Alternatively, `match buffer::ElementType::from_format(buf.format())`.
422    pub fn copy_from_slice(&self, py: Python<'_>, source: &[T]) -> PyResult<()> {
423        self._copy_from_slice(py, source, b'C')
424    }
425
426    /// Copies the specified slice into the buffer.
427    /// If the buffer is multi-dimensional, the elements in the slice are expected to be in Fortran-style order.
428    ///
429    ///  * Fails if the buffer is read-only.
430    ///  * Fails if the slice does not have the correct length (`buf.item_count()`).
431    ///  * Fails if the buffer format is not compatible with type `T`.
432    ///
433    /// To check whether the buffer format is compatible before calling this method,
434    /// use `<T as buffer::Element>::is_compatible_format(buf.format())`.
435    /// Alternatively, `match buffer::ElementType::from_format(buf.format())`.
436    pub fn copy_from_fortran_slice(&self, py: Python<'_>, source: &[T]) -> PyResult<()> {
437        self._copy_from_slice(py, source, b'F')
438    }
439
440    fn _copy_from_slice(&self, py: Python<'_>, source: &[T], fort: u8) -> PyResult<()> {
441        if self.readonly() {
442            return Err(PyBufferError::new_err("cannot write to read-only buffer"));
443        } else if mem::size_of_val(source) != self.len_bytes() {
444            return Err(PyBufferError::new_err(format!(
445                "slice to copy from (of length {}) does not match buffer length of {}",
446                source.len(),
447                self.item_count()
448            )));
449        }
450
451        err::error_on_minusone(py, unsafe {
452            ffi::PyBuffer_FromContiguous(
453                #[cfg(Py_3_11)]
454                self.raw(),
455                #[cfg(not(Py_3_11))]
456                ptr::from_ref(self.raw()).cast_mut(),
457                #[cfg(Py_3_11)]
458                {
459                    source.as_ptr().cast()
460                },
461                #[cfg(not(Py_3_11))]
462                {
463                    source.as_ptr().cast::<c_void>().cast_mut()
464                },
465                self.raw().len,
466                fort as std::ffi::c_char,
467            )
468        })
469    }
470}
471
472impl<T> std::ops::Deref for PyBuffer<T> {
473    type Target = PyUntypedBuffer;
474
475    fn deref(&self) -> &Self::Target {
476        &self.0
477    }
478}
479
480impl PyUntypedBuffer {
481    /// Gets the underlying buffer from the specified python object.
482    pub fn get(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
483        let buf = {
484            let mut buf = Box::<RawBuffer>::new_uninit();
485            // SAFETY: RawBuffer is `#[repr(transparent)]` around FFI struct
486            err::error_on_minusone(obj.py(), unsafe {
487                ffi::PyObject_GetBuffer(
488                    obj.as_ptr(),
489                    buf.as_mut_ptr().cast::<ffi::Py_buffer>(),
490                    ffi::PyBUF_FULL_RO,
491                )
492            })?;
493            // Safety: buf is initialized by PyObject_GetBuffer.
494            unsafe { buf.assume_init() }
495        };
496        // Create PyBuffer immediately so that if validation checks fail, the PyBuffer::drop code
497        // will call PyBuffer_Release (thus avoiding any leaks).
498        let buf = Self(Pin::from(buf));
499        let raw = buf.raw();
500
501        if raw.shape.is_null() {
502            Err(PyBufferError::new_err("shape is null"))
503        } else if raw.strides.is_null() {
504            Err(PyBufferError::new_err("strides is null"))
505        } else {
506            Ok(buf)
507        }
508    }
509
510    /// Returns a `[PyBuffer]` instance if the buffer can be interpreted as containing elements of type `T`.
511    pub fn into_typed<T: Element>(self) -> PyResult<PyBuffer<T>> {
512        self.ensure_compatible_with::<T>()?;
513        Ok(PyBuffer(self, PhantomData))
514    }
515
516    /// Non-owning equivalent of [`into_typed()`][Self::into_typed].
517    pub fn as_typed<T: Element>(&self) -> PyResult<&PyBuffer<T>> {
518        self.ensure_compatible_with::<T>()?;
519        // SAFETY: PyBuffer<T> is repr(transparent) around PyUntypedBuffer
520        Ok(unsafe { NonNull::from(self).cast::<PyBuffer<T>>().as_ref() })
521    }
522
523    fn ensure_compatible_with<T: Element>(&self) -> PyResult<()> {
524        if mem::size_of::<T>() != self.item_size() || !T::is_compatible_format(self.format()) {
525            Err(PyBufferError::new_err(format!(
526                "buffer contents are not compatible with {}",
527                std::any::type_name::<T>()
528            )))
529        } else if self.raw().buf.align_offset(mem::align_of::<T>()) != 0 {
530            Err(PyBufferError::new_err(format!(
531                "buffer contents are insufficiently aligned for {}",
532                std::any::type_name::<T>()
533            )))
534        } else {
535            Ok(())
536        }
537    }
538
539    /// Releases the buffer object, freeing the reference to the Python object
540    /// which owns the buffer.
541    ///
542    /// This will automatically be called on drop.
543    pub fn release(self, _py: Python<'_>) {
544        // First move self into a ManuallyDrop, so that PyBuffer::drop will
545        // never be called. (It would attach to the interpreter and call PyBuffer_Release
546        // again.)
547        let mut mdself = mem::ManuallyDrop::new(self);
548        unsafe {
549            // Next, make the actual PyBuffer_Release call.
550            // Fine to get a mutable reference to the inner ffi::Py_buffer here, as we're destroying it.
551            mdself.0.release();
552
553            // Finally, drop the contained Pin<Box<_>> in place, to free the
554            // Box memory.
555            ptr::drop_in_place::<Pin<Box<RawBuffer>>>(&mut mdself.0);
556        }
557    }
558
559    /// Gets the pointer to the start of the buffer memory.
560    ///
561    /// Warning: the buffer memory can be mutated by other code (including
562    /// other Python functions, if the GIL is released, or other extension
563    /// modules even if the GIL is held). You must either access memory
564    /// atomically, or ensure there are no data races yourself. See
565    /// [this blog post] for more details.
566    ///
567    /// [this blog post]: https://alexgaynor.net/2022/oct/23/buffers-on-the-edge/
568    #[inline]
569    pub fn buf_ptr(&self) -> *mut c_void {
570        self.raw().buf
571    }
572
573    /// Returns the Python object that owns the buffer data.
574    ///
575    /// This is the object that was passed to [`PyBuffer::get()`]
576    /// when the buffer was created.
577    /// Calling this before [`release()`][Self::release] and cloning the result
578    /// allows you to keep the object alive after the buffer is released.
579    #[inline]
580    pub fn obj<'py>(&self, py: Python<'py>) -> Option<&Bound<'py, PyAny>> {
581        unsafe { Bound::ref_from_ptr_or_opt(py, &self.raw().obj).as_ref() }
582    }
583
584    /// Gets a pointer to the specified item.
585    ///
586    /// If `indices.len() < self.dimensions()`, returns the start address of the sub-array at the specified dimension.
587    pub fn get_ptr(&self, indices: &[usize]) -> *mut c_void {
588        let shape = &self.shape()[..indices.len()];
589        for i in 0..indices.len() {
590            assert!(indices[i] < shape[i]);
591        }
592        unsafe {
593            ffi::PyBuffer_GetPointer(
594                #[cfg(Py_3_11)]
595                self.raw(),
596                #[cfg(not(Py_3_11))]
597                ptr::from_ref(self.raw()).cast_mut(),
598                #[cfg(Py_3_11)]
599                indices.as_ptr().cast(),
600                #[cfg(not(Py_3_11))]
601                indices.as_ptr().cast_mut().cast(),
602            )
603        }
604    }
605
606    /// Gets whether the underlying buffer is read-only.
607    #[inline]
608    pub fn readonly(&self) -> bool {
609        self.raw().readonly != 0
610    }
611
612    /// Gets the size of a single element, in bytes.
613    /// Important exception: when requesting an unformatted buffer, item_size still has the value
614    #[inline]
615    pub fn item_size(&self) -> usize {
616        self.raw().itemsize as usize
617    }
618
619    /// Gets the total number of items.
620    #[inline]
621    pub fn item_count(&self) -> usize {
622        (self.raw().len as usize) / (self.raw().itemsize as usize)
623    }
624
625    /// `item_size() * item_count()`.
626    /// For contiguous arrays, this is the length of the underlying memory block.
627    /// For non-contiguous arrays, it is the length that the logical structure would have if it were copied to a contiguous representation.
628    #[inline]
629    pub fn len_bytes(&self) -> usize {
630        self.raw().len as usize
631    }
632
633    /// Gets the number of dimensions.
634    ///
635    /// May be 0 to indicate a single scalar value.
636    #[inline]
637    pub fn dimensions(&self) -> usize {
638        self.raw().ndim as usize
639    }
640
641    /// Returns an array of length `dimensions`. `shape()[i]` is the length of the array in dimension number `i`.
642    ///
643    /// May return None for single-dimensional arrays or scalar values (`dimensions() <= 1`);
644    /// You can call `item_count()` to get the length of the single dimension.
645    ///
646    /// Despite Python using an array of signed integers, the values are guaranteed to be non-negative.
647    /// However, dimensions of length 0 are possible and might need special attention.
648    #[inline]
649    pub fn shape(&self) -> &[usize] {
650        unsafe { slice::from_raw_parts(self.raw().shape.cast(), self.raw().ndim as usize) }
651    }
652
653    /// Returns an array that holds, for each dimension, the number of bytes to skip to get to the next element in the dimension.
654    ///
655    /// Stride values can be any integer. For regular arrays, strides are usually positive,
656    /// but a consumer MUST be able to handle the case `strides[n] <= 0`.
657    #[inline]
658    pub fn strides(&self) -> &[isize] {
659        unsafe { slice::from_raw_parts(self.raw().strides, self.raw().ndim as usize) }
660    }
661
662    /// An array of length ndim.
663    /// If `suboffsets[n] >= 0`, the values stored along the nth dimension are pointers and the suboffset value dictates how many bytes to add to each pointer after de-referencing.
664    /// A suboffset value that is negative indicates that no de-referencing should occur (striding in a contiguous memory block).
665    ///
666    /// If all suboffsets are negative (i.e. no de-referencing is needed), then this field must be NULL (the default value).
667    #[inline]
668    pub fn suboffsets(&self) -> Option<&[isize]> {
669        unsafe {
670            if self.raw().suboffsets.is_null() {
671                None
672            } else {
673                Some(slice::from_raw_parts(
674                    self.raw().suboffsets,
675                    self.raw().ndim as usize,
676                ))
677            }
678        }
679    }
680
681    /// A string in struct module style syntax describing the contents of a single item.
682    #[inline]
683    pub fn format(&self) -> &CStr {
684        if self.raw().format.is_null() {
685            ffi::c_str!("B")
686        } else {
687            unsafe { CStr::from_ptr(self.raw().format) }
688        }
689    }
690
691    /// Gets whether the buffer is contiguous in C-style order (last index varies fastest when visiting items in order of memory address).
692    #[inline]
693    pub fn is_c_contiguous(&self) -> bool {
694        unsafe { ffi::PyBuffer_IsContiguous(self.raw(), b'C' as std::ffi::c_char) != 0 }
695    }
696
697    /// Gets whether the buffer is contiguous in Fortran-style order (first index varies fastest when visiting items in order of memory address).
698    #[inline]
699    pub fn is_fortran_contiguous(&self) -> bool {
700        unsafe { ffi::PyBuffer_IsContiguous(self.raw(), b'F' as std::ffi::c_char) != 0 }
701    }
702
703    fn raw(&self) -> &ffi::Py_buffer {
704        &self.0 .0
705    }
706}
707
708impl RawBuffer {
709    /// Release the contents of this pinned buffer.
710    ///
711    /// # Safety
712    ///
713    /// - The buffer must not be used after calling this function.
714    /// - This function can only be called once.
715    /// - Must be attached to the interpreter.
716    ///
717    unsafe fn release(self: &mut Pin<Box<Self>>) {
718        unsafe {
719            ffi::PyBuffer_Release(&mut Pin::get_unchecked_mut(self.as_mut()).0);
720        }
721    }
722}
723
724impl Drop for PyUntypedBuffer {
725    fn drop(&mut self) {
726        if Python::try_attach(|_| unsafe { self.0.release() }).is_none()
727            && crate::internal::state::is_in_gc_traversal()
728        {
729            eprintln!("Warning: PyBuffer dropped while in GC traversal, this is a bug and will leak memory.");
730        }
731        // If `try_attach` failed and `is_in_gc_traversal()` is false, then probably the interpreter has
732        // already finalized and we can just assume that the underlying memory has already been freed.
733        //
734        // So we don't handle that case here.
735    }
736}
737
738/// Like [std::cell::Cell], but only provides read-only access to the data.
739///
740/// `&ReadOnlyCell<T>` is basically a safe version of `*const T`:
741///  The data cannot be modified through the reference, but other references may
742///  be modifying the data.
743#[repr(transparent)]
744pub struct ReadOnlyCell<T: Element>(cell::UnsafeCell<T>);
745
746impl<T: Element> ReadOnlyCell<T> {
747    /// Returns a copy of the current value.
748    #[inline]
749    pub fn get(&self) -> T {
750        unsafe { *self.0.get() }
751    }
752
753    /// Returns a pointer to the current value.
754    #[inline]
755    pub fn as_ptr(&self) -> *const T {
756        self.0.get()
757    }
758}
759
760macro_rules! impl_element(
761    ($t:ty, $f:ident) => {
762        unsafe impl Element for $t {
763            fn is_compatible_format(format: &CStr) -> bool {
764                let slice = format.to_bytes();
765                if slice.len() > 1 && !is_matching_endian(slice[0]) {
766                    return false;
767                }
768                ElementType::from_format(format) == ElementType::$f { bytes: mem::size_of::<$t>() }
769            }
770        }
771    }
772);
773
774impl_element!(u8, UnsignedInteger);
775impl_element!(u16, UnsignedInteger);
776impl_element!(u32, UnsignedInteger);
777impl_element!(u64, UnsignedInteger);
778impl_element!(usize, UnsignedInteger);
779impl_element!(i8, SignedInteger);
780impl_element!(i16, SignedInteger);
781impl_element!(i32, SignedInteger);
782impl_element!(i64, SignedInteger);
783impl_element!(isize, SignedInteger);
784impl_element!(f32, Float);
785impl_element!(f64, Float);
786
787#[repr(u8)]
788enum PyBufferContiguity {
789    Undefined = 0,
790    C = 1,
791    F = 2,
792    Any = 3,
793}
794
795const CONTIGUITY_UNDEFINED: u8 = PyBufferContiguity::Undefined as u8;
796const CONTIGUITY_C: u8 = PyBufferContiguity::C as u8;
797const CONTIGUITY_F: u8 = PyBufferContiguity::F as u8;
798const CONTIGUITY_ANY: u8 = PyBufferContiguity::Any as u8;
799
800/// Type-safe buffer request flags. The const parameters encode which fields
801/// the exporter is required to fill.
802pub struct PyBufferFlags<
803    const FORMAT: bool = false,
804    const SHAPE: bool = false,
805    const STRIDE: bool = false,
806    const WRITABLE: bool = false,
807    const CONTIGUITY: u8 = CONTIGUITY_UNDEFINED,
808>(c_int);
809
810mod py_buffer_flags_sealed {
811    pub trait Sealed {}
812    impl<
813            const FORMAT: bool,
814            const SHAPE: bool,
815            const STRIDE: bool,
816            const WRITABLE: bool,
817            const CONTIGUITY: u8,
818        > Sealed for super::PyBufferFlags<FORMAT, SHAPE, STRIDE, WRITABLE, CONTIGUITY>
819    {
820    }
821}
822
823/// Trait implemented by all [`PyBufferFlags`] instantiations.
824pub trait PyBufferFlagsType: py_buffer_flags_sealed::Sealed {
825    /// The contiguity requirement encoded by these flags.
826    const CONTIGUITY: u8;
827}
828
829impl<
830        const FORMAT: bool,
831        const SHAPE: bool,
832        const STRIDE: bool,
833        const WRITABLE: bool,
834        const CONTIGUITY_REQ: u8,
835    > PyBufferFlagsType for PyBufferFlags<FORMAT, SHAPE, STRIDE, WRITABLE, CONTIGUITY_REQ>
836{
837    const CONTIGUITY: u8 = CONTIGUITY_REQ;
838}
839
840impl<const SHAPE: bool, const STRIDE: bool, const WRITABLE: bool, const CONTIGUITY: u8>
841    PyBufferFlags<false, SHAPE, STRIDE, WRITABLE, CONTIGUITY>
842{
843    /// Request format information.
844    pub const fn format(self) -> PyBufferFlags<true, SHAPE, STRIDE, WRITABLE, CONTIGUITY> {
845        PyBufferFlags(self.0 | ffi::PyBUF_FORMAT)
846    }
847}
848
849impl<const FORMAT: bool, const STRIDE: bool, const WRITABLE: bool, const CONTIGUITY: u8>
850    PyBufferFlags<FORMAT, false, STRIDE, WRITABLE, CONTIGUITY>
851{
852    /// Request shape information.
853    pub const fn nd(self) -> PyBufferFlags<FORMAT, true, STRIDE, WRITABLE, CONTIGUITY> {
854        PyBufferFlags(self.0 | ffi::PyBUF_ND)
855    }
856}
857
858impl<const FORMAT: bool, const SHAPE: bool, const WRITABLE: bool, const CONTIGUITY: u8>
859    PyBufferFlags<FORMAT, SHAPE, false, WRITABLE, CONTIGUITY>
860{
861    /// Request strides information. Implies shape.
862    pub const fn strides(self) -> PyBufferFlags<FORMAT, true, true, WRITABLE, CONTIGUITY> {
863        PyBufferFlags(self.0 | ffi::PyBUF_STRIDES)
864    }
865
866    /// Request suboffsets (indirect). Implies shape and strides.
867    pub const fn indirect(self) -> PyBufferFlags<FORMAT, true, true, WRITABLE, CONTIGUITY> {
868        PyBufferFlags(self.0 | ffi::PyBUF_INDIRECT)
869    }
870}
871
872impl<const FORMAT: bool, const SHAPE: bool, const STRIDE: bool, const CONTIGUITY: u8>
873    PyBufferFlags<FORMAT, SHAPE, STRIDE, false, CONTIGUITY>
874{
875    /// Request a writable buffer.
876    pub const fn writable(self) -> PyBufferFlags<FORMAT, SHAPE, STRIDE, true, CONTIGUITY> {
877        PyBufferFlags(self.0 | ffi::PyBUF_WRITABLE)
878    }
879}
880
881impl<const FORMAT: bool, const SHAPE: bool, const STRIDE: bool, const WRITABLE: bool>
882    PyBufferFlags<FORMAT, SHAPE, STRIDE, WRITABLE, CONTIGUITY_UNDEFINED>
883{
884    /// Require C-contiguous layout. Implies shape and strides.
885    pub const fn c_contiguous(self) -> PyBufferFlags<FORMAT, true, true, WRITABLE, CONTIGUITY_C> {
886        PyBufferFlags(self.0 | ffi::PyBUF_C_CONTIGUOUS)
887    }
888
889    /// Require Fortran-contiguous layout. Implies shape and strides.
890    pub const fn f_contiguous(self) -> PyBufferFlags<FORMAT, true, true, WRITABLE, CONTIGUITY_F> {
891        PyBufferFlags(self.0 | ffi::PyBUF_F_CONTIGUOUS)
892    }
893
894    /// Require contiguous layout (C or Fortran). Implies shape and strides.
895    ///
896    /// The specific contiguity order is not known at compile time,
897    /// so this does not unlock non-Option slice accessors.
898    pub const fn any_contiguous(
899        self,
900    ) -> PyBufferFlags<FORMAT, true, true, WRITABLE, CONTIGUITY_ANY> {
901        PyBufferFlags(self.0 | ffi::PyBUF_ANY_CONTIGUOUS)
902    }
903}
904
905impl PyBufferFlags {
906    /// Create a base buffer request. Chain builder methods to add flags.
907    pub const fn simple() -> PyBufferFlags {
908        PyBufferFlags(ffi::PyBUF_SIMPLE)
909    }
910
911    /// Create a writable request for all buffer information including suboffsets.
912    pub const fn full() -> PyBufferFlags<true, true, true, true> {
913        PyBufferFlags(ffi::PyBUF_FULL)
914    }
915
916    /// Create a read-only request for all buffer information including suboffsets.
917    pub const fn full_ro() -> PyBufferFlags<true, true, true> {
918        PyBufferFlags(ffi::PyBUF_FULL_RO)
919    }
920
921    /// Create a writable request for format, shape, and strides.
922    pub const fn records() -> PyBufferFlags<true, true, true, true> {
923        PyBufferFlags(ffi::PyBUF_RECORDS)
924    }
925
926    /// Create a read-only request for format, shape, and strides.
927    pub const fn records_ro() -> PyBufferFlags<true, true, true> {
928        PyBufferFlags(ffi::PyBUF_RECORDS_RO)
929    }
930
931    /// Create a writable request for shape and strides.
932    pub const fn strided() -> PyBufferFlags<false, true, true, true> {
933        PyBufferFlags(ffi::PyBUF_STRIDED)
934    }
935
936    /// Create a read-only request for shape and strides.
937    pub const fn strided_ro() -> PyBufferFlags<false, true, true> {
938        PyBufferFlags(ffi::PyBUF_STRIDED_RO)
939    }
940
941    /// Create a writable C-contiguous request.
942    pub const fn contig() -> PyBufferFlags<false, true, false, true, CONTIGUITY_C> {
943        PyBufferFlags(ffi::PyBUF_CONTIG)
944    }
945
946    /// Create a read-only C-contiguous request.
947    pub const fn contig_ro() -> PyBufferFlags<false, true, false, false, CONTIGUITY_C> {
948        PyBufferFlags(ffi::PyBUF_CONTIG_RO)
949    }
950}
951
952/// A typed form of [`PyUntypedBufferView`]. Not constructible directly — use
953/// [`PyBufferView::with()`] or [`PyBufferView::with_flags()`].
954#[repr(transparent)]
955pub struct PyBufferView<T, Flags: PyBufferFlagsType = PyBufferFlags<true, true, true>>(
956    PyUntypedBufferView<Flags>,
957    PhantomData<[T]>,
958);
959
960/// Stack-allocated untyped buffer view.
961///
962/// Unlike [`PyUntypedBuffer`] which heap-allocates, this places the `Py_buffer` on the
963/// stack. The scoped closure API ensures the buffer cannot be moved.
964///
965/// Use [`with_flags()`](Self::with_flags) with a [`PyBufferFlags`] value to acquire a view.
966/// The available accessors depend on the flags used.
967pub struct PyUntypedBufferView<Flags: PyBufferFlagsType = PyBufferFlags> {
968    raw: ffi::Py_buffer,
969    _flags: PhantomData<Flags>,
970}
971
972impl<Flags: PyBufferFlagsType> PyUntypedBufferView<Flags> {
973    /// Gets the pointer to the start of the buffer memory.
974    #[inline]
975    pub fn buf_ptr(&self) -> *mut c_void {
976        self.raw.buf
977    }
978
979    /// Returns the Python object that owns the buffer data.
980    #[inline]
981    pub fn obj<'py>(&self, py: Python<'py>) -> Option<&Bound<'py, PyAny>> {
982        unsafe { Bound::ref_from_ptr_or_opt(py, &self.raw.obj).as_ref() }
983    }
984
985    /// Gets whether the underlying buffer is read-only.
986    #[inline]
987    pub fn readonly(&self) -> bool {
988        self.raw.readonly != 0
989    }
990
991    /// Gets the size of a single element, in bytes.
992    #[inline]
993    pub fn item_size(&self) -> usize {
994        self.raw.itemsize as usize
995    }
996
997    /// Gets the total number of items.
998    #[inline]
999    pub fn item_count(&self) -> usize {
1000        (self.raw.len as usize) / (self.raw.itemsize as usize)
1001    }
1002
1003    /// `item_size() * item_count()`.
1004    /// For contiguous arrays, this is the length of the underlying memory block.
1005    #[inline]
1006    pub fn len_bytes(&self) -> usize {
1007        self.raw.len as usize
1008    }
1009
1010    /// Gets the number of dimensions.
1011    ///
1012    /// May be 0 to indicate a single scalar value.
1013    #[inline]
1014    pub fn dimensions(&self) -> usize {
1015        self.raw.ndim as usize
1016    }
1017
1018    /// Returns the suboffsets array.
1019    ///
1020    /// May return `None` even with `PyBUF_INDIRECT` if the exporter sets `suboffsets` to NULL.
1021    #[inline]
1022    pub fn suboffsets(&self) -> Option<&[isize]> {
1023        if self.raw.suboffsets.is_null() {
1024            return None;
1025        }
1026
1027        Some(unsafe { slice::from_raw_parts(self.raw.suboffsets, self.raw.ndim as usize) })
1028    }
1029
1030    /// Gets whether the buffer is contiguous in C-style order.
1031    #[inline]
1032    pub fn is_c_contiguous(&self) -> bool {
1033        Flags::CONTIGUITY == CONTIGUITY_C
1034            || unsafe { ffi::PyBuffer_IsContiguous(&self.raw, b'C' as std::ffi::c_char) != 0 }
1035    }
1036
1037    /// Gets whether the buffer is contiguous in Fortran-style order.
1038    #[inline]
1039    pub fn is_fortran_contiguous(&self) -> bool {
1040        Flags::CONTIGUITY == CONTIGUITY_F
1041            || unsafe { ffi::PyBuffer_IsContiguous(&self.raw, b'F' as std::ffi::c_char) != 0 }
1042    }
1043}
1044
1045impl<const SHAPE: bool, const STRIDE: bool, const WRITABLE: bool, const CONTIGUITY: u8>
1046    PyUntypedBufferView<PyBufferFlags<true, SHAPE, STRIDE, WRITABLE, CONTIGUITY>>
1047{
1048    /// A [struct module style](https://docs.python.org/3/c-api/buffer.html#c.Py_buffer.format)
1049    /// string describing the contents of a single item.
1050    #[inline]
1051    pub fn format(&self) -> &CStr {
1052        debug_assert!(!self.raw.format.is_null());
1053        unsafe { CStr::from_ptr(self.raw.format) }
1054    }
1055
1056    /// Attempt to interpret this untyped view as containing elements of type `T`.
1057    pub fn as_typed<T: Element>(
1058        &self,
1059    ) -> PyResult<&PyBufferView<T, PyBufferFlags<true, SHAPE, STRIDE, WRITABLE, CONTIGUITY>>> {
1060        self.ensure_compatible_with::<T>()?;
1061        // SAFETY: PyBufferView<T, ..> is repr(transparent) around PyUntypedBufferView<..>
1062        Ok(unsafe {
1063            NonNull::from(self)
1064                .cast::<PyBufferView<T, PyBufferFlags<true, SHAPE, STRIDE, WRITABLE, CONTIGUITY>>>()
1065                .as_ref()
1066        })
1067    }
1068
1069    fn ensure_compatible_with<T: Element>(&self) -> PyResult<()> {
1070        check_buffer_compatibility::<T>(self.raw.buf, self.item_size(), self.format())
1071    }
1072}
1073
1074impl<const FORMAT: bool, const STRIDE: bool, const WRITABLE: bool, const CONTIGUITY: u8>
1075    PyUntypedBufferView<PyBufferFlags<FORMAT, true, STRIDE, WRITABLE, CONTIGUITY>>
1076{
1077    /// Returns the shape array. `shape[i]` is the length of dimension `i`.
1078    ///
1079    /// Despite Python using an array of signed integers, the values are guaranteed to be
1080    /// non-negative. However, dimensions of length 0 are possible and might need special
1081    /// attention.
1082    #[inline]
1083    pub fn shape(&self) -> &[usize] {
1084        debug_assert!(!self.raw.shape.is_null());
1085        unsafe { slice::from_raw_parts(self.raw.shape.cast(), self.raw.ndim as usize) }
1086    }
1087}
1088
1089impl<const FORMAT: bool, const SHAPE: bool, const WRITABLE: bool, const CONTIGUITY: u8>
1090    PyUntypedBufferView<PyBufferFlags<FORMAT, SHAPE, true, WRITABLE, CONTIGUITY>>
1091{
1092    /// Returns the strides array.
1093    ///
1094    /// Stride values can be any integer. For regular arrays, strides are usually positive,
1095    /// but a consumer MUST be able to handle the case `strides[n] <= 0`.
1096    #[inline]
1097    pub fn strides(&self) -> &[isize] {
1098        debug_assert!(!self.raw.strides.is_null());
1099        unsafe { slice::from_raw_parts(self.raw.strides, self.raw.ndim as usize) }
1100    }
1101}
1102
1103// SIMPLE and WRITABLE requests guarantee the implicit "B" format.
1104impl<const WRITABLE: bool>
1105    PyUntypedBufferView<PyBufferFlags<false, false, false, WRITABLE, CONTIGUITY_UNDEFINED>>
1106{
1107    /// Returns the format string for a simple byte buffer, which is always `"B"`.
1108    #[inline]
1109    pub fn format(&self) -> &CStr {
1110        ffi::c_str!("B")
1111    }
1112}
1113
1114/// Check that a buffer is compatible with element type `T`.
1115fn check_buffer_compatibility<T: Element>(
1116    buf: *mut c_void,
1117    itemsize: usize,
1118    format: &CStr,
1119) -> PyResult<()> {
1120    let name = std::any::type_name::<T>();
1121
1122    if mem::size_of::<T>() != itemsize || !T::is_compatible_format(format) {
1123        return Err(PyBufferError::new_err(format!(
1124            "buffer contents are not compatible with {name}"
1125        )));
1126    }
1127
1128    if buf.align_offset(mem::align_of::<T>()) != 0 {
1129        return Err(PyBufferError::new_err(format!(
1130            "buffer contents are insufficiently aligned for {name}"
1131        )));
1132    }
1133
1134    Ok(())
1135}
1136
1137impl PyUntypedBufferView {
1138    /// Acquire a buffer view with the given flags,
1139    /// pass it to `f`, then release the buffer.
1140    ///
1141    /// Use [`PyBufferFlags::simple()`] or one of the compound-request constructors such as
1142    /// [`PyBufferFlags::full_ro()`] to acquire a view.
1143    pub fn with_flags<
1144        const FORMAT: bool,
1145        const SHAPE: bool,
1146        const STRIDE: bool,
1147        const WRITABLE: bool,
1148        const CONTIGUITY: u8,
1149        R,
1150    >(
1151        obj: &Bound<'_, PyAny>,
1152        flags: PyBufferFlags<FORMAT, SHAPE, STRIDE, WRITABLE, CONTIGUITY>,
1153        f: impl FnOnce(
1154            &PyUntypedBufferView<PyBufferFlags<FORMAT, SHAPE, STRIDE, WRITABLE, CONTIGUITY>>,
1155        ) -> R,
1156    ) -> PyResult<R> {
1157        let mut raw = mem::MaybeUninit::<ffi::Py_buffer>::uninit();
1158
1159        err::error_on_minusone(obj.py(), unsafe {
1160            ffi::PyObject_GetBuffer(obj.as_ptr(), raw.as_mut_ptr(), flags.0)
1161        })?;
1162
1163        let view = PyUntypedBufferView {
1164            raw: unsafe { raw.assume_init() },
1165            _flags: PhantomData,
1166        };
1167
1168        Ok(f(&view))
1169    }
1170}
1171
1172fn debug_buffer_view(
1173    name: &str,
1174    raw: &ffi::Py_buffer,
1175    f: &mut std::fmt::Formatter<'_>,
1176) -> std::fmt::Result {
1177    let ndim = raw.ndim as usize;
1178    let format = NonNull::new(raw.format).map(|p| unsafe { CStr::from_ptr(p.as_ptr()) });
1179    let shape = NonNull::new(raw.shape)
1180        .map(|p| unsafe { slice::from_raw_parts(p.as_ptr().cast::<usize>(), ndim) });
1181    let strides =
1182        NonNull::new(raw.strides).map(|p| unsafe { slice::from_raw_parts(p.as_ptr(), ndim) });
1183    let suboffsets =
1184        NonNull::new(raw.suboffsets).map(|p| unsafe { slice::from_raw_parts(p.as_ptr(), ndim) });
1185
1186    f.debug_struct(name)
1187        .field("buf", &raw.buf)
1188        .field("obj", &raw.obj)
1189        .field("len", &raw.len)
1190        .field("itemsize", &raw.itemsize)
1191        .field("readonly", &raw.readonly)
1192        .field("ndim", &raw.ndim)
1193        .field("format", &format)
1194        .field("shape", &shape)
1195        .field("strides", &strides)
1196        .field("suboffsets", &suboffsets)
1197        .field("internal", &raw.internal)
1198        .finish()
1199}
1200
1201impl<Flags: PyBufferFlagsType> Drop for PyUntypedBufferView<Flags> {
1202    fn drop(&mut self) {
1203        unsafe { ffi::PyBuffer_Release(&mut self.raw) }
1204    }
1205}
1206
1207impl<Flags: PyBufferFlagsType> Debug for PyUntypedBufferView<Flags> {
1208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1209        debug_buffer_view("PyUntypedBufferView", &self.raw, f)
1210    }
1211}
1212
1213impl<T: Element> PyBufferView<T> {
1214    /// Acquire a typed buffer view with `PyBufferFlags::full_ro()` flags,
1215    /// validating that the buffer format is compatible with `T`.
1216    pub fn with<R>(obj: &Bound<'_, PyAny>, f: impl FnOnce(&PyBufferView<T>) -> R) -> PyResult<R> {
1217        PyUntypedBufferView::with_flags(obj, PyBufferFlags::full_ro(), |view| {
1218            view.as_typed::<T>().map(f)
1219        })?
1220    }
1221
1222    /// Acquire a typed buffer view with the given flags.
1223    ///
1224    /// [`ffi::PyBUF_FORMAT`] is implicitly added for type validation.
1225    pub fn with_flags<
1226        const FORMAT: bool,
1227        const SHAPE: bool,
1228        const STRIDE: bool,
1229        const WRITABLE: bool,
1230        const CONTIGUITY: u8,
1231        R,
1232    >(
1233        obj: &Bound<'_, PyAny>,
1234        flags: PyBufferFlags<FORMAT, SHAPE, STRIDE, WRITABLE, CONTIGUITY>,
1235        f: impl FnOnce(&PyBufferView<T, PyBufferFlags<true, SHAPE, STRIDE, WRITABLE, CONTIGUITY>>) -> R,
1236    ) -> PyResult<R> {
1237        let mut raw = mem::MaybeUninit::<ffi::Py_buffer>::uninit();
1238
1239        err::error_on_minusone(obj.py(), unsafe {
1240            ffi::PyObject_GetBuffer(obj.as_ptr(), raw.as_mut_ptr(), flags.0 | ffi::PyBUF_FORMAT)
1241        })?;
1242
1243        let view = PyUntypedBufferView::<PyBufferFlags<true, SHAPE, STRIDE, WRITABLE, CONTIGUITY>> {
1244            raw: unsafe { raw.assume_init() },
1245            _flags: PhantomData,
1246        };
1247
1248        view.as_typed::<T>().map(f)
1249    }
1250}
1251
1252impl<T: Element, Flags: PyBufferFlagsType> PyBufferView<T, Flags> {
1253    /// Gets the buffer memory as a slice.
1254    ///
1255    /// Returns `None` if the buffer is not C-contiguous.
1256    ///
1257    /// The returned slice uses type [`ReadOnlyCell<T>`] because it's theoretically possible
1258    /// for any call into the Python runtime to modify the values in the slice.
1259    pub fn as_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [ReadOnlyCell<T>]> {
1260        if !self.is_c_contiguous() {
1261            return None;
1262        }
1263
1264        Some(unsafe { slice::from_raw_parts(self.0.raw.buf.cast(), self.item_count()) })
1265    }
1266
1267    /// Gets the buffer memory as a mutable slice.
1268    ///
1269    /// Returns `None` if the buffer is read-only or not C-contiguous.
1270    ///
1271    /// The returned slice uses type [`Cell<T>`](cell::Cell) because it's theoretically possible
1272    /// for any call into the Python runtime to modify the values in the slice.
1273    pub fn as_mut_slice<'a>(&'a self, _py: Python<'a>) -> Option<&'a [cell::Cell<T>]> {
1274        if self.readonly() || !self.is_c_contiguous() {
1275            return None;
1276        }
1277
1278        Some(unsafe { slice::from_raw_parts(self.0.raw.buf.cast(), self.item_count()) })
1279    }
1280}
1281
1282// C-contiguous guaranteed — no contiguity check needed.
1283impl<
1284        T: Element,
1285        const FORMAT: bool,
1286        const SHAPE: bool,
1287        const STRIDE: bool,
1288        const WRITABLE: bool,
1289    > PyBufferView<T, PyBufferFlags<FORMAT, SHAPE, STRIDE, WRITABLE, CONTIGUITY_C>>
1290{
1291    /// Gets the buffer memory as a slice. The buffer is guaranteed C-contiguous.
1292    pub fn as_contiguous_slice<'a>(&'a self, _py: Python<'a>) -> &'a [ReadOnlyCell<T>] {
1293        unsafe { slice::from_raw_parts(self.0.raw.buf.cast(), self.item_count()) }
1294    }
1295}
1296
1297// C-contiguous + writable guaranteed — no checks needed.
1298impl<T: Element, const FORMAT: bool, const SHAPE: bool, const STRIDE: bool>
1299    PyBufferView<T, PyBufferFlags<FORMAT, SHAPE, STRIDE, true, CONTIGUITY_C>>
1300{
1301    /// Gets the buffer memory as a mutable slice.
1302    /// The buffer is guaranteed C-contiguous and writable.
1303    pub fn as_contiguous_mut_slice<'a>(&'a self, _py: Python<'a>) -> &'a [cell::Cell<T>] {
1304        unsafe { slice::from_raw_parts(self.0.raw.buf.cast(), self.item_count()) }
1305    }
1306}
1307
1308// Fortran-contiguous guaranteed — no contiguity check needed.
1309impl<
1310        T: Element,
1311        const FORMAT: bool,
1312        const SHAPE: bool,
1313        const STRIDE: bool,
1314        const WRITABLE: bool,
1315    > PyBufferView<T, PyBufferFlags<FORMAT, SHAPE, STRIDE, WRITABLE, CONTIGUITY_F>>
1316{
1317    /// Gets the buffer memory as a slice. The buffer is guaranteed Fortran-contiguous.
1318    pub fn as_fortran_contiguous_slice<'a>(&'a self, _py: Python<'a>) -> &'a [ReadOnlyCell<T>] {
1319        unsafe { slice::from_raw_parts(self.0.raw.buf.cast(), self.item_count()) }
1320    }
1321}
1322
1323// Fortran-contiguous + writable guaranteed — no checks needed.
1324impl<T: Element, const FORMAT: bool, const SHAPE: bool, const STRIDE: bool>
1325    PyBufferView<T, PyBufferFlags<FORMAT, SHAPE, STRIDE, true, CONTIGUITY_F>>
1326{
1327    /// Gets the buffer memory as a mutable slice.
1328    /// The buffer is guaranteed Fortran-contiguous and writable.
1329    pub fn as_fortran_contiguous_mut_slice<'a>(&'a self, _py: Python<'a>) -> &'a [cell::Cell<T>] {
1330        unsafe { slice::from_raw_parts(self.0.raw.buf.cast(), self.item_count()) }
1331    }
1332}
1333
1334impl<T, Flags: PyBufferFlagsType> std::ops::Deref for PyBufferView<T, Flags> {
1335    type Target = PyUntypedBufferView<Flags>;
1336
1337    fn deref(&self) -> &Self::Target {
1338        &self.0
1339    }
1340}
1341
1342impl<T, Flags: PyBufferFlagsType> Debug for PyBufferView<T, Flags> {
1343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1344        debug_buffer_view("PyBufferView", &self.0.raw, f)
1345    }
1346}
1347
1348#[cfg(test)]
1349mod tests {
1350    use super::*;
1351
1352    use crate::ffi;
1353    use crate::types::any::PyAnyMethods;
1354    use crate::types::PyBytes;
1355    use crate::Python;
1356
1357    #[test]
1358    fn test_debug() {
1359        Python::attach(|py| {
1360            let bytes = PyBytes::new(py, b"abcde");
1361            let buffer: PyBuffer<u8> = PyBuffer::get(&bytes).unwrap();
1362            let expected = format!(
1363                concat!(
1364                    "PyBuffer {{ buf: {:?}, obj: {:?}, ",
1365                    "len: 5, itemsize: 1, readonly: 1, ",
1366                    "ndim: 1, format: \"B\", shape: [5], ",
1367                    "strides: [1], suboffsets: None, internal: {:?} }}",
1368                ),
1369                buffer.raw().buf,
1370                buffer.raw().obj,
1371                buffer.raw().internal
1372            );
1373            let debug_repr = format!("{:?}", buffer);
1374            assert_eq!(debug_repr, expected);
1375        });
1376    }
1377
1378    #[test]
1379    fn test_element_type_from_format() {
1380        use super::ElementType::*;
1381        use std::mem::size_of;
1382
1383        for (cstr, expected) in [
1384            // @ prefix goes to native_element_type_from_type_char
1385            (
1386                c"@b",
1387                SignedInteger {
1388                    bytes: size_of::<c_schar>(),
1389                },
1390            ),
1391            (
1392                c"@c",
1393                UnsignedInteger {
1394                    bytes: size_of::<c_char>(),
1395                },
1396            ),
1397            (
1398                c"@b",
1399                SignedInteger {
1400                    bytes: size_of::<c_schar>(),
1401                },
1402            ),
1403            (
1404                c"@B",
1405                UnsignedInteger {
1406                    bytes: size_of::<c_uchar>(),
1407                },
1408            ),
1409            (c"@?", Bool),
1410            (
1411                c"@h",
1412                SignedInteger {
1413                    bytes: size_of::<c_short>(),
1414                },
1415            ),
1416            (
1417                c"@H",
1418                UnsignedInteger {
1419                    bytes: size_of::<c_ushort>(),
1420                },
1421            ),
1422            (
1423                c"@i",
1424                SignedInteger {
1425                    bytes: size_of::<c_int>(),
1426                },
1427            ),
1428            (
1429                c"@I",
1430                UnsignedInteger {
1431                    bytes: size_of::<c_uint>(),
1432                },
1433            ),
1434            (
1435                c"@l",
1436                SignedInteger {
1437                    bytes: size_of::<c_long>(),
1438                },
1439            ),
1440            (
1441                c"@L",
1442                UnsignedInteger {
1443                    bytes: size_of::<c_ulong>(),
1444                },
1445            ),
1446            (
1447                c"@q",
1448                SignedInteger {
1449                    bytes: size_of::<c_longlong>(),
1450                },
1451            ),
1452            (
1453                c"@Q",
1454                UnsignedInteger {
1455                    bytes: size_of::<c_ulonglong>(),
1456                },
1457            ),
1458            (
1459                c"@n",
1460                SignedInteger {
1461                    bytes: size_of::<libc::ssize_t>(),
1462                },
1463            ),
1464            (
1465                c"@N",
1466                UnsignedInteger {
1467                    bytes: size_of::<libc::size_t>(),
1468                },
1469            ),
1470            (c"@e", Float { bytes: 2 }),
1471            (c"@f", Float { bytes: 4 }),
1472            (c"@d", Float { bytes: 8 }),
1473            (c"@z", Unknown),
1474            // = prefix goes to standard_element_type_from_type_char
1475            (c"=b", SignedInteger { bytes: 1 }),
1476            (c"=c", UnsignedInteger { bytes: 1 }),
1477            (c"=B", UnsignedInteger { bytes: 1 }),
1478            (c"=?", Bool),
1479            (c"=h", SignedInteger { bytes: 2 }),
1480            (c"=H", UnsignedInteger { bytes: 2 }),
1481            (c"=l", SignedInteger { bytes: 4 }),
1482            (c"=l", SignedInteger { bytes: 4 }),
1483            (c"=I", UnsignedInteger { bytes: 4 }),
1484            (c"=L", UnsignedInteger { bytes: 4 }),
1485            (c"=q", SignedInteger { bytes: 8 }),
1486            (c"=Q", UnsignedInteger { bytes: 8 }),
1487            (c"=e", Float { bytes: 2 }),
1488            (c"=f", Float { bytes: 4 }),
1489            (c"=d", Float { bytes: 8 }),
1490            (c"=z", Unknown),
1491            (c"=0", Unknown),
1492            // bare char (no prefix) goes to native_element_type_from_type_char
1493            (
1494                c"b",
1495                SignedInteger {
1496                    bytes: size_of::<c_schar>(),
1497                },
1498            ),
1499            (
1500                c"B",
1501                UnsignedInteger {
1502                    bytes: size_of::<c_uchar>(),
1503                },
1504            ),
1505            (c"?", Bool),
1506            (c"f", Float { bytes: 4 }),
1507            (c"d", Float { bytes: 8 }),
1508            (c"z", Unknown),
1509            // <, >, ! prefixes go to standard_element_type_from_type_char
1510            (c"<i", SignedInteger { bytes: 4 }),
1511            (c">H", UnsignedInteger { bytes: 2 }),
1512            (c"!q", SignedInteger { bytes: 8 }),
1513            // unknown prefix -> Unknown
1514            (c":b", Unknown),
1515        ] {
1516            assert_eq!(
1517                ElementType::from_format(cstr),
1518                expected,
1519                "element from format &Cstr: {cstr:?}",
1520            );
1521        }
1522    }
1523
1524    #[test]
1525    fn test_compatible_size() {
1526        // for the cast in PyBuffer::shape()
1527        assert_eq!(
1528            std::mem::size_of::<ffi::Py_ssize_t>(),
1529            std::mem::size_of::<usize>()
1530        );
1531    }
1532
1533    #[test]
1534    fn test_bytes_buffer() {
1535        Python::attach(|py| {
1536            let bytes = PyBytes::new(py, b"abcde");
1537            let buffer = PyBuffer::get(&bytes).unwrap();
1538            assert_eq!(buffer.dimensions(), 1);
1539            assert_eq!(buffer.item_count(), 5);
1540            assert_eq!(buffer.format().to_str().unwrap(), "B");
1541            assert_eq!(buffer.shape(), [5]);
1542            // single-dimensional buffer is always contiguous
1543            assert!(buffer.is_c_contiguous());
1544            assert!(buffer.is_fortran_contiguous());
1545
1546            let slice = buffer.as_slice(py).unwrap();
1547            assert_eq!(slice.len(), 5);
1548            assert_eq!(slice[0].get(), b'a');
1549            assert_eq!(slice[2].get(), b'c');
1550            assert_eq!(unsafe { *slice[0].as_ptr() }, b'a');
1551
1552            assert_eq!(unsafe { *(buffer.get_ptr(&[1]).cast::<u8>()) }, b'b');
1553
1554            assert!(buffer.as_mut_slice(py).is_none());
1555
1556            assert!(buffer.copy_to_slice(py, &mut [0u8]).is_err());
1557            let mut arr = [0; 5];
1558            buffer.copy_to_slice(py, &mut arr).unwrap();
1559            assert_eq!(arr, b"abcde" as &[u8]);
1560
1561            assert!(buffer.copy_from_slice(py, &[0u8; 5]).is_err());
1562            assert_eq!(buffer.to_vec(py).unwrap(), b"abcde");
1563        });
1564    }
1565
1566    #[test]
1567    fn test_array_buffer() {
1568        Python::attach(|py| {
1569            let array = py
1570                .import("array")
1571                .unwrap()
1572                .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None)
1573                .unwrap();
1574            let buffer = PyBuffer::get(&array).unwrap();
1575            assert_eq!(buffer.dimensions(), 1);
1576            assert_eq!(buffer.item_count(), 4);
1577            assert_eq!(buffer.format().to_str().unwrap(), "f");
1578            assert_eq!(buffer.shape(), [4]);
1579
1580            // array creates a 1D contiguous buffer, so it's both C and F contiguous.  This would
1581            // be more interesting if we can come up with a 2D buffer but I think it would need a
1582            // third-party lib or a custom class.
1583
1584            // C-contiguous fns
1585            let slice = buffer.as_slice(py).unwrap();
1586            assert_eq!(slice.len(), 4);
1587            assert_eq!(slice[0].get(), 1.0);
1588            assert_eq!(slice[3].get(), 2.5);
1589
1590            let mut_slice = buffer.as_mut_slice(py).unwrap();
1591            assert_eq!(mut_slice.len(), 4);
1592            assert_eq!(mut_slice[0].get(), 1.0);
1593            mut_slice[3].set(2.75);
1594            assert_eq!(slice[3].get(), 2.75);
1595
1596            buffer
1597                .copy_from_slice(py, &[10.0f32, 11.0, 12.0, 13.0])
1598                .unwrap();
1599            assert_eq!(slice[2].get(), 12.0);
1600
1601            assert_eq!(buffer.to_vec(py).unwrap(), [10.0, 11.0, 12.0, 13.0]);
1602
1603            // F-contiguous fns
1604            let buffer = PyBuffer::get(&array).unwrap();
1605            let slice = buffer.as_fortran_slice(py).unwrap();
1606            assert_eq!(slice.len(), 4);
1607            assert_eq!(slice[1].get(), 11.0);
1608
1609            let mut_slice = buffer.as_fortran_mut_slice(py).unwrap();
1610            assert_eq!(mut_slice.len(), 4);
1611            assert_eq!(mut_slice[2].get(), 12.0);
1612            mut_slice[3].set(2.75);
1613            assert_eq!(slice[3].get(), 2.75);
1614
1615            buffer
1616                .copy_from_fortran_slice(py, &[10.0f32, 11.0, 12.0, 13.0])
1617                .unwrap();
1618            assert_eq!(slice[2].get(), 12.0);
1619
1620            assert_eq!(buffer.to_fortran_vec(py).unwrap(), [10.0, 11.0, 12.0, 13.0]);
1621        });
1622    }
1623
1624    #[test]
1625    fn test_obj_getter() {
1626        Python::attach(|py| {
1627            let bytes = PyBytes::new(py, b"hello");
1628            let buf = PyUntypedBuffer::get(bytes.as_any()).unwrap();
1629
1630            // obj() returns the same object that owns the buffer
1631            let owner = buf.obj(py).unwrap();
1632            assert!(owner.is_instance_of::<PyBytes>());
1633            assert!(owner.is(&bytes));
1634
1635            // can keep the owner alive after releasing the buffer
1636            let owner_ref: crate::Py<PyAny> = owner.clone().unbind();
1637            buf.release(py);
1638            drop(bytes);
1639            // owner_ref still valid after buffer and original are dropped
1640            Python::attach(|py| {
1641                let rebound = owner_ref.bind(py);
1642                assert!(rebound.is_instance_of::<PyBytes>());
1643            });
1644        });
1645    }
1646
1647    #[test]
1648    fn test_copy_to_fortran_slice() {
1649        Python::attach(|py| {
1650            let array = py
1651                .import("array")
1652                .unwrap()
1653                .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None)
1654                .unwrap();
1655            let buffer = PyBuffer::get(&array).unwrap();
1656
1657            // wrong length
1658            assert!(buffer.copy_to_fortran_slice(py, &mut [0.0f32]).is_err());
1659            // correct length
1660            let mut arr = [0.0f32; 4];
1661            buffer.copy_to_fortran_slice(py, &mut arr).unwrap();
1662            assert_eq!(arr, [1.0, 1.5, 2.0, 2.5]);
1663        });
1664    }
1665
1666    #[test]
1667    fn test_copy_from_slice_wrong_length() {
1668        Python::attach(|py| {
1669            let array = py
1670                .import("array")
1671                .unwrap()
1672                .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None)
1673                .unwrap();
1674            let buffer = PyBuffer::get(&array).unwrap();
1675            // writable buffer, but wrong length
1676            assert!(!buffer.readonly());
1677            assert!(buffer.copy_from_slice(py, &[0.0f32; 2]).is_err());
1678            assert!(buffer.copy_from_fortran_slice(py, &[0.0f32; 2]).is_err());
1679        });
1680    }
1681
1682    #[test]
1683    fn test_untyped_buffer() {
1684        Python::attach(|py| {
1685            let bytes = PyBytes::new(py, b"abcde");
1686            let buffer = PyUntypedBuffer::get(&bytes).unwrap();
1687            assert_eq!(buffer.dimensions(), 1);
1688            assert_eq!(buffer.item_count(), 5);
1689            assert_eq!(buffer.format().to_str().unwrap(), "B");
1690            assert_eq!(buffer.shape(), [5]);
1691            assert!(!buffer.buf_ptr().is_null());
1692            assert_eq!(buffer.strides(), &[1]);
1693            assert_eq!(buffer.len_bytes(), 5);
1694            assert_eq!(buffer.item_size(), 1);
1695            assert!(buffer.readonly());
1696            assert!(buffer.suboffsets().is_none());
1697
1698            assert!(format!("{:?}", buffer).starts_with("PyUntypedBuffer { buf: "));
1699
1700            let typed: &PyBuffer<u8> = buffer.as_typed().unwrap();
1701            assert_eq!(typed.dimensions(), 1);
1702            assert_eq!(typed.item_count(), 5);
1703            assert_eq!(typed.format().to_str().unwrap(), "B");
1704            assert_eq!(typed.shape(), [5]);
1705        });
1706    }
1707
1708    #[test]
1709    fn test_untyped_buffer_view() {
1710        Python::attach(|py| {
1711            let bytes = PyBytes::new(py, b"abcde");
1712            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::full_ro(), |view| {
1713                assert!(!view.buf_ptr().is_null());
1714                assert_eq!(view.len_bytes(), 5);
1715                assert_eq!(view.item_size(), 1);
1716                assert_eq!(view.item_count(), 5);
1717                assert!(view.readonly());
1718                assert_eq!(view.dimensions(), 1);
1719                // with_flags() uses PyBufferFlags::full_ro() — all Known, direct return types
1720                assert_eq!(view.format().to_str().unwrap(), "B");
1721                assert_eq!(view.shape(), [5]);
1722                assert_eq!(view.strides(), [1]);
1723                assert!(view.suboffsets().is_none());
1724                assert!(view.is_c_contiguous());
1725                assert!(view.is_fortran_contiguous());
1726                assert!(view.obj(py).unwrap().is(&bytes));
1727            })
1728            .unwrap();
1729        });
1730    }
1731
1732    #[test]
1733    fn test_typed_buffer_view() {
1734        Python::attach(|py| {
1735            let bytes = PyBytes::new(py, b"abcde");
1736            PyBufferView::<u8>::with(&bytes, |view| {
1737                assert_eq!(view.dimensions(), 1);
1738                assert_eq!(view.item_count(), 5);
1739                // PyBufferView::with uses PyBufferFlags::full_ro() — all Known
1740                assert_eq!(view.format().to_str().unwrap(), "B");
1741                assert_eq!(view.shape(), [5]);
1742
1743                let slice = view.as_slice(py).unwrap();
1744                assert_eq!(slice.len(), 5);
1745                assert_eq!(slice[0].get(), b'a');
1746                assert_eq!(slice[4].get(), b'e');
1747
1748                // bytes are read-only
1749                assert!(view.as_mut_slice(py).is_none());
1750            })
1751            .unwrap();
1752        });
1753    }
1754
1755    #[test]
1756    fn test_buffer_view_array() {
1757        Python::attach(|py| {
1758            let array = py
1759                .import("array")
1760                .unwrap()
1761                .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None)
1762                .unwrap();
1763            PyBufferView::<f32>::with(&array, |view| {
1764                assert_eq!(view.dimensions(), 1);
1765                assert_eq!(view.item_count(), 4);
1766                assert_eq!(view.format().to_str().unwrap(), "f");
1767                assert_eq!(view.shape(), [4]);
1768
1769                let slice = view.as_slice(py).unwrap();
1770                assert_eq!(slice.len(), 4);
1771                assert_eq!(slice[0].get(), 1.0);
1772                assert_eq!(slice[3].get(), 2.5);
1773
1774                // array.array is writable
1775                let mut_slice = view.as_mut_slice(py).unwrap();
1776                assert_eq!(mut_slice[0].get(), 1.0);
1777                mut_slice[3].set(2.75);
1778                assert_eq!(slice[3].get(), 2.75);
1779            })
1780            .unwrap();
1781        });
1782    }
1783
1784    #[test]
1785    fn test_buffer_view_with_flags() {
1786        Python::attach(|py| {
1787            let bytes = PyBytes::new(py, b"abcde");
1788
1789            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::simple(), |view| {
1790                assert_eq!(view.item_count(), 5);
1791                assert_eq!(view.len_bytes(), 5);
1792                assert!(view.readonly());
1793                assert!(view.suboffsets().is_none());
1794            })
1795            .unwrap();
1796
1797            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::simple().nd(), |view| {
1798                assert_eq!(view.item_count(), 5);
1799                assert_eq!(view.shape(), [5]);
1800            })
1801            .unwrap();
1802
1803            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::simple().strides(), |view| {
1804                assert_eq!(view.shape(), [5]);
1805                assert_eq!(view.strides(), [1]);
1806            })
1807            .unwrap();
1808
1809            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::simple().format(), |view| {
1810                assert_eq!(view.item_count(), 5);
1811                assert_eq!(view.format().to_str().unwrap(), "B");
1812            })
1813            .unwrap();
1814        });
1815    }
1816
1817    #[test]
1818    fn test_typed_buffer_view_with_flags() {
1819        Python::attach(|py| {
1820            let array = py
1821                .import("array")
1822                .unwrap()
1823                .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None)
1824                .unwrap();
1825
1826            PyBufferView::<f32>::with_flags(&array, PyBufferFlags::simple().nd(), |view| {
1827                assert_eq!(view.item_count(), 4);
1828                assert_eq!(view.format().to_str().unwrap(), "f");
1829                assert_eq!(view.shape(), [4]);
1830
1831                let slice = view.as_slice(py).unwrap();
1832                assert_eq!(slice[0].get(), 1.0);
1833                assert_eq!(slice[3].get(), 2.5);
1834
1835                let mut_slice = view.as_mut_slice(py).unwrap();
1836                mut_slice[0].set(9.0);
1837                assert_eq!(slice[0].get(), 9.0);
1838            })
1839            .unwrap();
1840        });
1841    }
1842
1843    #[test]
1844    fn test_typed_buffer_view_with_flags_incompatible() {
1845        Python::attach(|py| {
1846            let bytes = PyBytes::new(py, b"abcde");
1847            let result =
1848                PyBufferView::<f32>::with_flags(&bytes, PyBufferFlags::simple().nd(), |_view| {});
1849            assert!(result.is_err());
1850        });
1851    }
1852
1853    #[test]
1854    fn test_c_contiguous_slice() {
1855        Python::attach(|py| {
1856            let array = py
1857                .import("array")
1858                .unwrap()
1859                .call_method("array", ("f", (1.0, 1.5, 2.0)), None)
1860                .unwrap();
1861
1862            // C_CONTIGUOUS: guaranteed contiguous readonly access (no Option)
1863            PyBufferView::<f32>::with_flags(
1864                &array,
1865                PyBufferFlags::simple().c_contiguous(),
1866                |view| {
1867                    let slice = view.as_contiguous_slice(py);
1868                    assert_eq!(slice.len(), 3);
1869                    assert_eq!(slice[0].get(), 1.0);
1870                    assert_eq!(slice[2].get(), 2.0);
1871                },
1872            )
1873            .unwrap();
1874
1875            // C_CONTIGUOUS | WRITABLE (via CONTIG combined with STRIDES-level):
1876            // no predefined constant, but we can use PyBufferView::with on a writable array
1877            // and the Option-based as_mut_slice still works
1878            PyBufferView::<f32>::with(&array, |view| {
1879                let mut_slice = view.as_mut_slice(py).unwrap();
1880                mut_slice[2].set(9.0);
1881                assert_eq!(view.as_slice(py).unwrap()[2].get(), 9.0);
1882            })
1883            .unwrap();
1884        });
1885    }
1886
1887    #[test]
1888    fn test_buffer_view_error() {
1889        Python::attach(|py| {
1890            let list = crate::types::PyList::empty(py);
1891            let result =
1892                PyUntypedBufferView::with_flags(&list, PyBufferFlags::full_ro(), |_view| {});
1893            assert!(result.is_err());
1894        });
1895    }
1896
1897    #[test]
1898    fn test_flag_builders() {
1899        Python::attach(|py| {
1900            let bytes = PyBytes::new(py, b"abcde");
1901            let array = py
1902                .import("array")
1903                .unwrap()
1904                .call_method("array", ("f", (1.0, 1.5, 2.0, 2.5)), None)
1905                .unwrap();
1906
1907            // Primitive builders
1908            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::simple(), |view| {
1909                assert_eq!(view.format().to_str().unwrap(), "B");
1910            })
1911            .unwrap();
1912
1913            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::simple().format(), |view| {
1914                assert_eq!(view.format().to_str().unwrap(), "B");
1915            })
1916            .unwrap();
1917
1918            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::simple().nd(), |view| {
1919                assert_eq!(view.shape(), [5]);
1920            })
1921            .unwrap();
1922
1923            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::simple().strides(), |view| {
1924                assert_eq!(view.shape(), [5]);
1925                assert_eq!(view.strides(), [1]);
1926            })
1927            .unwrap();
1928
1929            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::simple().indirect(), |view| {
1930                assert_eq!(view.shape(), [5]);
1931                assert_eq!(view.strides(), [1]);
1932            })
1933            .unwrap();
1934
1935            PyUntypedBufferView::with_flags(&array, PyBufferFlags::simple().writable(), |view| {
1936                assert_eq!(view.format().to_str().unwrap(), "B");
1937                assert!(!view.readonly());
1938            })
1939            .unwrap();
1940
1941            PyUntypedBufferView::with_flags(
1942                &array,
1943                PyBufferFlags::simple().writable().nd(),
1944                |view| {
1945                    assert_eq!(view.shape(), [4]);
1946                    assert!(!view.readonly());
1947                },
1948            )
1949            .unwrap();
1950
1951            // Chained primitive builders
1952            PyUntypedBufferView::with_flags(
1953                &bytes,
1954                PyBufferFlags::simple().nd().format(),
1955                |view| {
1956                    assert_eq!(view.shape(), [5]);
1957                    assert_eq!(view.format().to_str().unwrap(), "B");
1958                },
1959            )
1960            .unwrap();
1961
1962            PyUntypedBufferView::with_flags(
1963                &bytes,
1964                PyBufferFlags::simple().strides().format(),
1965                |view| {
1966                    assert_eq!(view.shape(), [5]);
1967                    assert_eq!(view.strides(), [1]);
1968                    assert_eq!(view.format().to_str().unwrap(), "B");
1969                },
1970            )
1971            .unwrap();
1972
1973            // Contiguity builders
1974            PyUntypedBufferView::with_flags(
1975                &bytes,
1976                PyBufferFlags::simple().c_contiguous(),
1977                |view| {
1978                    assert_eq!(view.shape(), [5]);
1979                    assert_eq!(view.strides(), [1]);
1980                },
1981            )
1982            .unwrap();
1983
1984            PyUntypedBufferView::with_flags(
1985                &bytes,
1986                PyBufferFlags::simple().f_contiguous(),
1987                |view| {
1988                    assert_eq!(view.shape(), [5]);
1989                    assert_eq!(view.strides(), [1]);
1990                },
1991            )
1992            .unwrap();
1993
1994            PyUntypedBufferView::with_flags(
1995                &bytes,
1996                PyBufferFlags::simple().any_contiguous(),
1997                |view| {
1998                    assert_eq!(view.shape(), [5]);
1999                    assert_eq!(view.strides(), [1]);
2000                },
2001            )
2002            .unwrap();
2003
2004            // Compound requests (read-only)
2005            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::full_ro(), |view| {
2006                assert_eq!(view.format().to_str().unwrap(), "B");
2007                assert_eq!(view.shape(), [5]);
2008                assert_eq!(view.strides(), [1]);
2009            })
2010            .unwrap();
2011
2012            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::records_ro(), |view| {
2013                assert_eq!(view.format().to_str().unwrap(), "B");
2014                assert_eq!(view.shape(), [5]);
2015                assert_eq!(view.strides(), [1]);
2016            })
2017            .unwrap();
2018
2019            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::strided_ro(), |view| {
2020                assert_eq!(view.shape(), [5]);
2021                assert_eq!(view.strides(), [1]);
2022            })
2023            .unwrap();
2024
2025            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::contig_ro(), |view| {
2026                assert_eq!(view.shape(), [5]);
2027                assert!(view.is_c_contiguous());
2028            })
2029            .unwrap();
2030
2031            // Writable compound requests
2032            PyUntypedBufferView::with_flags(&array, PyBufferFlags::full(), |view| {
2033                assert_eq!(view.format().to_str().unwrap(), "f");
2034                assert_eq!(view.shape(), [4]);
2035                assert_eq!(view.strides(), [4]);
2036                assert!(!view.readonly());
2037            })
2038            .unwrap();
2039
2040            PyUntypedBufferView::with_flags(&array, PyBufferFlags::records(), |view| {
2041                assert_eq!(view.format().to_str().unwrap(), "f");
2042                assert_eq!(view.shape(), [4]);
2043                assert!(!view.readonly());
2044            })
2045            .unwrap();
2046
2047            PyUntypedBufferView::with_flags(&array, PyBufferFlags::strided(), |view| {
2048                assert_eq!(view.shape(), [4]);
2049                assert_eq!(view.strides(), [4]);
2050                assert!(!view.readonly());
2051            })
2052            .unwrap();
2053
2054            PyUntypedBufferView::with_flags(&array, PyBufferFlags::contig(), |view| {
2055                assert_eq!(view.shape(), [4]);
2056                assert!(!view.readonly());
2057                assert!(view.is_c_contiguous());
2058            })
2059            .unwrap();
2060
2061            // Compound + contiguity
2062            PyUntypedBufferView::with_flags(
2063                &bytes,
2064                PyBufferFlags::full_ro().c_contiguous(),
2065                |view| {
2066                    assert_eq!(view.format().to_str().unwrap(), "B");
2067                    assert_eq!(view.shape(), [5]);
2068                    assert_eq!(view.strides(), [1]);
2069                },
2070            )
2071            .unwrap();
2072
2073            PyUntypedBufferView::with_flags(&array, PyBufferFlags::full().c_contiguous(), |view| {
2074                assert_eq!(view.format().to_str().unwrap(), "f");
2075                assert!(!view.readonly());
2076            })
2077            .unwrap();
2078
2079            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::strided_ro().format(), |view| {
2080                assert_eq!(view.format().to_str().unwrap(), "B");
2081                assert_eq!(view.shape(), [5]);
2082                assert_eq!(view.strides(), [1]);
2083            })
2084            .unwrap();
2085
2086            PyUntypedBufferView::with_flags(
2087                &bytes,
2088                PyBufferFlags::simple().c_contiguous().format(),
2089                |view| {
2090                    assert_eq!(view.format().to_str().unwrap(), "B");
2091                    assert_eq!(view.shape(), [5]);
2092                },
2093            )
2094            .unwrap();
2095
2096            // Contiguity builder on typed view
2097            PyBufferView::<u8>::with_flags(&bytes, PyBufferFlags::simple().format(), |view| {
2098                assert_eq!(view.format().to_str().unwrap(), "B");
2099                assert_eq!(view.item_count(), 5);
2100            })
2101            .unwrap();
2102
2103            PyBufferView::<f32>::with_flags(&array, PyBufferFlags::contig(), |view| {
2104                let slice = view.as_contiguous_slice(py);
2105                assert_eq!(slice[0].get(), 1.0);
2106            })
2107            .unwrap();
2108
2109            // Writable + contiguity on typed view
2110            PyBufferView::<f32>::with_flags(&array, PyBufferFlags::contig(), |view| {
2111                let slice = view.as_contiguous_slice(py);
2112                assert_eq!(slice[0].get(), 1.0);
2113                let mut_slice = view.as_contiguous_mut_slice(py);
2114                mut_slice[0].set(9.0);
2115                assert_eq!(slice[0].get(), 9.0);
2116            })
2117            .unwrap();
2118
2119            // SIMPLE format() returns "B"
2120            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::simple(), |view| {
2121                assert_eq!(view.format().to_str().unwrap(), "B");
2122            })
2123            .unwrap();
2124        });
2125    }
2126
2127    #[test]
2128    fn test_buffer_view_debug() {
2129        Python::attach(|py| {
2130            let bytes = PyBytes::new(py, b"abcde");
2131
2132            // Debug always uses raw_format/raw_shape/raw_strides (Option in output)
2133            PyUntypedBufferView::with_flags(&bytes, PyBufferFlags::full_ro(), |view| {
2134                let expected = format!(
2135                    concat!(
2136                        "PyUntypedBufferView {{ buf: {:?}, obj: {:?}, ",
2137                        "len: 5, itemsize: 1, readonly: 1, ",
2138                        "ndim: 1, format: Some(\"B\"), shape: Some([5]), ",
2139                        "strides: Some([1]), suboffsets: None, internal: {:?} }}",
2140                    ),
2141                    view.raw.buf, view.raw.obj, view.raw.internal,
2142                );
2143
2144                let debug_repr = format!("{:?}", view);
2145                assert_eq!(debug_repr, expected);
2146            })
2147            .unwrap();
2148
2149            PyBufferView::<u8>::with(&bytes, |view| {
2150                let expected = format!(
2151                    concat!(
2152                        "PyBufferView {{ buf: {:?}, obj: {:?}, ",
2153                        "len: 5, itemsize: 1, readonly: 1, ",
2154                        "ndim: 1, format: Some(\"B\"), shape: Some([5]), ",
2155                        "strides: Some([1]), suboffsets: None, internal: {:?} }}",
2156                    ),
2157                    view.0.raw.buf, view.0.raw.obj, view.0.raw.internal,
2158                );
2159
2160                let debug_repr = format!("{:?}", view);
2161                assert_eq!(debug_repr, expected);
2162            })
2163            .unwrap();
2164        });
2165    }
2166}