diff --git a/CHANGELOG.md b/CHANGELOG.md index 452660610..e70d59904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog - Unreleased + - `PyReadonlyArray`/`PyReadwriteArray` extraction now returns a `PyErr` which reports the actual dtype/dimensionality mismatch instead of nonsensical `CastError` messages like “'ndarray' is not an instance of 'ndarray'” ([#562](https://github.com/PyO3/rust-numpy/pull/562)) - v0.29.0 - Fix PyArray_DTypeMeta definition when Py_LIMITED_API is disabled ([#532](https://github.com/PyO3/rust-numpy/pull/532)) diff --git a/src/array.rs b/src/array.rs index 861bb6c8c..3b30bb2ca 100644 --- a/src/array.rs +++ b/src/array.rs @@ -17,7 +17,7 @@ use num_traits::AsPrimitive; use pyo3::{ ffi, types::{DerefToPyAny, PyModule}, - Bound, CastError, Py, PyAny, PyErr, PyResult, PyTypeCheck, PyTypeInfo, Python, + Borrowed, Bound, CastError, Py, PyAny, PyErr, PyResult, PyTypeCheck, PyTypeInfo, Python, }; use crate::borrow::{PyReadonlyArray, PyReadwriteArray}; @@ -132,30 +132,28 @@ unsafe impl PyTypeInfo for PyArray { } fn is_type_of(ob: &Bound<'_, PyAny>) -> bool { - Self::extract::(ob, npyffi::PyArray_Check).is_ok() + Self::extract::(ob.as_borrowed(), npyffi::PyArray_Check).is_ok() } fn is_exact_type_of(ob: &Bound<'_, PyAny>) -> bool { - Self::extract::(ob, npyffi::PyArray_CheckExact).is_ok() + Self::extract::(ob.as_borrowed(), npyffi::PyArray_CheckExact).is_ok() } } impl PyArray { - fn extract<'a, 'py, E>( - ob: &'a Bound<'py, PyAny>, + pub(crate) fn extract<'a, 'py, E>( + ob: Borrowed<'a, 'py, PyAny>, check: unsafe fn(Python<'py>, *mut ffi::PyObject) -> c_int, - ) -> Result<&'a Bound<'py, Self>, E> + ) -> Result, E> where E: From> + From + From>, { // Check if the object is an array. let array = unsafe { if check(ob.py(), ob.as_ptr()) == 0 { - return Err(CastError::new( - ob.as_borrowed(), - ::classinfo_object(ob.py()), - ) - .into()); + return Err( + CastError::new(ob, ::classinfo_object(ob.py())).into(), + ); } ob.cast_unchecked::() }; diff --git a/src/borrow/mod.rs b/src/borrow/mod.rs index 7ffd4faa6..40f38ee50 100644 --- a/src/borrow/mod.rs +++ b/src/borrow/mod.rs @@ -175,12 +175,13 @@ use std::ops::Deref; use ndarray::{ ArrayView, ArrayViewMut, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn, }; -use pyo3::{Borrowed, Bound, CastError, FromPyObject, PyAny, PyResult}; +use pyo3::{Borrowed, Bound, FromPyObject, PyAny, PyErr, PyResult}; use crate::array::{PyArray, PyArrayMethods}; use crate::convert::NpyIndex; use crate::dtype::Element; use crate::error::{AsSliceError, BorrowError}; +use crate::npyffi; use crate::npyffi::flags; use crate::untyped_array::PyUntypedArrayMethods; @@ -240,10 +241,10 @@ where impl<'a, 'py, T: Element + 'a, D: Dimension + 'a> FromPyObject<'a, 'py> for PyReadonlyArray<'py, T, D> { - type Error = CastError<'a, 'py>; + type Error = PyErr; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { - let array = obj.cast::>()?; + let array = PyArray::::extract::(obj, npyffi::PyArray_Check)?; Ok(array.readonly()) } } @@ -483,10 +484,10 @@ where impl<'a, 'py, T: Element + 'a, D: Dimension + 'a> FromPyObject<'a, 'py> for PyReadwriteArray<'py, T, D> { - type Error = CastError<'a, 'py>; + type Error = PyErr; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { - let array = obj.cast::>()?; + let array = PyArray::::extract::(obj, npyffi::PyArray_Check)?; Ok(array.readwrite()) } } @@ -675,7 +676,10 @@ where mod tests { use super::*; - use pyo3::{types::IntoPyDict, Python}; + use pyo3::{ + types::{IntoPyDict, PyAnyMethods}, + Python, + }; use crate::array::PyArray1; use pyo3::ffi::c_str; @@ -743,4 +747,36 @@ mod tests { assert!(exclusive.resize(10).is_ok()); }); } + + #[test] + fn extraction_reports_dtype_mismatch() { + Python::attach(|py| { + let array = PyArray::::zeros(py, (2, 2), false); + let any = array.as_any(); + + let err = any.extract::>().unwrap_err(); + let msg = err.to_string(); + assert_eq!(msg, "TypeError: type mismatch:\n from=float64, to=float32"); + + let err = any.extract::>().unwrap_err(); + let msg = err.to_string(); + assert_eq!(msg, "TypeError: type mismatch:\n from=float64, to=float32"); + }); + } + + #[test] + fn extraction_reports_dimensionality_mismatch() { + Python::attach(|py| { + let array = PyArray::::zeros(py, (2, 2), false); + let any = array.as_any(); + + let err = any.extract::>().unwrap_err(); + let msg = err.to_string(); + assert_eq!(msg, "TypeError: dimensionality mismatch:\n from=2, to=3"); + + let err = any.extract::>().unwrap_err(); + let msg = err.to_string(); + assert_eq!(msg, "TypeError: dimensionality mismatch:\n from=2, to=3"); + }); + } } diff --git a/src/strings.rs b/src/strings.rs index caa3d0d68..65764d2b6 100644 --- a/src/strings.rs +++ b/src/strings.rs @@ -212,7 +212,7 @@ mod tests { "foo" ); assert_eq!( - PyFixedString([b'f', b'o', b'o', b'b', b'a', b'r']).to_string(), + PyFixedString(*b"foobar").to_string(), "foobar" ); }