Skip to content

Commit 5571b45

Browse files
committed
fix: report dtype/ndim error when extracting PyReadonlyArray/PyReadwriteArray
1 parent 4ef0102 commit 5571b45

3 files changed

Lines changed: 56 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
- Unreleased
4+
- Fix `PyReadonlyArray`/`PyReadwriteArray` extraction to report the actual dtype/dimensionality mismatch instead of a generic “not an instance of” error. ([#561](https://github.com/PyO3/rust-numpy/issues/561))
45

56
- v0.29.0
67
- Fix PyArray_DTypeMeta definition when Py_LIMITED_API is disabled ([#532](https://github.com/PyO3/rust-numpy/pull/532))

src/array.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use num_traits::AsPrimitive;
1717
use pyo3::{
1818
ffi,
1919
types::{DerefToPyAny, PyModule},
20-
Bound, CastError, Py, PyAny, PyErr, PyResult, PyTypeCheck, PyTypeInfo, Python,
20+
Borrowed, Bound, CastError, Py, PyAny, PyErr, PyResult, PyTypeCheck, PyTypeInfo, Python,
2121
};
2222

2323
use crate::borrow::{PyReadonlyArray, PyReadwriteArray};
@@ -132,27 +132,27 @@ unsafe impl<T: Element, D: Dimension> PyTypeInfo for PyArray<T, D> {
132132
}
133133

134134
fn is_type_of(ob: &Bound<'_, PyAny>) -> bool {
135-
Self::extract::<IgnoreError>(ob, npyffi::PyArray_Check).is_ok()
135+
Self::extract::<IgnoreError>(ob.as_borrowed(), npyffi::PyArray_Check).is_ok()
136136
}
137137

138138
fn is_exact_type_of(ob: &Bound<'_, PyAny>) -> bool {
139-
Self::extract::<IgnoreError>(ob, npyffi::PyArray_CheckExact).is_ok()
139+
Self::extract::<IgnoreError>(ob.as_borrowed(), npyffi::PyArray_CheckExact).is_ok()
140140
}
141141
}
142142

143143
impl<T: Element, D: Dimension> PyArray<T, D> {
144-
fn extract<'a, 'py, E>(
145-
ob: &'a Bound<'py, PyAny>,
144+
pub(crate) fn extract<'a, 'py, E>(
145+
ob: Borrowed<'a, 'py, PyAny>,
146146
check: unsafe fn(Python<'py>, *mut ffi::PyObject) -> c_int,
147-
) -> Result<&'a Bound<'py, Self>, E>
147+
) -> Result<Borrowed<'a, 'py, Self>, E>
148148
where
149149
E: From<CastError<'a, 'py>> + From<DimensionalityError> + From<TypeError<'py>>,
150150
{
151151
// Check if the object is an array.
152152
let array = unsafe {
153153
if check(ob.py(), ob.as_ptr()) == 0 {
154154
return Err(CastError::new(
155-
ob.as_borrowed(),
155+
ob,
156156
<Self as PyTypeCheck>::classinfo_object(ob.py()),
157157
)
158158
.into());

src/borrow/mod.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,13 @@ use std::ops::Deref;
175175
use ndarray::{
176176
ArrayView, ArrayViewMut, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn,
177177
};
178-
use pyo3::{Borrowed, Bound, CastError, FromPyObject, PyAny, PyResult};
178+
use pyo3::{Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult};
179179

180180
use crate::array::{PyArray, PyArrayMethods};
181181
use crate::convert::NpyIndex;
182182
use crate::dtype::Element;
183183
use crate::error::{AsSliceError, BorrowError};
184+
use crate::npyffi;
184185
use crate::npyffi::flags;
185186
use crate::untyped_array::PyUntypedArrayMethods;
186187

@@ -240,10 +241,10 @@ where
240241
impl<'a, 'py, T: Element + 'a, D: Dimension + 'a> FromPyObject<'a, 'py>
241242
for PyReadonlyArray<'py, T, D>
242243
{
243-
type Error = CastError<'a, 'py>;
244+
type Error = PyErr;
244245

245246
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
246-
let array = obj.cast::<PyArray<T, D>>()?;
247+
let array = PyArray::<T, D>::extract::<PyErr>(obj, npyffi::PyArray_Check)?;
247248
Ok(array.readonly())
248249
}
249250
}
@@ -483,10 +484,10 @@ where
483484
impl<'a, 'py, T: Element + 'a, D: Dimension + 'a> FromPyObject<'a, 'py>
484485
for PyReadwriteArray<'py, T, D>
485486
{
486-
type Error = CastError<'a, 'py>;
487+
type Error = PyErr;
487488

488489
fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
489-
let array = obj.cast::<PyArray<T, D>>()?;
490+
let array = PyArray::<T, D>::extract::<PyErr>(obj, npyffi::PyArray_Check)?;
490491
Ok(array.readwrite())
491492
}
492493
}
@@ -675,7 +676,10 @@ where
675676
mod tests {
676677
use super::*;
677678

678-
use pyo3::{types::IntoPyDict, Python};
679+
use pyo3::{
680+
types::{IntoPyDict, PyAnyMethods},
681+
Python,
682+
};
679683

680684
use crate::array::PyArray1;
681685
use pyo3::ffi::c_str;
@@ -743,4 +747,42 @@ mod tests {
743747
assert!(exclusive.resize(10).is_ok());
744748
});
745749
}
750+
751+
#[test]
752+
fn extraction_reports_dtype_mismatch() {
753+
Python::attach(|py| {
754+
let array = PyArray::<f64, _>::zeros(py, (2, 2), false);
755+
let any = array.as_any();
756+
757+
let err = any.extract::<PyReadonlyArray2<'_, f32>>().unwrap_err();
758+
let msg = err.to_string();
759+
assert!(msg.contains("float64"), "message was: {msg}");
760+
assert!(msg.contains("float32"), "message was: {msg}");
761+
762+
let err = any.extract::<PyReadwriteArray2<'_, f32>>().unwrap_err();
763+
let msg = err.to_string();
764+
assert!(msg.contains("float64"), "message was: {msg}");
765+
assert!(msg.contains("float32"), "message was: {msg}");
766+
});
767+
}
768+
769+
#[test]
770+
fn extraction_reports_dimensionality_mismatch() {
771+
Python::attach(|py| {
772+
let array = PyArray::<f64, _>::zeros(py, (2, 2), false);
773+
let any = array.as_any();
774+
775+
let err = any.extract::<PyReadonlyArray3<'_, f64>>().unwrap_err();
776+
let msg = err.to_string();
777+
assert!(msg.contains("dimensionality"), "message was: {msg}");
778+
assert!(msg.contains('2'), "message was: {msg}");
779+
assert!(msg.contains('3'), "message was: {msg}");
780+
781+
let err = any.extract::<PyReadwriteArray3<'_, f64>>().unwrap_err();
782+
let msg = err.to_string();
783+
assert!(msg.contains("dimensionality"), "message was: {msg}");
784+
assert!(msg.contains('2'), "message was: {msg}");
785+
assert!(msg.contains('3'), "message was: {msg}");
786+
});
787+
}
746788
}

0 commit comments

Comments
 (0)