diff --git a/src/zarr/codecs/bytes.py b/src/zarr/codecs/bytes.py index 622161c458..a6d8767241 100644 --- a/src/zarr/codecs/bytes.py +++ b/src/zarr/codecs/bytes.py @@ -105,14 +105,7 @@ def _decode_sync( # configuration; the byte order of the decoded array is set by the array's # data type. The two are independent: the raw bytes are viewed with a dtype # in the stored byte order, then converted to the declared dtype if needed. - if isinstance(chunk_spec.dtype, HasEndianness): - view_dtype = replace(chunk_spec.dtype, endianness=endian_str).to_native_dtype() # type: ignore[call-arg] - elif isinstance(chunk_spec.dtype, Struct) and endian_str is not None: - # Per the struct data type spec, all multi-byte fields are stored in the - # byte order configured on this codec. - view_dtype = dtype.newbyteorder(endian_str) - else: - view_dtype = dtype + view_dtype = dtype.newbyteorder(endian_str) if endian_str is not None else dtype as_array_like = chunk_bytes.as_array_like() chunk_array = chunk_spec.prototype.nd_buffer.from_ndarray_like( as_array_like.view(dtype=view_dtype) # type: ignore[attr-defined] diff --git a/tests/test_codecs/test_bytes.py b/tests/test_codecs/test_bytes.py index ead778f526..746d18c9e7 100644 --- a/tests/test_codecs/test_bytes.py +++ b/tests/test_codecs/test_bytes.py @@ -38,10 +38,12 @@ [ ">u2", "f4"), ("mask", ">i4")], [("flux", "f4"), ("mask", " None: @pytest.mark.parametrize("endian", ENDIAN) @pytest.mark.parametrize( "native_dtype", - [np.dtype("float64"), np.dtype(">u2"), np.dtype([("a", ">f4"), ("b", "u2"), + np.dtype("i4"), + np.dtype("f2"), + np.dtype("f8"), + np.dtype([("a", ">f4"), ("b", " None: """ @@ -140,6 +162,72 @@ def test_bytes_codec_sync_roundtrip(endian: EndianLiteral, native_dtype: np.dtyp np.testing.assert_array_equal(arr, decoded.as_numpy_array()) +@pytest.mark.parametrize( + ("native_dtype", "endian", "needs_copy"), + [ + (np.dtype("i4"), "big", False), + (np.dtype("i2"), ("b", " None: + arr = np.zeros(4, dtype=native_dtype) + if native_dtype.fields is None: + arr[:] = np.arange(4) + else: + for offset, name in enumerate(native_dtype.names or ()): + arr[name] = np.arange(4) + offset + zdtype = get_data_type_from_native_dtype(native_dtype) + spec = ArraySpec( + shape=arr.shape, + dtype=zdtype, + fill_value=zdtype.cast_scalar(0), + config=ArrayConfig(order="C", write_empty_chunks=True), + prototype=default_buffer_prototype(), + ) + codec = BytesCodec(endian=endian).evolve_from_array_spec(spec) + encoded = codec._encode_sync(default_buffer_prototype().nd_buffer.from_numpy_array(arr), spec) + assert encoded is not None + stored_dtype = native_dtype.newbyteorder(endian) if endian is not None else native_dtype + assert encoded.to_bytes() == arr.astype(stored_dtype).tobytes() + + decoded = codec._decode_sync(encoded, spec) + assert type(decoded) is spec.prototype.nd_buffer + assert decoded.shape == arr.shape + assert decoded.dtype == zdtype.to_native_dtype() + np.testing.assert_array_equal(decoded.as_numpy_array(), arr) + assert np.shares_memory(decoded.as_numpy_array(), encoded.as_array_like()) is not needs_copy + + +@pytest.mark.parametrize("native_dtype", [np.dtype("i2"), np.dtype("f8")]) +def test_bytes_codec_missing_endian_rejects_multibyte_dtype(native_dtype: np.dtype[Any]) -> None: + zdtype = get_data_type_from_native_dtype(native_dtype) + spec = ArraySpec( + shape=(1,), + dtype=zdtype, + fill_value=zdtype.cast_scalar(0), + config=ArrayConfig(order="C", write_empty_chunks=True), + prototype=default_buffer_prototype(), + ) + with pytest.raises(ValueError, match="needs to be specified for multi-byte"): + BytesCodec(endian=None).evolve_from_array_spec(spec) + + @pytest.mark.parametrize("endian", ENDIAN) def test_bytes_codec_accepts_all_endians(endian: EndianLiteral) -> None: """