Repository navigation
fix: migrate v2 arrays whose fill_value is null - #4616
Conversation
Zarr v2 allows "fill_value": null, and xarray writes it for variables without a _FillValue. Zarr v3 requires a fill value, so `zarr migrate v3` raised a TypeError on such arrays. Use the data type's default scalar, which is what zarr-python already returns for unwritten chunks of the v2 array.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4616 +/- ##
=======================================
Coverage 94.90% 94.90%
=======================================
Files 96 96
Lines 14048 14051 +3
=======================================
+ Hits 13332 13335 +3
Misses 716 716
🚀 New features to boost your workflow:
|
|
i think using the default scalar for the data type makes sense. we should also allow the user to define the new fill value. but we also need to re-encode any partial chunks because the v2 try adding a test case with a partially-written v2 chunk, and a new v3 fill value. reading the raw partially-written chunk in a v3 array should expose an incorrect fill value. |
Write only part of the first chunk, so the test checks that the padding stored in a partial chunk reads back the same after migration, and compare the v2 read against the same expected values as the v3 read. Assisted-by: ClaudeCode:claude-opus-5-5
|
nvm, we can't do anything about partial chunks because there is no way to distinguish a partial chunk from a chunk written intentionally with the same values. |
Summary
zarr migrate v3fails on any v2 array whose.zarrayhas"fill_value": null. That is valid in Zarr v2, and xarray writes it:Dataset.to_zarr(..., zarr_format=2)with xarray 2026.9.0 givesnullfor anint32data variable and for anint64coordinate. Migrating that store stops withand exit code 1. The cause is that
_convert_array_metadatapasses the v2 fill value straight toArrayV3Metadata, which requires one.The migrated metadata now uses
dtype.default_scalar()when the v2 fill value is null. That is the value zarr-python already returns for chunks of the v2 array that were never written (fill_value_or_defaultinchunk_utils.py), so reading the migrated array gives the same result as reading the v2 array. It is also whatcreate_arrayuses for a v3 array whenfill_value=None.test_migrate_null_fill_valuecreates a v2 array withfill_value=Nonefor an int and a float dtype, writes one of its two chunks, migrates it through the CLI and checks the fill value and the data. Without the change both cases fail withassert 1 == 0from theTypeErrorabove. With it they pass, and the whole oftests/test_cli/test_migrate_v3.pypasses (39 tests). I also ranzarr migrate v3on the xarray-written store: it exits 0 and both arrays read back with fill value 0 and their data unchanged.ruff check,ruff format --checkandcodespellpass on the two files.mypyreports no new errors (the only ones are in_obstore.py, the same on unmodified main in my environment without obstore).I did not run the full test suite, and I did not check other dtypes such as strings beyond what the parametrized test covers.
For reviewers
The choice of
default_scalar()rather than, say, refusing to migrate is the part worth a look. I went with it because it keeps the visible values of unwritten chunks the same before and after migration.Author attestation
TODO
docs/user-guide/*.mdchanges/