Repository navigation
fix: tolerate unencodable default_scalar in ShardingCodec.validate - #4526
inchang-ing wants to merge 5 commits into
Conversation
ShardingCodec._validate_inner_codecs (and ArrayV3Metadata codec threading) probe the codec chain with dtype.default_scalar() as the fill value. When the chain maps only a subrange of the dtype — e.g. ScaleOffset + CastValue scaling float32 values in [100, 200] to uint16 — the probe value itself is unencodable, and array creation failed even though no real value ever needs that encoding. Make the fill-value threading in ScaleOffset.resolve_metadata and CastValue.resolve_metadata best-effort: if encoding the fill value raises a value-domain error (ValueError/OverflowError), carry the fill value forward unchanged instead of failing. Shape/dtype threading and per-codec validate() are untouched, so structurally invalid configurations (wrong dtype for a codec, scale=0, out_of_range='wrap' with a float target, ...) still raise at creation time. Genuine value-domain errors for actual chunk data still surface in _encode_sync. Fixes zarr-developers#4511
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4526 +/- ##
=======================================
Coverage 94.71% 94.71%
=======================================
Files 94 94
Lines 13663 13666 +3
=======================================
+ Hits 12941 12944 +3
Misses 722 722
🚀 New features to boost your workflow:
|
|
this fix is incorrect. we need to error when the fill value cannot be round-tripped because the array must be readable before any chunks have been written. the correct fix to the problem reported #4511 is to use the array's fill value instead of the data type's default value when validating the codec chain |
ShardingCodec._validate_inner_codecs probed the inner codec chain with dtype.default_scalar() as the fill value. When the chain maps only a subrange of the dtype (e.g. ScaleOffset + CastValue for float32 values in [100, 200] stored as uint16), the probe value itself was unencodable and array creation failed, even though the array's actual fill value round-trips fine. Thread the array's fill value into Codec.validate (new optional keyword) and use it for the probe. The fill value is what an unwritten chunk returns, so it is the value that must round-trip: if the array's fill value cannot be encoded, creation still fails. Codec subclasses that override validate() accept and ignore the new keyword; callers that have no fill value keep the old behavior. Fixes zarr-developers#4511 Co-Authored-By: Claude <noreply@anthropic.com>
|
@d-v-b You're right — thanks for the clear direction. I've reworked the PR to do exactly that:
So the repro from #4511 now succeeds when the array's fill value lies inside the encodable range (e.g. |
Fixes #4511
Root cause
When creating an array, codec chains are validated by threading an
ArraySpecthrough each codec'sresolve_metadata:ArrayV3Metadata.__init__threads the array's fill value (which defaults todtype.default_scalar()) through the codec chain,ShardingCodec._validate_inner_codecsthreads the inner codec chain withdtype.default_scalar()as the probe fill value — not the array's fill value.Value-transforming codecs (
ScaleOffset,CastValue) encode the fill value as part of that threading. When the chain maps only a subrange of the dtype — e.g. float32 values known to lie in [100, 200], scaled to [0, 65535] and cast to uint16 — the probe value (0) scales to -65535, whichCastValuerejects:Array creation failed even though no real value ever needs that encoding: the failure is a property of the probe fill value, not of the codec configuration.
Fix
Probe the codec chain with the array's fill value instead of the data type's default scalar:
Codec.validate(and the codecs that override it) gain an optionalfill_valuekeyword. Callers that validate array metadata (ArrayV3Metadata,create_array) pass the array's fill value; callers without one keep the previous behavior.ShardingCodec._validate_inner_codecsuses the passed fill value for the probe instead ofdtype.default_scalar().The fill value is what an unwritten chunk returns, so it is the value that must round-trip through the chain: if the array's fill value cannot be encoded, creation still fails — that array could never be read before its first write. A configuration is accepted exactly when the fill value the user chose round-trips.
Verification
fill_value=100with the [100, 200] → uint16 chain), including the sharded form.ValueErrorat creation: the error is genuine, since reading an unwritten chunk would return an unencodable value.scale=0,out_of_range='wrap'with a float target) — unchanged.test_creation_with_encodable_fill_value*).tests/test_codecs/: 901 passed, 1 skipped, 2 xfailed.Changes
src/zarr/abc/codec.py: optionalfill_valuekeyword onCodec.validatesrc/zarr/codecs/sharding.py:validate/_validate_inner_codecsaccept and use the fill valuesrc/zarr/codecs/{cast_value,scale_offset,transpose}.py: accept and ignore the new keywordsrc/zarr/core/codec_pipeline.py,src/zarr/core/metadata/v3.py,src/zarr/core/array.py: thread the array's fill value into validationCo-Authored-By: Claude noreply@anthropic.com