From aabb3d7ef961f726ab62b5cb89b6b995714ec3c6 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Sat, 30 Aug 2025 21:55:43 +0200 Subject: [PATCH 1/9] rename test_endian.py to test_bytes.py --- tests/test_codecs/{test_endian.py => test_bytes.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/test_codecs/{test_endian.py => test_bytes.py} (100%) diff --git a/tests/test_codecs/test_endian.py b/tests/test_codecs/test_bytes.py similarity index 100% rename from tests/test_codecs/test_endian.py rename to tests/test_codecs/test_bytes.py From bde404ed8f6da24695fc204207cab70f755e85da Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Sat, 30 Aug 2025 22:01:52 +0200 Subject: [PATCH 2/9] tests for `BytesCodec.to_dict` --- tests/test_codecs/test_bytes.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/test_codecs/test_bytes.py b/tests/test_codecs/test_bytes.py index ab64afb1b8..0a96c53ae3 100644 --- a/tests/test_codecs/test_bytes.py +++ b/tests/test_codecs/test_bytes.py @@ -1,4 +1,4 @@ -from typing import Literal +from typing import Any, Literal import numpy as np import pytest @@ -58,3 +58,21 @@ async def test_endian_write( await _AsyncArrayProxy(a)[:, :].set(data) readback_data = await _AsyncArrayProxy(a)[:, :].get() assert np.array_equal(data, readback_data) + + +@pytest.mark.parametrize( + ("endian", "expected"), + [ + pytest.param( + "little", {"name": "bytes", "configuration": {"endian": "little"}}, id="little" + ), + pytest.param("big", {"name": "bytes", "configuration": {"endian": "big"}}, id="big"), + pytest.param(None, {"name": "bytes"}, id="missing"), + ], +) +def test_to_dict(endian: str, expected: dict[str, Any]) -> None: + codec = BytesCodec(endian=endian) + + actual = codec.to_dict() + + assert actual == expected From cfad862405bb76e11defc1f6ef95ff3ab01bf4ed Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Sat, 30 Aug 2025 22:07:57 +0200 Subject: [PATCH 3/9] tests for `from_dict` and roundtripping --- tests/test_codecs/test_bytes.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/test_codecs/test_bytes.py b/tests/test_codecs/test_bytes.py index 0a96c53ae3..fd36cb4a29 100644 --- a/tests/test_codecs/test_bytes.py +++ b/tests/test_codecs/test_bytes.py @@ -5,7 +5,7 @@ import zarr from zarr.abc.store import Store -from zarr.codecs import BytesCodec +from zarr.codecs import BytesCodec, Endian from zarr.storage import StorePath from .test_codecs import _AsyncArrayProxy @@ -76,3 +76,29 @@ def test_to_dict(endian: str, expected: dict[str, Any]) -> None: actual = codec.to_dict() assert actual == expected + + +@pytest.mark.parametrize( + ("mapping", "expected"), + [ + pytest.param( + {"name": "bytes", "configuration": {"endian": "little"}}, Endian.little, id="little" + ), + pytest.param({"name": "bytes", "configuration": {"endian": "big"}}, Endian.big, id="big"), + pytest.param({"name": "bytes"}, None, id="missing"), + ], +) +def test_from_dict(mapping: dict[str, Any], expected: Endian | None) -> None: + actual = BytesCodec.from_dict(mapping) + + assert actual.endian == expected + + +@pytest.mark.parametrize("endian", ["little", "big", pytest.param(None, id="missing")]) +def test_roundtrip(endian: str | None) -> None: + codec = BytesCodec(endian=endian) + + encoded = codec.to_dict() + roundtripped = BytesCodec.from_dict(encoded) + + assert codec == roundtripped From 1027f2c5a34e2d675d3c7602c2abb7364689ecee Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Sat, 30 Aug 2025 22:09:09 +0200 Subject: [PATCH 4/9] don't use the system's default in `from_dict` --- src/zarr/codecs/bytes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zarr/codecs/bytes.py b/src/zarr/codecs/bytes.py index 7576119c82..f5df964f90 100644 --- a/src/zarr/codecs/bytes.py +++ b/src/zarr/codecs/bytes.py @@ -50,6 +50,7 @@ def from_dict(cls, data: dict[str, JSON]) -> Self: data, "bytes", require_configuration=False ) configuration_parsed = configuration_parsed or {} + configuration_parsed.setdefault("endian", None) return cls(**configuration_parsed) # type: ignore[arg-type] def to_dict(self) -> dict[str, JSON]: From 99243585452f4f5a5544ba58c149d870d827e186 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Sat, 30 Aug 2025 22:15:55 +0200 Subject: [PATCH 5/9] changelog --- changes/3417.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/3417.bugfix.rst diff --git a/changes/3417.bugfix.rst b/changes/3417.bugfix.rst new file mode 100644 index 0000000000..51ff7049a6 --- /dev/null +++ b/changes/3417.bugfix.rst @@ -0,0 +1 @@ +Allow roundtripping ``BytesCodec`` instances to / from dict. From 8da13336dfa60c2b4412097bc5e58488aebbb66c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 31 May 2026 19:28:07 +0200 Subject: [PATCH 6/9] chore(deps): bump the actions group across 1 directory with 8 updates (#176) Bumps the actions group with 8 updates in the / directory: | Package | From | To | | --- | --- | --- | | [prefix-dev/setup-pixi](https://github.com/prefix-dev/setup-pixi) | `0.9.5` | `0.9.6` | | [codecov/codecov-action](https://github.com/codecov/codecov-action) | `6.0.0` | `6.0.1` | | [github/issue-metrics](https://github.com/github/issue-metrics) | `4.2.2` | `4.2.7` | | [j178/prek-action](https://github.com/j178/prek-action) | `2.0.3` | `2.0.4` | | [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.0` | `7.0.1` | | [actions/download-artifact](https://github.com/actions/download-artifact) | `7.0.0` | `8.0.1` | | [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) | `1.13.0` | `1.14.0` | | [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) | `0.5.3` | `0.5.6` | Updates `prefix-dev/setup-pixi` from 0.9.5 to 0.9.6 - [Release notes](https://github.com/prefix-dev/setup-pixi/releases) - [Commits](https://github.com/prefix-dev/setup-pixi/compare/1b2de7f3351f171c8b4dfeb558c639cb58ed4ec0...5185adfbffb4bd703da3010310260805d89ebb11) Updates `codecov/codecov-action` from 6.0.0 to 6.0.1 - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/57e3a136b779b570ffcdbf80b3bdc90e7fab3de2...e79a6962e0d4c0c17b229090214935d2e33f8354) Updates `github/issue-metrics` from 4.2.2 to 4.2.7 - [Release notes](https://github.com/github/issue-metrics/releases) - [Commits](https://github.com/github/issue-metrics/compare/c9e9838147fd355dace335ba787f01b6641a400a...1e38d5e62363e14db8019ed7d106b9855bdba6cc) Updates `j178/prek-action` from 2.0.3 to 2.0.4 - [Release notes](https://github.com/j178/prek-action/releases) - [Commits](https://github.com/j178/prek-action/compare/6ad80277337ad479fe43bd70701c3f7f8aa74db3...bdca6f102f98e2b4c7029491a53dfd366469e33d) Updates `actions/upload-artifact` from 7.0.0 to 7.0.1 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v7...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a) Updates `actions/download-artifact` from 7.0.0 to 8.0.1 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v7...3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c) Updates `pypa/gh-action-pypi-publish` from 1.13.0 to 1.14.0 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.13.0...cef221092ed1bacb1cc03d23a2d87d1d172e277b) Updates `zizmorcore/zizmor-action` from 0.5.3 to 0.5.6 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/b1d7e1fb5de872772f31590499237e7cce841e8e...5f14fd08f7cf1cb1609c1e344975f152c7ee938d) --- updated-dependencies: - dependency-name: prefix-dev/setup-pixi dependency-version: 0.9.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: codecov/codecov-action dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: github/issue-metrics dependency-version: 4.2.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: j178/prek-action dependency-version: 2.0.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: actions/upload-artifact dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: actions/download-artifact dependency-version: 8.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: pypa/gh-action-pypi-publish dependency-version: 1.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions - dependency-name: zizmorcore/zizmor-action dependency-version: 0.5.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/downstream.yml | 2 +- .github/workflows/gpu_test.yml | 2 +- .github/workflows/hypothesis.yaml | 2 +- .github/workflows/issue-metrics.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 4 ++-- .github/workflows/zarr-metadata-release.yml | 12 ++++++------ .github/workflows/zizmor.yml | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 74026233c4..3eb6898895 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -34,7 +34,7 @@ jobs: persist-credentials: false - name: Set up pixi - uses: prefix-dev/setup-pixi@1b2de7f3351f171c8b4dfeb558c639cb58ed4ec0 # v0.9.5 + uses: prefix-dev/setup-pixi@5185adfbffb4bd703da3010310260805d89ebb11 # v0.9.6 with: manifest-path: xarray/pixi.toml diff --git a/.github/workflows/gpu_test.yml b/.github/workflows/gpu_test.yml index 403441b306..333769cb9e 100644 --- a/.github/workflows/gpu_test.yml +++ b/.github/workflows/gpu_test.yml @@ -76,7 +76,7 @@ jobs: hatch env run --env "$HATCH_ENV" run-coverage - name: Upload coverage - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 + uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 with: token: ${{ secrets.CODECOV_TOKEN }} flags: gpu diff --git a/.github/workflows/hypothesis.yaml b/.github/workflows/hypothesis.yaml index 4f9467be7d..a456b2aa0a 100644 --- a/.github/workflows/hypothesis.yaml +++ b/.github/workflows/hypothesis.yaml @@ -93,7 +93,7 @@ jobs: key: cache-hypothesis-${{ runner.os }}-${{ github.run_id }} - name: Upload coverage - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 + uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 with: token: ${{ secrets.CODECOV_TOKEN }} flags: tests diff --git a/.github/workflows/issue-metrics.yml b/.github/workflows/issue-metrics.yml index 14fba5b9ec..510849ef3e 100644 --- a/.github/workflows/issue-metrics.yml +++ b/.github/workflows/issue-metrics.yml @@ -33,7 +33,7 @@ jobs: echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV" - name: Run issue-metrics tool - uses: github/issue-metrics@c9e9838147fd355dace335ba787f01b6641a400a # v4.2.2 + uses: github/issue-metrics@1e38d5e62363e14db8019ed7d106b9855bdba6cc # v4.2.7 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} SEARCH_QUERY: 'repo:zarr-developers/zarr-python is:issue created:${{ env.last_month }} -reason:"not planned"' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 768e660ec2..fec211b4dd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -30,4 +30,4 @@ jobs: uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true - - uses: j178/prek-action@6ad80277337ad479fe43bd70701c3f7f8aa74db3 # v2.0.3 + - uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 03143d3e5b..62e571856b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -78,7 +78,7 @@ jobs: hatch env run --env "$HATCH_ENV" run-coverage - name: Upload coverage if: ${{ matrix.dependency-set == 'optional' && matrix.os == 'ubuntu-latest' }} - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 + uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 with: token: ${{ secrets.CODECOV_TOKEN }} flags: tests @@ -125,7 +125,7 @@ jobs: run: | hatch env run --env "$HATCH_ENV" run-coverage - name: Upload coverage - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 + uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 with: token: ${{ secrets.CODECOV_TOKEN }} flags: tests diff --git a/.github/workflows/zarr-metadata-release.yml b/.github/workflows/zarr-metadata-release.yml index 809d502f16..9639fcfdd3 100644 --- a/.github/workflows/zarr-metadata-release.yml +++ b/.github/workflows/zarr-metadata-release.yml @@ -35,7 +35,7 @@ jobs: - name: Build run: hatch build - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: zarr-metadata-dist path: packages/zarr-metadata/dist @@ -45,7 +45,7 @@ jobs: needs: [build] runs-on: ubuntu-latest steps: - - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: zarr-metadata-dist path: dist @@ -76,7 +76,7 @@ jobs: id-token: write # required for OIDC trusted publishing attestations: write # required for artifact attestations steps: - - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: zarr-metadata-dist path: dist @@ -87,7 +87,7 @@ jobs: subject-path: dist/* - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 upload_testpypi: name: Upload to TestPyPI @@ -101,7 +101,7 @@ jobs: id-token: write attestations: write steps: - - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: zarr-metadata-dist path: dist @@ -112,6 +112,6 @@ jobs: subject-path: dist/* - name: Publish package to TestPyPI - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 with: repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml index da19f22421..7ac4fe5d0e 100644 --- a/.github/workflows/zizmor.yml +++ b/.github/workflows/zizmor.yml @@ -32,4 +32,4 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 From 929ffcb1a539a6d03ef44dac0633c60216f5ea8f Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 18 Jun 2026 18:17:25 +0200 Subject: [PATCH 7/9] test: consolidate endian tests into one config-parameterized test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_endian and test_endian_write tested the same property — the bytes codec serializes multi-byte data correctly given an input configuration — differing only in the input array's byte order, which test_endian fixed to native (i.e. a slice of test_endian_write's matrix). Merge them into a single test parameterized over the configuration: input dtype byte order (>u2 / --- tests/test_codecs/test_bytes.py | 61 +++++++++++++++------------------ 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/tests/test_codecs/test_bytes.py b/tests/test_codecs/test_bytes.py index 4b1040e2d7..ce7ddb195f 100644 --- a/tests/test_codecs/test_bytes.py +++ b/tests/test_codecs/test_bytes.py @@ -29,24 +29,44 @@ from .test_codecs import _AsyncArrayProxy -@pytest.mark.filterwarnings("ignore:The endianness of the requested serializer") @pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) -@pytest.mark.parametrize("endian", ["big", "little"]) -async def test_endian(store: Store, endian: Literal["big", "little"]) -> None: - data = np.arange(0, 256, dtype="uint16").reshape((16, 16)) +@pytest.mark.parametrize("input_dtype", [">u2", "u2", " None: + """ + The `bytes` codec stores multi-byte data in the byte order configured on the + codec, regardless of the input array's byte order, and reads it back to the + original values. The input-dtype/store-endian cross-product exercises the + encode-side byteswap (input byte order != store byte order) and the no-op + case alike. Compression is disabled so the stored chunk is the codec's raw + output and its byte layout can be asserted directly. + """ + data = np.arange(0, 256, dtype=input_dtype).reshape((16, 16)) path = "endian" spath = StorePath(store, path) a = await zarr.api.asynchronous.create_array( spath, shape=data.shape, chunks=(16, 16), - dtype=data.dtype, + dtype="uint16", fill_value=0, - chunk_key_encoding={"name": "v2", "separator": "."}, - serializer=BytesCodec(endian=endian), + compressors=None, + serializer=BytesCodec(endian=store_endian), ) await _AsyncArrayProxy(a)[:, :].set(data) + + # The stored chunk is laid out in the byte order configured on the codec. + stored = await store.get(f"{path}/c/0/0", prototype=default_buffer_prototype()) + assert stored is not None + expected_dtype = ">u2" if store_endian == "big" else " None: np.testing.assert_array_equal(arr, decoded.as_numpy_array()) -@pytest.mark.filterwarnings("ignore:The endianness of the requested serializer") -@pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) -@pytest.mark.parametrize("dtype_input_endian", [">u2", "u2", " None: - data = np.arange(0, 256, dtype=dtype_input_endian).reshape((16, 16)) - path = "endian" - spath = StorePath(store, path) - a = await zarr.api.asynchronous.create_array( - spath, - shape=data.shape, - chunks=(16, 16), - dtype="uint16", - fill_value=0, - chunk_key_encoding={"name": "v2", "separator": "."}, - serializer=BytesCodec(endian=dtype_store_endian), - ) - - await _AsyncArrayProxy(a)[:, :].set(data) - readback_data = await _AsyncArrayProxy(a)[:, :].get() - assert np.array_equal(data, readback_data) - - @pytest.mark.parametrize("endian", ENDIAN) def test_bytes_codec_accepts_all_endians(endian: EndianLiteral) -> None: """ From 10ff17073de8673e17b9b292de0d6f360d0b05f8 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 18 Jun 2026 18:26:38 +0200 Subject: [PATCH 8/9] test: use Expect/ExpectFail helpers for bytes codec dict tests - to_dict and from_dict are inverses over the same (endian, wire-dict) table; express that with a single shared list of `Expect` cases instead of two duplicated parametrize tables. - rejects_unknown_endian now uses `ExpectFail`, matching the error-table idiom used elsewhere (e.g. test_chunk_grids). - Move the annotation-only `Store` import into a TYPE_CHECKING block (TC001), a latent issue from combining `from __future__ import annotations` with the integration tests during the earlier merge. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/test_codecs/test_bytes.py | 77 ++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/tests/test_codecs/test_bytes.py b/tests/test_codecs/test_bytes.py index ce7ddb195f..03dd0b40c6 100644 --- a/tests/test_codecs/test_bytes.py +++ b/tests/test_codecs/test_bytes.py @@ -5,14 +5,14 @@ import enum import sys import warnings -from typing import Any, Literal, cast +from typing import TYPE_CHECKING, Any, Literal, cast import numpy as np import pytest import zarr +from tests.conftest import Expect, ExpectFail from zarr.abc.codec import SupportsSyncCodec -from zarr.abc.store import Store from zarr.codecs.bytes import ( ENDIAN, BytesCodec, @@ -28,6 +28,9 @@ from .test_codecs import _AsyncArrayProxy +if TYPE_CHECKING: + from zarr.abc.store import Store + @pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) @pytest.mark.parametrize("input_dtype", [">u2", " None: assert restored == codec -@pytest.mark.parametrize( - ("endian", "expected"), - [ - pytest.param( - "little", {"name": "bytes", "configuration": {"endian": "little"}}, id="little" - ), - pytest.param("big", {"name": "bytes", "configuration": {"endian": "big"}}, id="big"), - pytest.param(None, {"name": "bytes"}, id="missing"), - ], -) -def test_to_dict(endian: EndianLiteral | None, expected: dict[str, Any]) -> None: - codec = BytesCodec(endian=endian) +# to_dict and from_dict are inverses over this (endian setting, wire dict) mapping: +# to_dict turns the endian setting into the dict; from_dict recovers it. +_ENDIAN_DICT_CASES: list[Expect[EndianLiteral | None, dict[str, Any]]] = [ + Expect( + input="little", + output={"name": "bytes", "configuration": {"endian": "little"}}, + id="little", + ), + Expect( + input="big", + output={"name": "bytes", "configuration": {"endian": "big"}}, + id="big", + ), + Expect(input=None, output={"name": "bytes"}, id="missing"), +] - actual = codec.to_dict() - assert actual == expected +@pytest.mark.parametrize("case", _ENDIAN_DICT_CASES, ids=lambda c: c.id) +def test_to_dict(case: Expect[EndianLiteral | None, dict[str, Any]]) -> None: + assert BytesCodec(endian=case.input).to_dict() == case.output -@pytest.mark.parametrize( - ("mapping", "expected"), - [ - pytest.param( - {"name": "bytes", "configuration": {"endian": "little"}}, "little", id="little" - ), - pytest.param({"name": "bytes", "configuration": {"endian": "big"}}, "big", id="big"), - pytest.param({"name": "bytes"}, None, id="missing"), - ], -) -def test_from_dict(mapping: dict[str, Any], expected: EndianLiteral | None) -> None: - actual = BytesCodec.from_dict(mapping) - - assert actual.endian == expected +@pytest.mark.parametrize("case", _ENDIAN_DICT_CASES, ids=lambda c: c.id) +def test_from_dict(case: Expect[EndianLiteral | None, dict[str, Any]]) -> None: + assert BytesCodec.from_dict(case.output).endian == case.input @pytest.mark.parametrize("endian", ["little", "big", pytest.param(None, id="missing")]) @@ -224,14 +220,25 @@ def test_bytes_codec_init_with_deprecated_class_member() -> None: assert codec.endian == "little" -def test_bytes_codec_rejects_unknown_endian() -> None: +@pytest.mark.parametrize( + "case", + [ + ExpectFail( + input="north", + exception=ValueError, + id="unknown-string", + msg="endian must be one of", + ), + ], + ids=lambda c: c.id, +) +def test_bytes_codec_rejects_unknown_endian(case: ExpectFail[Any]) -> None: """ - `BytesCodec.__init__` raises `ValueError` when given a string outside + `BytesCodec.__init__` raises `ValueError` when given a value outside `ENDIAN`, and the error message names the offending parameter. """ - kwargs: dict[str, Any] = {"endian": "north"} - with pytest.raises(ValueError, match="endian must be one of"): - BytesCodec(**kwargs) + with case.raises(): + BytesCodec(endian=case.input) def test_endian_attribute_error_for_unknown_member() -> None: From 2454c907fd31fb13bb73b8cc2cb3c711174c0771 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 18 Jun 2026 18:28:37 +0200 Subject: [PATCH 9/9] doc: note from_dict behavior change in changelog Clarify that the fix alters BytesCodec.from_dict: a missing `endian` configuration is now interpreted as endian=None (what to_dict emits) rather than the system's native byte order. Co-Authored-By: Claude Opus 4.8 (1M context) --- changes/3417.bugfix.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/changes/3417.bugfix.md b/changes/3417.bugfix.md index e98fcfb778..be5b44f3e9 100644 --- a/changes/3417.bugfix.md +++ b/changes/3417.bugfix.md @@ -1 +1,4 @@ -Allow roundtripping `BytesCodec` instances to / from dict. +Fixed `BytesCodec.from_dict` so that `BytesCodec` instances roundtrip to / from +their dict representation. `BytesCodec.from_dict` now interprets a missing +`endian` configuration as `endian=None` (matching what `BytesCodec.to_dict` +emits), instead of falling back to the system's native byte order.