Skip to content

Commit

Permalink
Add safe interface to clear WRITEABLE flag
Browse files Browse the repository at this point in the history
The dynamic borrow checker for `PyReadwriteArray` understood the flag,
but the crate offered no public way to set it.  General manipulation of
the `flags` field is unsafe (things like `OWNDATA` should never be
modified by hand), but flipping the `WRITEABLE` flag to `false` is safe.
  • Loading branch information
jakelishman authored and davidhewitt committed Oct 27, 2024
1 parent 6cd2a84 commit a4b922f
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 1 deletion.
3 changes: 3 additions & 0 deletions src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,9 @@ impl<T: Element, D: Dimension> PyArray<T, D> {

/// Creates a NumPy array backed by `array` and ties its ownership to the Python object `container`.
///
/// The resulting NumPy array will be writeable from Python space. If this is undesireable, use
/// [PyReadwriteArray::make_nonwriteable].
///
/// # Safety
///
/// `container` is set as a base object of the returned array which must not be dropped until `container` is dropped.
Expand Down
19 changes: 18 additions & 1 deletion src/borrow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//! ```rust
//! # use std::panic::{catch_unwind, AssertUnwindSafe};
//! #
//! use numpy::{PyArray1, PyArrayMethods};
//! use numpy::{PyArray1, PyArrayMethods, npyffi::flags};
//! use ndarray::Zip;
//! use pyo3::{Python, Bound};
//!
Expand Down Expand Up @@ -176,6 +176,7 @@ use crate::convert::NpyIndex;
use crate::dtype::Element;
use crate::error::{BorrowError, NotContiguousError};
use crate::untyped_array::PyUntypedArrayMethods;
use crate::npyffi::flags;

use shared::{acquire, acquire_mut, release, release_mut};

Expand Down Expand Up @@ -494,6 +495,22 @@ where
{
unsafe { self.array.get_mut(index) }
}

/// Clear the [`WRITEABLE` flag][writeable] from the underlying NumPy array.
///
/// Calling this will prevent any further [PyReadwriteArray]s from being taken out. Python
/// space can reset this flag, unless the additional flag [`OWNDATA`][owndata] is unset. Such
/// an array can be created from Rust space by using [PyArray::borrow_from_array_bound].
///
/// [writeable]: https://numpy.org/doc/stable/reference/c-api/array.html#c.NPY_ARRAY_WRITEABLE
/// [owndata]: https://numpy.org/doc/stable/reference/c-api/array.html#c.NPY_ARRAY_OWNDATA
pub fn make_nonwriteable(self) {
// SAFETY: consuming the only extant mutable reference guarantees we cannot invalidate an
// existing reference, nor allow the caller to keep hold of one.
unsafe {
(*self.as_array_ptr()).flags &= !flags::NPY_ARRAY_WRITEABLE;
}
}
}

#[cfg(feature = "nalgebra")]
Expand Down
14 changes: 14 additions & 0 deletions tests/borrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,20 @@ fn resize_using_exclusive_borrow() {
});
}

#[test]
fn can_make_python_array_nonwriteable() {
Python::with_gil(|py| {
let array = PyArray1::<f64>::zeros_bound(py, 10, false);
let locals = [("array", &array)].into_py_dict_bound(py);
array.readwrite().make_nonwriteable();
assert!(!py
.eval_bound("array.flags.writeable", None, Some(&locals))
.unwrap()
.extract::<bool>()
.unwrap())
})
}

#[cfg(feature = "nalgebra")]
#[test]
fn matrix_from_numpy() {
Expand Down

0 comments on commit a4b922f

Please sign in to comment.