Skip to content

Commit 6665849

Browse files
committed
Merge branch 'main' into poc/unified-zarr-chunk-grid
2 parents 0dcab70 + d022da5 commit 6665849

4 files changed

Lines changed: 40 additions & 4 deletions

File tree

doc/whats-new.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ Bug Fixes
3434
- Fix a major performance regression in :py:meth:`Coordinates.to_index` (and
3535
consequently :py:meth:`Dataset.to_dataframe`) caused by converting the cached
3636
code ndarrays into Python lists (:issue:`11305`).
37+
- Preserve the Zarr array ``fill_value`` in the variable ``encoding`` when reading
38+
a ``zarr_format=3`` store with ``use_zarr_fill_value_as_mask=False``, so it is no
39+
longer silently lost on round-trip (:issue:`10269`).
40+
By `Davis Bennett <https://github.com/d-v-b>`_.
3741

3842

3943
Documentation

xarray/backends/zarr.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -975,10 +975,19 @@ def open_store_variable(self, name):
975975
# by interpreting Zarr's fill_value to mean the same as netCDF's _FillValue
976976
if zarr_array.fill_value is not None:
977977
attributes["_FillValue"] = zarr_array.fill_value
978-
elif "_FillValue" in attributes:
979-
attributes["_FillValue"] = FillValueCoder.decode(
980-
attributes["_FillValue"], zarr_array.dtype
981-
)
978+
else:
979+
# Preserve the Zarr array fill_value in the encoding so it is not
980+
# lost on round-trip. The write path reads it back from here.
981+
# Only zarr_format 3 supports `fill_value` as an encoding key
982+
# (in zarr_format 2 the fill_value is set via `_FillValue`).
983+
# See https://github.com/pydata/xarray/issues/10269
984+
zarr_format_3 = _zarr_v3() and self.zarr_group.metadata.zarr_format == 3
985+
if zarr_format_3:
986+
encoding["fill_value"] = zarr_array.fill_value
987+
if "_FillValue" in attributes:
988+
attributes["_FillValue"] = FillValueCoder.decode(
989+
attributes["_FillValue"], zarr_array.dtype
990+
)
982991

983992
return Variable(dimensions, data, attributes, encoding)
984993

xarray/core/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,5 @@ def acquire(self, *args, **kwargs) -> Any: ...
379379
def release(self) -> None: ...
380380
def __enter__(self) -> Any: ...
381381
def __exit__(self, *args, **kwargs) -> None: ...
382+
def __hash__(self) -> int:
383+
return super().__hash__()

xarray/tests/test_backends.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3880,6 +3880,27 @@ def test_zarr_fill_value_setting(self, dtype):
38803880
# ``raise_on_invalid=vn in check_encoding_set`` line in zarr.py
38813881
# ds.foo.encoding["fill_value"] = fv
38823882

3883+
def test_zarr_fill_value_in_encoding_on_read(self) -> None:
3884+
# GH #10269: the Zarr array fill_value should be preserved in the
3885+
# variable encoding on read, so that it is not lost on round-trip.
3886+
# `fill_value` is an independent encoding key only for zarr_format 3;
3887+
# for zarr_format 2 the fill_value is set via `_FillValue`.
3888+
if not has_zarr_v3 or zarr.config.get("default_zarr_format") != 3:
3889+
pytest.skip("fill_value is only an encoding key for zarr_format 3")
3890+
3891+
ds = xr.Dataset({"foo": ("x", [1, 2, 3])})
3892+
ds.foo.encoding = {"fill_value": -99}
3893+
3894+
open_kwargs = {"consolidated": False, "use_zarr_fill_value_as_mask": False}
3895+
with self.roundtrip(ds, open_kwargs=open_kwargs) as actual:
3896+
assert actual.foo.encoding["fill_value"] == -99
3897+
3898+
# the fill_value must survive an open -> write -> open round-trip even
3899+
# when the user never touches the encoding explicitly
3900+
with self.roundtrip(ds, open_kwargs=open_kwargs) as opened:
3901+
with self.roundtrip(opened, open_kwargs=open_kwargs) as actual:
3902+
assert actual.foo.encoding["fill_value"] == -99
3903+
38833904

38843905
@requires_zarr
38853906
@pytest.mark.skipif(

0 commit comments

Comments
 (0)