Fix structured dtype v2 round-trip and silent loss of field offsets/padding#188
Draft
d-v-b wants to merge 7 commits into
Draft
Fix structured dtype v2 round-trip and silent loss of field offsets/padding#188d-v-b wants to merge 7 commits into
d-v-b wants to merge 7 commits into
Conversation
…#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](prefix-dev/setup-pixi@1b2de7f...5185adf) 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](codecov/codecov-action@57e3a13...e79a696) Updates `github/issue-metrics` from 4.2.2 to 4.2.7 - [Release notes](https://github.com/github/issue-metrics/releases) - [Commits](github-community-projects/issue-metrics@c9e9838...1e38d5e) Updates `j178/prek-action` from 2.0.3 to 2.0.4 - [Release notes](https://github.com/j178/prek-action/releases) - [Commits](j178/prek-action@6ad8027...bdca6f1) Updates `actions/upload-artifact` from 7.0.0 to 7.0.1 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v7...043fb46) Updates `actions/download-artifact` from 7.0.0 to 8.0.1 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](actions/download-artifact@v7...3e5f45b) 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](pypa/gh-action-pypi-publish@v1.13.0...cef2210) Updates `zizmorcore/zizmor-action` from 0.5.3 to 0.5.6 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](zizmorcore/zizmor-action@b1d7e1f...5f14fd0) --- 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] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…adding Bug 1: Nested structured dtypes failed the Zarr V2 JSON round-trip. The inner type guard check_structured_dtype_v2_inner recursed into itself for a nested field's last element, but that element is a list of [name, dtype] field pairs, not a single pair. It now validates that element with check_structured_dtype_name_v2, so metadata written by to_json(zarr_format=2) can be read back. Bug 2: Structured dtypes with non-default (aligned / padded) field layouts were silently re-packed contiguously on round-trip, changing field offsets and itemsize and corrupting stored bytes. from_native_dtype now detects non-packed layouts (recursively) and raises a clear ValueError instead. Reading existing packed data is unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Open
59 tasks
db473cd to
fd6189b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two verified HIGH-severity bugs in structured-dtype handling. Addresses B9–B10 of #181.
Bug 1 — Nested structured dtypes failed the Zarr V2 JSON round-trip
check_structured_dtype_v2_inner(src/zarr/core/dtype/common.py) is a recursive type guard for the inner elements of a V2 structured-dtype name. For a nested field such as["nested", [["x","<f4"],["y","<f8"]]], the last element is a list of[name, dtype]field pairs, but the function recursed into itself — which expects a single[name, dtype]pair — and returnedFalse.Consequence:
Struct.to_json(zarr_format=2)emits exactly this nested form, thenget_data_type_from_json(j, zarr_format=2)raisedValueError: No Zarr data type found. Zarr wrote V2 metadata it could not read back.Fix: when the last element is a non-string
Sequence, validate it withcheck_structured_dtype_name_v2(the list-of-fields checker) instead of recursing as a single pair. Verified the full round-tripfrom_native_dtype → to_json(v2) → from_json → to_native_dtypefor nested dtypes 2 and 3 levels deep, plus an end-to-end V2 array write/reopen/read.Bug 2 — Structured dtypes silently lost field offsets / padding / alignment
Structured.from_native_dtype(src/zarr/core/dtype/npy/structured.py) records only(name, dtype)pairs, andto_native_dtyperebuilds withnp.dtype([(name, dtype), ...]), which re-packs fields contiguously. Sonp.dtype([('a','i1'),('b','i8')], align=True)(itemsize 16) round-tripped to itemsize 9 — silently changing field offsets and misinterpreting stored chunk bytes (data corruption).Decision — loud failure rather than layout preservation. numpy's only faithfully round-trippable representation of an aligned/padded structured dtype is
dtype.descr, which inserts synthetic empty-named void padding fields (e.g.('', '|V7')). The Zarr V2 structurednameform is a list of[name, dtype]pairs and could carry those padding entries shape-wise, but doing so would require materially changing the publicStructured.fieldsmodel (adding synthetic void fields), and would ripple through the V3 form, scalar handling, and all field-iteration logic — a large, risky change to field semantics that is not what the zarr-v2 / numcodecs ecosystem historically wrote. Given the hard constraints "reading existing packed data must keep working unchanged" and "no drive-by refactors," the surgical and safe choice is to fail loudly rather than silently corrupt.Fix:
from_native_dtypenow detects a non-default-packed layout (a recursive check that compares the dtype's itemsize and per-field offsets against a contiguously-repacked rebuild, so padding inside a nested field is caught even when the outer dtype is packed) and raises a clearValueErrorexplaining that aligned/padded structured dtypes are not serializable. The error is aValueError(notDataTypeValidationError) on purpose: the data type registry suppressesDataTypeValidationErroras "does not match", but a padded layout does match this dtype class and must surface to the caller.Reading existing stored data is unaffected — the
from_jsonpaths reconstruct packed dtypes exactly as before; onlyfrom_native_dtypeof a non-packed numpy dtype now raises.Tests
Added regression tests in
tests/test_dtype/test_npy/test_structured.py: the exact repro cases plus a 2-level-nested case andalign=Truecases (top-level padding and nested-field padding), and a packed round-trip guard against false positives.Notes
🤖 Generated with Claude Code