Skip to content

feat(zarr-metadata): draw nested fields and codec pipelines - #416

Draft
d-v-b wants to merge 5 commits into
mainfrom
claude/zarr-metadata-nested-strategies
Draft

d-v-b wants to merge 5 commits into
mainfrom
claude/zarr-metadata-nested-strategies

Conversation

@d-v-b

@d-v-b d-v-b commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

🤖 AI text below 🤖

Added strategies to zarr_metadata.testing.strategies for the definitions that hold other declarations: struct, cast_value and sharding_indexed. Their nested declarations are drawn from the same Strategies mapping, so a struct's field types, a cast's target data type and a shard's codecs come from every definition the mapping holds. A new pipelines strategy draws a whole codec pipeline for a Chunk, drawing each codec for the chunk the codec before it hands on, as that codec's transition gives it. Shards are drawn at most one inside another, and structs likewise. Every function a strategy record holds now also takes the Strategies mapping as its last argument, and declarations reads what it draws in the scope of every definition the mapping holds.

cast_value and scale_offset reject fixed_length_utf32, null_terminated_bytes, raw_bytes and variable_length_bytes, whose values are not numbers. The strategies no longer draw them for either codec.

How the nested strategies work

  • Every strategy record's functions take Strategies as their last argument, so a definition that holds fields draws them from the same mapping, including any strategies an extension author added. This changes the record signatures from PR 1, which is fine since zarr-metadata is pre-1.0.
  • A shard's two pipelines are drawn for the chunks its definition's pipelines returns: inner chunks of the shard's data type, and a uint64 index whose codecs must be of static size. The inner chunk lengths divide every length the shard takes along each axis, including the mixed lengths a rectilinear grid gives.
  • The pipeline walk reads each drawn array -> array codec and asks its definition's transition what it hands on. For example, a bytes codec after a cast_value to int16 gets an endian.
  • Shards and structs nest at most one level deep. Without a cap, shards nested up to 8 deep and overran Hypothesis's buffer.
  • declarations reads each draw in the scope of every definition the mapping holds, plus the definition being drawn. When two definitions share a name, the later one wins, so a definition named as a default replaces that default instead of raising ScopeConflictError.
  • The public functions wrap the mapping in one whose repr is short. Hypothesis puts a strategy's repr into its notes on every error raised during a draw, including its own floats(width=16) rejections, and warns when that repr passes 30,000 characters. Under filterwarnings = error, the full mapping's repr made the test fail intermittently.

Tests

  • test_pipelines_read reads each drawn pipeline with read_pipeline. The chunks come from a grid over a shape (regular or rectilinear), plus chunks with unknown lengths or an unread data type. It also covers the JSON round trip.
  • test_codecs_take_their_chunk now checks a shard's inner pipelines too.
  • A dedicated test draws shards over mixed lengths, and another draws over a length of 2**30.
  • New reach tests:
    • pipelines reaches every codec;
    • a shard's two pipelines reach every codec each one takes, and a shard inside a shard;
    • struct fields reach every fixed-size data type, including a struct;
    • cast_value reaches every real-number target, and scalars in both directions.
  • test_cast_value_draws_only_real_number_data_types checks that cast_value draws stay within the real-number types, a closed-set check the reach tests do not give.
  • tests/v3/test_zarr_python_scope.py checks that each codec rejects the non-numeric zarr-python data types.
  • Three tests cover same-named definitions, including a property test that reads every drawn spelling in the scope that also holds the default gzip. The agent reversed the fix in _scope and confirmed the suite fails.
  • The agent planted 20 bugs in the new strategies, and the suite catches every one. One of them (inner lengths from the smallest shard length rather than the gcd) got through once, before the mixed-lengths test was added.
  • just check is green on Python 3.11 and 3.14.

Left open

  • cast_value draws wrap to the numpy time types, because the package accepts it (_arithmetic.py treats them as int64). The cast_value spec says wrap is "only permitted when data_type is an integral type". That is a decision for the package's rules, not this PR.
  • A custom shard or struct strategy applies only at the outermost level.
  • Mappings narrowed too far give unhelpful errors: _struct with no fixed-size types, and a Chunk() blame on shards without bytes.
  • pipelines has no spellings option yet. It fits PR 3, which builds whole documents.
Stack

PR 2 of 5. PR 1 (#414) landed upstream as zarr-developers#4575, so this PR now targets main directly. The changelog fragments are numbered after this PR; renumber them if it is upstreamed.

🤖 Generated with Claude Code

d-v-b added a commit that referenced this pull request Oct 27, 2026
… after #416

Assisted-by: ClaudeCode:claude-opus-5-5
@d-v-b
d-v-b changed the base branch from claude/zarr-metadata-property-tests-4758bf to main November 3, 2026 20:43
d-v-b added 5 commits November 3, 2026 22:15
Add Hypothesis strategies for struct, cast_value and sharding_indexed,
which draw the fields they hold from the same Strategies mapping, and a
`pipelines` strategy that walks a codec pipeline, drawing each codec for
the chunk the codec before it hands on via its definition's transition.
A shard's inner and index pipelines are drawn for the chunks its
definition's `pipelines` gives. Shards and structs nest at most one
level deep, and strategies are wrapped in a mapping with a short repr so
Hypothesis's error notes stay under its 30,000-character warning.

Every strategy record's functions now take the Strategies mapping as
their last argument.

Assisted-by: ClaudeCode:claude-opus-5-5
`NOT_NUMBERS` predates the zarr-python data types, so `cast_value` and
`scale_offset` accepted `fixed_length_utf32`, `null_terminated_bytes`,
`raw_bytes` and `variable_length_bytes`, and the strategies drew them.
The cast_value spec defines the codec only for floating-point and
integral data types.

Assisted-by: ClaudeCode:claude-opus-5-5
`_scope` documented this but raised `ScopeConflictError`, so `declarations`
failed for any definition named as a default, which worked before this
branch. Pipelines and data type names now draw only the definition that
claims a name, and `declarations` reads a draw with its own definition
claiming the name.

Assisted-by: ClaudeCode:claude-opus-5-5
The test compared a draw with the bare name, which depended on the
example Hypothesis returned and said nothing of which definition read
it. Every spelling is now read by the definition it was drawn for.

Assisted-by: ClaudeCode:claude-opus-5-5
@d-v-b
d-v-b force-pushed the claude/zarr-metadata-nested-strategies branch from c6f10be to 747a4c0 Compare November 3, 2026 21:20

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant