From 218e844efa0f5a503e4440fab94c966682b82988 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Mon, 12 Jan 2026 16:32:05 +0100 Subject: [PATCH 1/5] set copy=False in reshape operation --- src/zarr/codecs/vlen_utf8.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zarr/codecs/vlen_utf8.py b/src/zarr/codecs/vlen_utf8.py index d8e6072333..91d4da0ec0 100644 --- a/src/zarr/codecs/vlen_utf8.py +++ b/src/zarr/codecs/vlen_utf8.py @@ -50,7 +50,7 @@ async def _decode_single( raw_bytes = chunk_bytes.as_array_like() decoded = _vlen_utf8_codec.decode(raw_bytes) assert decoded.dtype == np.object_ - decoded = decoded.reshape(chunk_spec.shape) + decoded = decoded.reshape(chunk_spec.shape, copy=False) as_string_dtype = decoded.astype(chunk_spec.dtype.to_native_dtype(), copy=False) return chunk_spec.prototype.nd_buffer.from_numpy_array(as_string_dtype) @@ -95,7 +95,7 @@ async def _decode_single( raw_bytes = chunk_bytes.as_array_like() decoded = _vlen_bytes_codec.decode(raw_bytes) assert decoded.dtype == np.object_ - decoded = decoded.reshape(chunk_spec.shape) + decoded = decoded.reshape(chunk_spec.shape, copy=False) return chunk_spec.prototype.nd_buffer.from_numpy_array(decoded) async def _encode_single( From b9a1a734bbfd89b3292a31fed2dc7fa50c3acba3 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Mon, 12 Jan 2026 17:07:43 +0100 Subject: [PATCH 2/5] add compat reshape with conditional --- src/zarr/_compat.py | 40 +++++++++++++++++++++++++++++++++++- src/zarr/codecs/vlen_utf8.py | 5 +++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/zarr/_compat.py b/src/zarr/_compat.py index 87427b486e..4f88990fe0 100644 --- a/src/zarr/_compat.py +++ b/src/zarr/_compat.py @@ -2,10 +2,16 @@ from collections.abc import Callable from functools import wraps from inspect import Parameter, signature -from typing import Any, TypeVar +from typing import TYPE_CHECKING, Any, TypeVar + +import numpy as np +from packaging.version import Version from zarr.errors import ZarrFutureWarning +if TYPE_CHECKING: + from numpy.typing import NDArray + T = TypeVar("T") # Based off https://github.com/scikit-learn/scikit-learn/blob/e87b32a81c70abed8f2e97483758eb64df8255e9/sklearn/utils/validation.py#L63 @@ -68,3 +74,35 @@ def inner_f(*args: Any, **kwargs: Any) -> T: return _inner_deprecate_positional_args(func) return _inner_deprecate_positional_args # type: ignore[return-value] + + +def _reshape_view(arr: "NDArray[Any]", shape: tuple[int, ...]) -> "NDArray[Any]": + """Reshape an array without copying data. + + This function provides compatibility across NumPy versions for reshaping arrays + as views. On NumPy >= 2.1, it uses ``reshape(copy=False)`` which explicitly + fails if a view cannot be created. On older versions, it uses direct shape + assignment which has the same behavior but is deprecated in 2.5+. + + Parameters + ---------- + arr : NDArray + The array to reshape. + shape : tuple of int + The new shape. + + Returns + ------- + NDArray + A reshaped view of the array. + + Raises + ------ + AttributeError + If a view cannot be created (the array is not contiguous). + """ + if Version(np.__version__) >= Version("2.1"): + return arr.reshape(shape, copy=False) + else: + arr.shape = shape + return arr diff --git a/src/zarr/codecs/vlen_utf8.py b/src/zarr/codecs/vlen_utf8.py index 91d4da0ec0..fb1fb76126 100644 --- a/src/zarr/codecs/vlen_utf8.py +++ b/src/zarr/codecs/vlen_utf8.py @@ -6,6 +6,7 @@ import numpy as np from numcodecs.vlen import VLenBytes, VLenUTF8 +from zarr._compat import _reshape_view from zarr.abc.codec import ArrayBytesCodec from zarr.core.buffer import Buffer, NDBuffer from zarr.core.common import JSON, parse_named_configuration @@ -50,7 +51,7 @@ async def _decode_single( raw_bytes = chunk_bytes.as_array_like() decoded = _vlen_utf8_codec.decode(raw_bytes) assert decoded.dtype == np.object_ - decoded = decoded.reshape(chunk_spec.shape, copy=False) + decoded = _reshape_view(decoded, chunk_spec.shape) as_string_dtype = decoded.astype(chunk_spec.dtype.to_native_dtype(), copy=False) return chunk_spec.prototype.nd_buffer.from_numpy_array(as_string_dtype) @@ -95,7 +96,7 @@ async def _decode_single( raw_bytes = chunk_bytes.as_array_like() decoded = _vlen_bytes_codec.decode(raw_bytes) assert decoded.dtype == np.object_ - decoded = decoded.reshape(chunk_spec.shape, copy=False) + decoded = _reshape_view(decoded, chunk_spec.shape) return chunk_spec.prototype.nd_buffer.from_numpy_array(decoded) async def _encode_single( From 171ad85bc05ae232d8d2333af68edca556bd5be3 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Mon, 12 Jan 2026 17:11:49 +0100 Subject: [PATCH 3/5] fix docstring --- src/zarr/_compat.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/zarr/_compat.py b/src/zarr/_compat.py index 4f88990fe0..ddb0fc7c87 100644 --- a/src/zarr/_compat.py +++ b/src/zarr/_compat.py @@ -99,7 +99,9 @@ def _reshape_view(arr: "NDArray[Any]", shape: tuple[int, ...]) -> "NDArray[Any]" Raises ------ AttributeError - If a view cannot be created (the array is not contiguous). + If a view cannot be created (the array is not contiguous) on NumPy < 2.1. + ValueError + If a view cannot be created (the array is not contiguous) on NumPy >= 2.1. """ if Version(np.__version__) >= Version("2.1"): return arr.reshape(shape, copy=False) From d621defe5b2b3364536841d812c6525a4c24e9be Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Mon, 12 Jan 2026 17:15:48 +0100 Subject: [PATCH 4/5] fix mypy --- src/zarr/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zarr/_compat.py b/src/zarr/_compat.py index ddb0fc7c87..4dd7a387d8 100644 --- a/src/zarr/_compat.py +++ b/src/zarr/_compat.py @@ -104,7 +104,7 @@ def _reshape_view(arr: "NDArray[Any]", shape: tuple[int, ...]) -> "NDArray[Any]" If a view cannot be created (the array is not contiguous) on NumPy >= 2.1. """ if Version(np.__version__) >= Version("2.1"): - return arr.reshape(shape, copy=False) + return arr.reshape(*shape, copy=False) # type: ignore[call-overload, no-any-return] else: arr.shape = shape return arr From afbb4729dbb346cab2b00f046b93f0de0b6ff9f4 Mon Sep 17 00:00:00 2001 From: Mathieu Scheltienne Date: Mon, 12 Jan 2026 17:40:07 +0100 Subject: [PATCH 5/5] remove tuple unpacking which makes it more readable --- src/zarr/_compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zarr/_compat.py b/src/zarr/_compat.py index 4dd7a387d8..ae973d6292 100644 --- a/src/zarr/_compat.py +++ b/src/zarr/_compat.py @@ -104,7 +104,7 @@ def _reshape_view(arr: "NDArray[Any]", shape: tuple[int, ...]) -> "NDArray[Any]" If a view cannot be created (the array is not contiguous) on NumPy >= 2.1. """ if Version(np.__version__) >= Version("2.1"): - return arr.reshape(*shape, copy=False) # type: ignore[call-overload, no-any-return] + return arr.reshape(shape, copy=False) # type: ignore[call-overload, no-any-return] else: arr.shape = shape return arr