|
13 | 13 | import tempfile
|
14 | 14 | import uuid
|
15 | 15 | import warnings
|
| 16 | +from collections import ChainMap |
16 | 17 | from collections.abc import Generator, Iterator, Mapping
|
17 | 18 | from contextlib import ExitStack
|
18 | 19 | from io import BytesIO
|
@@ -3343,6 +3344,67 @@ def test_cache_members(self) -> None:
|
3343 | 3344 | observed_keys_2 = sorted(zstore_mut.array_keys())
|
3344 | 3345 | assert observed_keys_2 == sorted(array_keys + [new_key])
|
3345 | 3346 |
|
| 3347 | + @requires_dask |
| 3348 | + @pytest.mark.parametrize("dtype", [int, float]) |
| 3349 | + def test_zarr_fill_value_setting(self, dtype): |
| 3350 | + # When zarr_format=2, _FillValue sets fill_value |
| 3351 | + # When zarr_format=3, fill_value is set independently |
| 3352 | + # We test this by writing a dask array with compute=False, |
| 3353 | + # on read we should receive chunks filled with `fill_value` |
| 3354 | + fv = -1 |
| 3355 | + ds = xr.Dataset( |
| 3356 | + {"foo": ("x", dask.array.from_array(np.array([0, 0, 0], dtype=dtype)))} |
| 3357 | + ) |
| 3358 | + expected = xr.Dataset({"foo": ("x", [fv] * 3)}) |
| 3359 | + |
| 3360 | + zarr_format_2 = ( |
| 3361 | + has_zarr_v3 and zarr.config.get("default_zarr_format") == 2 |
| 3362 | + ) or not has_zarr_v3 |
| 3363 | + if zarr_format_2: |
| 3364 | + attr = "_FillValue" |
| 3365 | + expected.foo.attrs[attr] = fv |
| 3366 | + else: |
| 3367 | + attr = "fill_value" |
| 3368 | + if dtype is float: |
| 3369 | + # for floats, Xarray inserts a default `np.nan` |
| 3370 | + expected.foo.attrs["_FillValue"] = np.nan |
| 3371 | + |
| 3372 | + # turn off all decoding so we see what Zarr returns to us. |
| 3373 | + # Since chunks, are not written, we should receive on `fill_value` |
| 3374 | + open_kwargs = { |
| 3375 | + "mask_and_scale": False, |
| 3376 | + "consolidated": False, |
| 3377 | + "use_zarr_fill_value_as_mask": False, |
| 3378 | + } |
| 3379 | + save_kwargs = dict(compute=False, consolidated=False) |
| 3380 | + with self.roundtrip( |
| 3381 | + ds, |
| 3382 | + save_kwargs=ChainMap(save_kwargs, dict(encoding={"foo": {attr: fv}})), |
| 3383 | + open_kwargs=open_kwargs, |
| 3384 | + ) as actual: |
| 3385 | + assert_identical(actual, expected) |
| 3386 | + |
| 3387 | + ds.foo.encoding[attr] = fv |
| 3388 | + with self.roundtrip( |
| 3389 | + ds, save_kwargs=save_kwargs, open_kwargs=open_kwargs |
| 3390 | + ) as actual: |
| 3391 | + assert_identical(actual, expected) |
| 3392 | + |
| 3393 | + if zarr_format_2: |
| 3394 | + ds = ds.drop_encoding() |
| 3395 | + with pytest.raises(ValueError, match="_FillValue"): |
| 3396 | + with self.roundtrip( |
| 3397 | + ds, |
| 3398 | + save_kwargs=ChainMap( |
| 3399 | + save_kwargs, dict(encoding={"foo": {"fill_value": fv}}) |
| 3400 | + ), |
| 3401 | + open_kwargs=open_kwargs, |
| 3402 | + ): |
| 3403 | + pass |
| 3404 | + # TODO: this doesn't fail because of the |
| 3405 | + # ``raise_on_invalid=vn in check_encoding_set`` line in zarr.py |
| 3406 | + # ds.foo.encoding["fill_value"] = fv |
| 3407 | + |
3346 | 3408 |
|
3347 | 3409 | @requires_zarr
|
3348 | 3410 | @pytest.mark.skipif(
|
@@ -5827,23 +5889,23 @@ def test_encode_zarr_attr_value() -> None:
|
5827 | 5889 | @requires_zarr
|
5828 | 5890 | def test_extract_zarr_variable_encoding() -> None:
|
5829 | 5891 | var = xr.Variable("x", [1, 2])
|
5830 |
| - actual = backends.zarr.extract_zarr_variable_encoding(var) |
| 5892 | + actual = backends.zarr.extract_zarr_variable_encoding(var, zarr_format=3) |
5831 | 5893 | assert "chunks" in actual
|
5832 | 5894 | assert actual["chunks"] == ("auto" if has_zarr_v3 else None)
|
5833 | 5895 |
|
5834 | 5896 | var = xr.Variable("x", [1, 2], encoding={"chunks": (1,)})
|
5835 |
| - actual = backends.zarr.extract_zarr_variable_encoding(var) |
| 5897 | + actual = backends.zarr.extract_zarr_variable_encoding(var, zarr_format=3) |
5836 | 5898 | assert actual["chunks"] == (1,)
|
5837 | 5899 |
|
5838 | 5900 | # does not raise on invalid
|
5839 | 5901 | var = xr.Variable("x", [1, 2], encoding={"foo": (1,)})
|
5840 |
| - actual = backends.zarr.extract_zarr_variable_encoding(var) |
| 5902 | + actual = backends.zarr.extract_zarr_variable_encoding(var, zarr_format=3) |
5841 | 5903 |
|
5842 | 5904 | # raises on invalid
|
5843 | 5905 | var = xr.Variable("x", [1, 2], encoding={"foo": (1,)})
|
5844 | 5906 | with pytest.raises(ValueError, match=r"unexpected encoding parameters"):
|
5845 | 5907 | actual = backends.zarr.extract_zarr_variable_encoding(
|
5846 |
| - var, raise_on_invalid=True |
| 5908 | + var, raise_on_invalid=True, zarr_format=3 |
5847 | 5909 | )
|
5848 | 5910 |
|
5849 | 5911 |
|
|
0 commit comments