From df7a40cdef9a4c2d17e4abfc4fb906c01653a475 Mon Sep 17 00:00:00 2001 From: Fabian Isensee Date: Thu, 5 Nov 2026 16:19:37 +0100 Subject: [PATCH] Accept non-C-contiguous NumPy values in NDArray.__setitem__ `a[5:50, 16:76] = y[5:50, 16:76]` raised "ValueError: ndarray is not C-contiguous": set_slice() takes the value through the buffer protocol as one dense C-order buffer, and a NumPy view with gaps (a box of a larger array), with negative strides, with permuted axes, or a Fortran-ordered array cannot be handed over that way. Values of another dtype only worked when astype() happened to return a C-ordered copy (not for Fortran-ordered ones). Make the value C-contiguous before set_slice() (a no-op for C-contiguous arrays, a copy otherwise). The non-unit-step and fancy-index paths already copy. Co-Authored-By: Claude Opus 5.5 --- src/blosc2/ndarray.py | 3 +++ tests/ndarray/test_setitem.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/blosc2/ndarray.py b/src/blosc2/ndarray.py index 23bad401a..483bc1cc6 100644 --- a/src/blosc2/ndarray.py +++ b/src/blosc2/ndarray.py @@ -5140,6 +5140,9 @@ def __setitem__( # noqa: C901 # numexpr type inference can lead to unnecessary type promotions # when using complex functions (e.g. conj) with real arrays value = value.real.astype(self.dtype) + # set_slice() takes one C-contiguous buffer: copy views with gaps or negative strides and + # Fortran-ordered arrays (a no-op for C-contiguous ones) + value = np.ascontiguousarray(value) return self._mark_indexes_stale(super().set_slice((start, stop), value)) diff --git a/tests/ndarray/test_setitem.py b/tests/ndarray/test_setitem.py index 3f2a7cdb3..858c5b765 100644 --- a/tests/ndarray/test_setitem.py +++ b/tests/ndarray/test_setitem.py @@ -89,6 +89,37 @@ def test_setitem_different_dtype(shape, slices): np.testing.assert_almost_equal(a[slices], nparray_[slices]) +def non_contiguous_values(dtype): + """(shape of the target, key, value) per layout of a NumPy value that is not C-contiguous.""" + src = np.arange(20 * 30 * 40, dtype=dtype).reshape(20, 30, 40) + box = (slice(2, 9), slice(3, 17)) + return { + "strided_view": ((20, 30, 40), box, src[5:12, 10:24]), + "reversed_view": ((20, 30, 40), box, src[11:4:-1, 23:9:-1]), + "fortran": ((20, 30, 40), box, np.asfortranarray(src[5:12, 10:24])), + "permuted_axes": ((20, 30, 40), box, src.transpose(1, 0, 2)[3:10, 5:19]), + "1d_strided": ((100,), slice(10, 30), src.ravel()[:40:2]), + } + + +@pytest.mark.parametrize("dtype", [np.float32, np.int64], ids=["same_dtype", "other_dtype"]) +@pytest.mark.parametrize( + "layout", ["strided_view", "reversed_view", "fortran", "permuted_axes", "1d_strided"] +) +@pytest.mark.parametrize("on_disk", [False, True], ids=["memory", "disk"]) +def test_setitem_non_contiguous_value(tmp_path, on_disk, layout, dtype): + # set_slice() needs a C-contiguous buffer; these values raised "ndarray is not C-contiguous" + shape, key, value = non_contiguous_values(dtype)[layout] + assert not value.flags.c_contiguous + urlpath = str(tmp_path / "a.b2nd") if on_disk else None + chunks = (5, 10, 40) if len(shape) == 3 else (16,) + a = blosc2.zeros(shape, dtype=np.float32, chunks=chunks, urlpath=urlpath, mode="w") + expected = np.zeros(shape, dtype=np.float32) + a[key] = value + expected[key] = value + np.testing.assert_array_equal(a[:], expected) + + def test_ndfield(): # Create a structured NumPy array shape = (50, 50)