fix(zarr-metadata): make JSONValue's array arm covariant - #295
Closed
d-v-b wants to merge 4 commits into
Closed
Conversation
* deps: bump cast-value.rs to >= 0.4.2 * deps: update uv.lock for cast-value-rs >= 0.4.2 The pyproject floor alone left uv.lock pinning 0.4.0, so any lock-honoring install (uv sync --locked/--frozen) kept the version that silently transposes non-row-major input. Assisted-by: ClaudeCode:claude-fable-5 * fix(cast_value): enforce the cast-value-rs floor at runtime The pyproject floor only binds installs that go through the zarr[cast-value-rs] extra. An environment that already has an older cast-value-rs installed kept silently corrupting non-row-major input after upgrading zarr, which is the failure this floor exists to stop. Check the installed version at import and raise from _do_cast, so the error surfaces when the codec is used rather than breaking `import zarr` for everyone else. A backend without distribution metadata (a `maturin develop` build) has no version to compare and is left alone. Assisted-by: ClaudeCode:claude-fable-5 * test(cast_value): cover cast_value next to the transpose codec Regression test for zarr-developers#4237: a cast_value codec on either side of a transpose codec must round-trip, because transpose hands the next codec a non-row-major view. Imported unchanged from zarr-developers#4238, where this test was written. The np.ascontiguousarray workaround that accompanied it there is deliberately left out: cast-value-rs 0.4.2 normalizes layout itself, and the workaround promotes 0-d arrays to shape (1,), breaking 0-d arrays. Co-authored-by: Raphael Jolivet <raphael.jolivet@minesparis.psl.eu> Assisted-by: ClaudeCode:claude-fable-5 --------- Co-authored-by: Raphael Jolivet <contact@raphael-jolivet.name> Co-authored-by: Raphael Jolivet <raphael.jolivet@minesparis.psl.eu>
`list["JSONValue"] | tuple["JSONValue", ...]` is invariant in the element type, so a value typed with any narrower element -- a `list[str]` field on a TypedDict, a `Sequence[float]` -- was not assignable to `JSONValue`, and a TypedDict carrying such fields was not assignable to `Mapping[str, JSONValue]`. pyright's diagnostic for the failure suggests the fix verbatim: "Consider switching from list to Sequence which is covariant." The array arm is now `Sequence["JSONValue"]`. The docstring records the deliberate type-level cost (`Sequence` admits `str`/`bytes`; runtime narrowing must exclude them regardless of the alias's spelling). Found while aliasing zarr-cm's JsonValue to this type: the two aliases are structurally identical except for this arm, and with it changed, pyright unifies them across the package boundary. Assisted-by: ClaudeCode:claude-opus-5
d-v-b
force-pushed
the
jsonvalue-sequence-arm
branch
from
August 14, 2026 07:23
d34a4fb to
84f10eb
Compare
7 tasks
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.
🤖 AI text below 🤖
The problem
JSONValue's array arm islist["JSONValue"] | tuple["JSONValue", ...], andlist/tupleare invariant in their element type. So a value typed with any narrower element does not count as a JSON value:By extension, any TypedDict whose fields carry precise types (
Sequence[str],list[int], ...) is not assignable toMapping[str, JSONValue]— which is exactly the shape downstream packages want: zarr-cm's convention TypedDicts hit all three failure modes, andtyagrees with pyright on every one of them.The fix
The array arm becomes
Sequence["JSONValue"].Sequenceis covariant, solist[str],tuple[int, ...], andSequence[float]are all JSON values, and precisely-typed TypedDicts flow intoMapping[str, JSONValue].The docstring records the deliberate type-level cost:
Sequencesays nothing about the concrete container and admitsstr/bytes(strwas already a union arm); runtime code narrowing a JSON array must excludestr/bytes/bytearrayregardless of how the alias is spelled.Motivation
Found while unifying zarr-cm's
JsonValuewith this alias — the two are structurally identical except for this arm (zarr-cm choseSequencefor exactly the covariance reasons above). With this change, pyright unifies the two recursive aliases across the package boundary with zero errors, and zarr-cm can delete its own definition and alias this one.Verification
From
packages/zarr-metadata:just test(595 passed),just typecheck(0 errors),just lintclean. Probes exercising all three failure modes go from 3 errors to 0 against the patched package, under both pyright and ty.