Commit e62e45b
* Add unified parse_json runtime type checker (#3285)
Introduce zarr.core.json_parse.parse_json, a single type-annotation-driven validator that consolidates the scattered per-field parse_* helpers. Handles primitives, Literal, unions/Optional, fixed and variadic tuples, Sequence/list (coerced to tuple), Mapping/dict, and TypedDict, with a bool-vs-int safe primitive check. Adds tests/test_json_parse.py (94 tests) and a changelog fragment. No existing call sites migrated yet; this is the proof-of-direction module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Migrate parse_order, parse_bool, parse_zarr_format to parse_json (#3285)
Pilot migration delegating three representative helpers to the unified parse_json validator. Public signatures and return types are unchanged. parse_zarr_format re-wraps parse_json's ValueError/TypeError as MetadataValidationError with the original message to preserve observable behavior. parse_json is imported function-locally to avoid circular imports. Focused suites green: test_common/test_config/test_metadata/test_json_parse = 430 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix lint and make TypedDict NotRequired robust under future annotations (#3285)
Apply ruff check/format to satisfy the Lint CI hook. Keep typing.Union/Optional spellings in tests (with noqa) to cover that origin path alongside X | Y. Rework _parse_typeddict to derive required/optional from get_type_hints(include_extras=True) + __total__ instead of __required_keys__, so class-syntax NotRequired is detected even when 'from __future__ import annotations' stringizes the hints (as in zarr's metadata modules). test_json_parse + test_common + test_metadata = 393 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Annotate origin as Any to satisfy mypy in _parse_typeddict (#3285)
get_origin(hint) is Required/NotRequired tripped mypy's comparison-overlap and unreachable checks; typing origin as Any keeps the runtime check while satisfying mypy. Full 'uv run --frozen mypy' is clean (190 files); ruff check/format clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Migrate Batch 1 literal parsers to parse_json (#3285)
Delegate the literal/type check of parse_indexing_order, parse_node_type, parse_node_type_array, parse_separator, two parse_zarr_format variants (group, v2), and parse_name to the unified parse_json. Public signatures and return types unchanged; each preserves its original exception type and message (wrapping parse_json's ValueError/TypeError into MetadataValidationError / NodeTypeValidationError / the original ValueError/TypeError where tests assert on them). parse_json imported function-locally to avoid circular imports. Focused suites + full mypy green (1196 passed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Migrate Batch 2 codec primitive parsers to parse_json (#3285)
Delegate the int/bool type check of parse_checksum, parse_clevel, parse_blocksize, parse_typesize, parse_gzip_level, and parse_zstd_level to parse_json, keeping each helper's range/bound check and exact error messages. Original exception types are preserved by wrapping parse_json's ValueError/TypeError. Note: parse_json rejects bool where the old isinstance(data, int) accepted it; verified no caller/test passes a bool to these, so this is a deliberate, more-correct strictening. parse_json imported function-locally. Codec suite + full mypy green (794 passed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Replace hand-written parse_json with msgspec.convert (#3285)
Delete the bespoke parse_json runtime type checker and route JSON metadata
validation through msgspec.convert, which handles the type coercions zarr needs
(Literal membership, int/bool strictness, list-to-tuple). A small hand-written
fallback (validate_json_value) covers the recursive JSON values msgspec cannot
build a schema for, and adds an explicit nesting-depth limit. The
registry/dtype/numcodec parsers stay hand-written since they need runtime
lookups.
Also fixes a latent generator-exhaustion bug in parse_storage_transformers,
adds msgspec as a dependency (pinned in the min_deps env), and preserves the
exception types and messages every migrated helper raised.
Net ~555 lines removed. Tests, mypy and ruff all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Encapsulate convert + field-context error into parse_field (#3285)
Address review feedback: factor the repeated convert-then-re-raise pattern out
of each per-field parser. convert now raises a field-agnostic
ValueError("Expected instance of TYPE, got DATA"); the new parse_field wraps it
and re-raises with field context ("Failed to parse input for FIELD") using the
caller's chosen exception type, chaining the original error. Every per-field
parser collapses to a one-liner.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: replace Sphinx roles in json_parse with plain literals (#3285)
The new ci/lint_docs.py check flags :func:/:class: roles, which pass through
as literal text under MkDocs/mkdocstrings instead of becoming links. msgspec
is not in the configured inventories, so cross-references would not resolve;
using inline literals matches how the codebase already writes np.nonzero.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep literal members and the offending value in parse errors (#3285)
The msgspec rewrite regressed error quality for Literal types: _type_name fell
back to __name__, rendering Literal[3] as bare "Literal", and parse_field
dropped the value entirely. The old per-field parsers reported both, e.g.
"Invalid value for 'zarr_format'. Expected '3'. Got '3.0'."
_type_name now renders parameterized types via str(), so members survive, and
parse_field reports expected type and received value:
Failed to parse input for 'zarr_format': expected Literal[3], got 3.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: hoist json_parse imports to module level (#3285)
These were function-local to dodge import cycles under the old hand-written
parse_json. After the msgspec rewrite json_parse's only zarr import is JSON
under TYPE_CHECKING, so it has no runtime zarr dependency and cannot form a
cycle. Verified each touched module still imports standalone.
Hoisting exposed a latent packaging gap: msgspec was added to pyproject.toml
but never locked, so uv.lock lacked it. That stayed hidden only because
json_parse was imported lazily; at module level it broke `uv run --frozen`
(ModuleNotFoundError in the docs job). Regenerated the lock, which adds
msgspec 0.21.1 and nothing else.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: note the stricter metadata parsing in the changelog (#3285)
The old per-field checks compared with ==, so numerically equal values passed:
zarr_format=2.0 was accepted. msgspec requires an actual int, so such inputs
are now rejected. Spec-conforming metadata is unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: link msgspec symbols via its inventory (#3285)
Adds msgspec's Sphinx inventory so the json_parse docstrings can reference
msgspec.convert and msgspec.ValidationError as real cross-references instead
of inert literals. Verified the inventory is served at msgspec.dev (the
jcristharif.com path in the package metadata is stale) and that both symbols
resolve in it.
Same-module names stay plain literals: zarr.core.json_parse is internal and
not rendered in the API reference, so a cross-reference to it would not
resolve and would fail `mkdocs build --strict`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Davis Bennett <davis.v.bennett@gmail.com>
1 parent 9fd669f commit e62e45b
17 files changed
Lines changed: 369 additions & 73 deletions
File tree
- changes
- src/zarr
- codecs
- core
- metadata
- tests
- test_metadata
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
199 | 199 | | |
200 | 200 | | |
201 | 201 | | |
| 202 | + | |
202 | 203 | | |
203 | 204 | | |
204 | 205 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| 52 | + | |
52 | 53 | | |
53 | 54 | | |
54 | 55 | | |
| |||
281 | 282 | | |
282 | 283 | | |
283 | 284 | | |
| 285 | + | |
284 | 286 | | |
285 | 287 | | |
286 | 288 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
| |||
104 | 105 | | |
105 | 106 | | |
106 | 107 | | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
115 | 115 | | |
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
119 | | - | |
120 | | - | |
121 | | - | |
| 119 | + | |
| 120 | + | |
122 | 121 | | |
123 | 122 | | |
124 | 123 | | |
125 | | - | |
126 | | - | |
127 | | - | |
| 124 | + | |
| 125 | + | |
128 | 126 | | |
129 | 127 | | |
130 | 128 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
22 | | - | |
23 | | - | |
24 | | - | |
| 23 | + | |
| 24 | + | |
25 | 25 | | |
26 | | - | |
| 26 | + | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| |||
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | | - | |
33 | | - | |
34 | | - | |
| 32 | + | |
| 33 | + | |
35 | 34 | | |
36 | 35 | | |
37 | 36 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
22 | | - | |
23 | | - | |
24 | | - | |
| 23 | + | |
25 | 24 | | |
26 | 25 | | |
27 | 26 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
| |||
147 | 148 | | |
148 | 149 | | |
149 | 150 | | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | | - | |
155 | | - | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
156 | 158 | | |
157 | 159 | | |
158 | 160 | | |
| |||
227 | 229 | | |
228 | 230 | | |
229 | 231 | | |
230 | | - | |
231 | | - | |
232 | | - | |
| 232 | + | |
233 | 233 | | |
234 | 234 | | |
235 | 235 | | |
236 | | - | |
237 | | - | |
238 | | - | |
| 236 | + | |
239 | 237 | | |
240 | 238 | | |
241 | 239 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
| 37 | + | |
36 | 38 | | |
37 | 39 | | |
38 | 40 | | |
| |||
159 | 161 | | |
160 | 162 | | |
161 | 163 | | |
162 | | - | |
163 | | - | |
164 | | - | |
165 | | - | |
| 164 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| |||
85 | 86 | | |
86 | 87 | | |
87 | 88 | | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
| 89 | + | |
92 | 90 | | |
93 | 91 | | |
94 | 92 | | |
95 | 93 | | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
100 | 98 | | |
101 | 99 | | |
102 | 100 | | |
| |||
0 commit comments