Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/4381.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Zarr format 2 arrays with a `<u1` or `>u1` data type can now be read as unsigned 8-bit integers. Serialized metadata continues to use the canonical `|u1` spelling.
6 changes: 5 additions & 1 deletion src/zarr/core/dtype/npy/int.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,11 @@ class UInt8(BaseInt[np.dtypes.UInt8DType, np.uint8]):

dtype_cls = np.dtypes.UInt8DType
_zarr_v3_name: ClassVar[Literal["uint8"]] = "uint8"
_zarr_v2_names: ClassVar[tuple[Literal["|u1"]]] = ("|u1",)
_zarr_v2_names: ClassVar[tuple[Literal["|u1"], Literal["<u1"], Literal[">u1"]]] = (
"|u1",
"<u1",
">u1",
)

@classmethod
def from_native_dtype(cls, dtype: TBaseDType) -> Self:
Expand Down
30 changes: 30 additions & 0 deletions tests/test_dtype/test_npy/test_int.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from __future__ import annotations

import numpy as np
import pytest

from tests.test_dtype.test_wrapper import BaseTestZDType
from zarr.core.dtype import get_data_type_from_json
from zarr.core.dtype.npy.int import Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64
from zarr.errors import DataTypeValidationError


class TestInt8(BaseTestZDType):
Expand Down Expand Up @@ -329,3 +332,30 @@ def test_string_integer_from_json_scalar() -> None:
# Test that it works for v2 format too
result = dtype_instance.from_json_scalar("123", zarr_format=2)
assert result == np.int32(123)


@pytest.mark.parametrize("name", ["|u1", "<u1", ">u1"])
def test_uint8_v2_aliases(name: str) -> None:
data = {"name": name, "object_codec_id": None}
dtype = UInt8.from_json(data, zarr_format=2)
assert dtype == UInt8()
assert get_data_type_from_json(data, zarr_format=2) == dtype
assert dtype.to_native_dtype() == np.dtype("uint8")
serialized = dtype.to_json(zarr_format=2)
assert serialized["name"] == "|u1"
assert serialized["object_codec_id"] is None
assert dtype.to_json(zarr_format=3) == "uint8"
with pytest.raises(DataTypeValidationError):
UInt8.from_json(name, zarr_format=3)


@pytest.mark.parametrize("name", ["|u1", "<u1", ">u1"])
def test_uint8_v2_aliases_reject_object_codec(name: str) -> None:
with pytest.raises(DataTypeValidationError):
UInt8.from_json({"name": name, "object_codec_id": "vlen-utf8"}, zarr_format=2)


@pytest.mark.parametrize("name", ["|i1", "<u2", ">u2", "uint8", "invalid"])
def test_uint8_v2_rejects_other_types(name: str) -> None:
with pytest.raises(DataTypeValidationError):
UInt8.from_json({"name": name, "object_codec_id": None}, zarr_format=2)
23 changes: 23 additions & 0 deletions tests/test_metadata/test_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,26 @@ def test_structured_dtype_fill_value_serialization(
root_group = zarr.open_group(group_path, mode="r")
observed = root_group.metadata.consolidated_metadata.metadata["structured_dtype"].fill_value # type: ignore[union-attr]
assert observed == fill_value


@pytest.mark.parametrize("dtype", ["|u1", "<u1", ">u1"])
def test_open_uint8_dtype_aliases(tmp_path: Path, dtype: str) -> None:
metadata = {
"zarr_format": 2,
"shape": [3],
"chunks": [3],
"dtype": dtype,
"compressor": None,
"fill_value": 0,
"order": "C",
"filters": None,
}
metadata_path = tmp_path / ".zarray"
metadata_path.write_text(json.dumps(metadata))
(tmp_path / "0").write_bytes(bytes([0, 128, 255]))

array = zarr.open_array(tmp_path, mode="r")
assert array.dtype == np.dtype("uint8")
np.testing.assert_array_equal(array[:], np.array([0, 128, 255], dtype=np.uint8))
assert array.metadata.to_dict()["dtype"] == "|u1"
assert json.loads(metadata_path.read_text()) == metadata
Loading