From 3d1a15e584fe7a220f7d585a95b6d55a9fbea999 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Mon, 7 Nov 2022 17:18:54 +0100 Subject: [PATCH] API: Always use BufferError when dlpack export fails See also https://github.com/data-apis/array-api/pull/498. I think we should just change this. It is a niche feature and just an error type change. Closes gh-20742 --- .../upcoming_changes/22542.compatibility.rst | 7 +++++ numpy/core/src/multiarray/dlpack.c | 31 ++++++++++--------- numpy/core/tests/test_dlpack.py | 10 +++--- 3 files changed, 29 insertions(+), 19 deletions(-) create mode 100644 doc/release/upcoming_changes/22542.compatibility.rst diff --git a/doc/release/upcoming_changes/22542.compatibility.rst b/doc/release/upcoming_changes/22542.compatibility.rst new file mode 100644 index 000000000000..047e58a1958b --- /dev/null +++ b/doc/release/upcoming_changes/22542.compatibility.rst @@ -0,0 +1,7 @@ +DLPack export raises ``BufferError`` +------------------------------------ +When an array buffer cannot be exported via DLPack a +``BufferError`` is now always raised where previously +``TypeError`` or ``RuntimeError`` was raises. +This allows falling back to the buffer protocol or +``__array_interface__`` when DLPack was tried first. diff --git a/numpy/core/src/multiarray/dlpack.c b/numpy/core/src/multiarray/dlpack.c index 5a46d5a859d6..b20674f5eadc 100644 --- a/numpy/core/src/multiarray/dlpack.c +++ b/numpy/core/src/multiarray/dlpack.c @@ -133,14 +133,15 @@ array_dlpack(PyArrayObject *self, } if (stream != Py_None) { - PyErr_SetString(PyExc_RuntimeError, "NumPy only supports " - "stream=None."); + PyErr_SetString(PyExc_RuntimeError, + "NumPy only supports stream=None."); return NULL; } if ( !(PyArray_FLAGS(self) & NPY_ARRAY_WRITEABLE)) { - PyErr_SetString(PyExc_TypeError, "NumPy currently only supports " - "dlpack for writeable arrays"); + PyErr_SetString(PyExc_BufferError, + "Cannot export readonly array since signalling readonly " + "is unsupported by DLPack."); return NULL; } @@ -152,7 +153,7 @@ array_dlpack(PyArrayObject *self, if (!PyArray_IS_C_CONTIGUOUS(self) && PyArray_SIZE(self) != 1) { for (int i = 0; i < ndim; ++i) { if (shape[i] != 1 && strides[i] % itemsize != 0) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_BufferError, "DLPack only supports strides which are a multiple of " "itemsize."); return NULL; @@ -164,8 +165,8 @@ array_dlpack(PyArrayObject *self, PyArray_Descr *dtype = PyArray_DESCR(self); if (PyDataType_ISBYTESWAPPED(dtype)) { - PyErr_SetString(PyExc_TypeError, "DLPack only supports native " - "byte swapping."); + PyErr_SetString(PyExc_BufferError, + "DLPack only supports native byte order."); return NULL; } @@ -182,8 +183,9 @@ array_dlpack(PyArrayObject *self, // We can't be sure that the dtype is // IEEE or padded. if (itemsize > 8) { - PyErr_SetString(PyExc_TypeError, "DLPack only supports IEEE " - "floating point types without padding."); + PyErr_SetString(PyExc_BufferError, + "DLPack only supports IEEE floating point types " + "without padding (longdouble typically is not IEEE)."); return NULL; } managed_dtype.code = kDLFloat; @@ -192,16 +194,17 @@ array_dlpack(PyArrayObject *self, // We can't be sure that the dtype is // IEEE or padded. if (itemsize > 16) { - PyErr_SetString(PyExc_TypeError, "DLPack only supports IEEE " - "complex point types without padding."); + PyErr_SetString(PyExc_BufferError, + "DLPack only supports IEEE floating point types " + "without padding (longdouble typically is not IEEE)."); return NULL; } managed_dtype.code = kDLComplex; } else { - PyErr_SetString(PyExc_TypeError, - "DLPack only supports signed/unsigned integers, float " - "and complex dtypes."); + PyErr_SetString(PyExc_BufferError, + "DLPack only supports signed/unsigned integers, float " + "and complex dtypes."); return NULL; } diff --git a/numpy/core/tests/test_dlpack.py b/numpy/core/tests/test_dlpack.py index 717210b5423a..278bdd12de53 100644 --- a/numpy/core/tests/test_dlpack.py +++ b/numpy/core/tests/test_dlpack.py @@ -26,7 +26,7 @@ def test_strides_not_multiple_of_itemsize(self): y = np.zeros((5,), dtype=dt) z = y['int'] - with pytest.raises(RuntimeError): + with pytest.raises(BufferError): np.from_dlpack(z) @pytest.mark.skipif(IS_PYPY, reason="PyPy can't get refcounts.") @@ -53,14 +53,14 @@ def test_dtype_passthrough(self, dtype): def test_invalid_dtype(self): x = np.asarray(np.datetime64('2021-05-27')) - with pytest.raises(TypeError): + with pytest.raises(BufferError): np.from_dlpack(x) def test_invalid_byte_swapping(self): dt = np.dtype('=i8').newbyteorder() x = np.arange(5, dtype=dt) - with pytest.raises(TypeError): + with pytest.raises(BufferError): np.from_dlpack(x) def test_non_contiguous(self): @@ -100,7 +100,7 @@ def dlpack_deleter_exception(self): x = np.arange(5) _ = x.__dlpack__() raise RuntimeError - + def test_dlpack_destructor_exception(self): with pytest.raises(RuntimeError): self.dlpack_deleter_exception() @@ -108,7 +108,7 @@ def test_dlpack_destructor_exception(self): def test_readonly(self): x = np.arange(5) x.flags.writeable = False - with pytest.raises(TypeError): + with pytest.raises(BufferError): x.__dlpack__() def test_ndim0(self):