Skip to content

Commit a4b922f

Browse files
jakelishmandavidhewitt
authored andcommitted
Add safe interface to clear WRITEABLE flag
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.
1 parent 6cd2a84 commit a4b922f

File tree

3 files changed

+35
-1
lines changed

3 files changed

+35
-1
lines changed

src/array.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ impl<T: Element, D: Dimension> PyArray<T, D> {
420420

421421
/// Creates a NumPy array backed by `array` and ties its ownership to the Python object `container`.
422422
///
423+
/// The resulting NumPy array will be writeable from Python space. If this is undesireable, use
424+
/// [PyReadwriteArray::make_nonwriteable].
425+
///
423426
/// # Safety
424427
///
425428
/// `container` is set as a base object of the returned array which must not be dropped until `container` is dropped.

src/borrow/mod.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
//! ```rust
1616
//! # use std::panic::{catch_unwind, AssertUnwindSafe};
1717
//! #
18-
//! use numpy::{PyArray1, PyArrayMethods};
18+
//! use numpy::{PyArray1, PyArrayMethods, npyffi::flags};
1919
//! use ndarray::Zip;
2020
//! use pyo3::{Python, Bound};
2121
//!
@@ -176,6 +176,7 @@ use crate::convert::NpyIndex;
176176
use crate::dtype::Element;
177177
use crate::error::{BorrowError, NotContiguousError};
178178
use crate::untyped_array::PyUntypedArrayMethods;
179+
use crate::npyffi::flags;
179180

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

@@ -494,6 +495,22 @@ where
494495
{
495496
unsafe { self.array.get_mut(index) }
496497
}
498+
499+
/// Clear the [`WRITEABLE` flag][writeable] from the underlying NumPy array.
500+
///
501+
/// Calling this will prevent any further [PyReadwriteArray]s from being taken out. Python
502+
/// space can reset this flag, unless the additional flag [`OWNDATA`][owndata] is unset. Such
503+
/// an array can be created from Rust space by using [PyArray::borrow_from_array_bound].
504+
///
505+
/// [writeable]: https://numpy.org/doc/stable/reference/c-api/array.html#c.NPY_ARRAY_WRITEABLE
506+
/// [owndata]: https://numpy.org/doc/stable/reference/c-api/array.html#c.NPY_ARRAY_OWNDATA
507+
pub fn make_nonwriteable(self) {
508+
// SAFETY: consuming the only extant mutable reference guarantees we cannot invalidate an
509+
// existing reference, nor allow the caller to keep hold of one.
510+
unsafe {
511+
(*self.as_array_ptr()).flags &= !flags::NPY_ARRAY_WRITEABLE;
512+
}
513+
}
497514
}
498515

499516
#[cfg(feature = "nalgebra")]

tests/borrow.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,20 @@ fn resize_using_exclusive_borrow() {
348348
});
349349
}
350350

351+
#[test]
352+
fn can_make_python_array_nonwriteable() {
353+
Python::with_gil(|py| {
354+
let array = PyArray1::<f64>::zeros_bound(py, 10, false);
355+
let locals = [("array", &array)].into_py_dict_bound(py);
356+
array.readwrite().make_nonwriteable();
357+
assert!(!py
358+
.eval_bound("array.flags.writeable", None, Some(&locals))
359+
.unwrap()
360+
.extract::<bool>()
361+
.unwrap())
362+
})
363+
}
364+
351365
#[cfg(feature = "nalgebra")]
352366
#[test]
353367
fn matrix_from_numpy() {

0 commit comments

Comments
 (0)