From 79dd9e287ca86dcd6d72981d64e228ee6d508641 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Sun, 25 May 2025 21:42:18 +0200 Subject: [PATCH 01/17] define zarr-specific FutureWarning and DeprecationWarning --- pyproject.toml | 2 +- src/zarr/_compat.py | 4 +++- src/zarr/api/asynchronous.py | 6 +++--- src/zarr/api/synchronous.py | 3 ++- src/zarr/convenience.py | 3 ++- src/zarr/core/array.py | 10 +++++----- src/zarr/core/group.py | 17 +++++++++++------ src/zarr/creation.py | 3 ++- src/zarr/errors.py | 6 ++++++ src/zarr/storage/__init__.py | 3 ++- tests/test_api.py | 14 +++++++------- tests/test_array.py | 35 ++++++++++++++++++++--------------- tests/test_group.py | 24 +++++++++++++++--------- tests/test_sync.py | 3 ++- tests/test_v2.py | 3 ++- 15 files changed, 83 insertions(+), 53 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f1c290e1b1..7db49a4460 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -408,7 +408,7 @@ filterwarnings = [ "ignore:Automatic shard shape inference is experimental and may change without notice.*:UserWarning", "ignore:The codec .* is currently not part in the Zarr format 3 specification.*:UserWarning", "ignore:The dtype .* is currently not part in the Zarr format 3 specification.*:UserWarning", - "ignore:Use zarr.create_array instead.:DeprecationWarning", + "ignore:Use zarr.create_array instead.:zarr.errors.ZarrDeprecationWarning", "ignore:Duplicate name.*:UserWarning", "ignore:The `compressor` argument is deprecated. Use `compressors` instead.:UserWarning", "ignore:Numcodecs codecs are not in the Zarr version 3 specification and may not be supported by other zarr implementations.:UserWarning", diff --git a/src/zarr/_compat.py b/src/zarr/_compat.py index 52d96005cc..87427b486e 100644 --- a/src/zarr/_compat.py +++ b/src/zarr/_compat.py @@ -4,6 +4,8 @@ from inspect import Parameter, signature from typing import Any, TypeVar +from zarr.errors import ZarrFutureWarning + T = TypeVar("T") # Based off https://github.com/scikit-learn/scikit-learn/blob/e87b32a81c70abed8f2e97483758eb64df8255e9/sklearn/utils/validation.py#L63 @@ -54,7 +56,7 @@ def inner_f(*args: Any, **kwargs: Any) -> T: f"{version} passing these as positional arguments " "will result in an error" ), - FutureWarning, + ZarrFutureWarning, stacklevel=2, ) kwargs.update(zip(sig.parameters, args, strict=False)) diff --git a/src/zarr/api/asynchronous.py b/src/zarr/api/asynchronous.py index 4f3c9c3f8f..6df6a552ff 100644 --- a/src/zarr/api/asynchronous.py +++ b/src/zarr/api/asynchronous.py @@ -39,7 +39,7 @@ ) from zarr.core.metadata import ArrayMetadataDict, ArrayV2Metadata, ArrayV3Metadata from zarr.core.metadata.v2 import _default_compressor, _default_filters -from zarr.errors import NodeTypeValidationError +from zarr.errors import NodeTypeValidationError, ZarrDeprecationWarning from zarr.storage._common import make_store_path if TYPE_CHECKING: @@ -159,7 +159,7 @@ def _handle_zarr_version_or_format( ) if zarr_version is not None: warnings.warn( - "zarr_version is deprecated, use zarr_format", DeprecationWarning, stacklevel=2 + "zarr_version is deprecated, use zarr_format", ZarrDeprecationWarning, stacklevel=2 ) return zarr_version return zarr_format @@ -517,7 +517,7 @@ async def save_group( await asyncio.gather(*aws) -@deprecated("Use AsyncGroup.tree instead.") +@deprecated("Use AsyncGroup.tree instead.", category=ZarrDeprecationWarning) async def tree(grp: AsyncGroup, expand: bool | None = None, level: int | None = None) -> Any: """Provide a rich display of the hierarchy. diff --git a/src/zarr/api/synchronous.py b/src/zarr/api/synchronous.py index d4b652ad6e..45aa4c6f99 100644 --- a/src/zarr/api/synchronous.py +++ b/src/zarr/api/synchronous.py @@ -11,6 +11,7 @@ from zarr.core.group import Group from zarr.core.sync import sync from zarr.core.sync_group import create_hierarchy +from zarr.errors import ZarrDeprecationWarning if TYPE_CHECKING: from collections.abc import Iterable @@ -335,7 +336,7 @@ def save_group( ) -@deprecated("Use Group.tree instead.") +@deprecated("Use Group.tree instead.", category=ZarrDeprecationWarning) def tree(grp: Group, expand: bool | None = None, level: int | None = None) -> Any: """Provide a rich display of the hierarchy. diff --git a/src/zarr/convenience.py b/src/zarr/convenience.py index 88f10663b7..3ca4ffcb4b 100644 --- a/src/zarr/convenience.py +++ b/src/zarr/convenience.py @@ -22,6 +22,7 @@ save_group, tree, ) +from zarr.errors import ZarrDeprecationWarning __all__ = [ "consolidate_metadata", @@ -40,6 +41,6 @@ warnings.warn( "zarr.convenience is deprecated. " "Import these functions from the top level zarr. namespace instead.", - DeprecationWarning, + ZarrDeprecationWarning, stacklevel=2, ) diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 78b5e92ed6..d14940301c 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -110,7 +110,7 @@ ) from zarr.core.metadata.v3 import DataType, parse_node_type_array from zarr.core.sync import sync -from zarr.errors import MetadataValidationError +from zarr.errors import MetadataValidationError, ZarrDeprecationWarning from zarr.registry import ( _parse_array_array_codec, _parse_array_bytes_codec, @@ -394,7 +394,7 @@ async def create( ) -> AsyncArray[ArrayV3Metadata] | AsyncArray[ArrayV2Metadata]: ... @classmethod - @deprecated("Use zarr.api.asynchronous.create_array instead.") + @deprecated("Use zarr.api.asynchronous.create_array instead.", category=ZarrDeprecationWarning) @_deprecate_positional_args async def create( cls, @@ -1002,7 +1002,7 @@ def serializer(self) -> ArrayBytesCodec | None: ) @property - @deprecated("Use AsyncArray.compressors instead.") + @deprecated("Use AsyncArray.compressors instead.", category=ZarrDeprecationWarning) def compressor(self) -> numcodecs.abc.Codec | None: """ Compressor that is applied to each chunk of the array. @@ -1727,7 +1727,7 @@ class Array: _async_array: AsyncArray[ArrayV3Metadata] | AsyncArray[ArrayV2Metadata] @classmethod - @deprecated("Use zarr.create_array instead.") + @deprecated("Use zarr.create_array instead.", category=ZarrDeprecationWarning) @_deprecate_positional_args def create( cls, @@ -2115,7 +2115,7 @@ def serializer(self) -> None | ArrayBytesCodec: return self._async_array.serializer @property - @deprecated("Use Array.compressors instead.") + @deprecated("Use Array.compressors instead.", category=ZarrDeprecationWarning) def compressor(self) -> numcodecs.abc.Codec | None: """ Compressor that is applied to each chunk of the array. diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index 5c470e29ca..ad32446f11 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -53,7 +53,12 @@ from zarr.core.metadata import ArrayV2Metadata, ArrayV3Metadata from zarr.core.metadata.v3 import V3JsonEncoder, _replace_special_floats from zarr.core.sync import SyncMixin, sync -from zarr.errors import ContainsArrayError, ContainsGroupError, MetadataValidationError +from zarr.errors import ( + ContainsArrayError, + ContainsGroupError, + MetadataValidationError, + ZarrDeprecationWarning, +) from zarr.storage import StoreLike, StorePath from zarr.storage._common import ensure_no_existing_node, make_store_path from zarr.storage._utils import _join_paths, _normalize_path_keys, normalize_path @@ -1128,7 +1133,7 @@ async def create_array( config=config, ) - @deprecated("Use AsyncGroup.create_array instead.") + @deprecated("Use AsyncGroup.create_array instead.", category=ZarrDeprecationWarning) async def create_dataset( self, name: str, *, shape: ShapeLike, **kwargs: Any ) -> AsyncArray[ArrayV2Metadata] | AsyncArray[ArrayV3Metadata]: @@ -1162,7 +1167,7 @@ async def create_dataset( await array.setitem(slice(None), data) return array - @deprecated("Use AsyncGroup.require_array instead.") + @deprecated("Use AsyncGroup.require_array instead.", category=ZarrDeprecationWarning) async def require_dataset( self, name: str, @@ -2504,7 +2509,7 @@ def create_array( ) ) - @deprecated("Use Group.create_array instead.") + @deprecated("Use Group.create_array instead.", category=ZarrDeprecationWarning) def create_dataset(self, name: str, **kwargs: Any) -> Array: """Create an array. @@ -2528,7 +2533,7 @@ def create_dataset(self, name: str, **kwargs: Any) -> Array: """ return Array(self._sync(self._async_group.create_dataset(name, **kwargs))) - @deprecated("Use Group.require_array instead.") + @deprecated("Use Group.require_array instead.", category=ZarrDeprecationWarning) def require_dataset(self, name: str, *, shape: ShapeLike, **kwargs: Any) -> Array: """Obtain an array, creating if it doesn't exist. @@ -2758,7 +2763,7 @@ def move(self, source: str, dest: str) -> None: """ return self._sync(self._async_group.move(source, dest)) - @deprecated("Use Group.create_array instead.") + @deprecated("Use Group.create_array instead.", category=ZarrDeprecationWarning) @_deprecate_positional_args def array( self, diff --git a/src/zarr/creation.py b/src/zarr/creation.py index 8197c4950c..622406ed75 100644 --- a/src/zarr/creation.py +++ b/src/zarr/creation.py @@ -23,6 +23,7 @@ zeros, zeros_like, ) +from zarr.errors import ZarrDeprecationWarning __all__ = [ "array", @@ -42,6 +43,6 @@ warnings.warn( "zarr.creation is deprecated. " "Import these functions from the top level zarr. namespace instead.", - DeprecationWarning, + ZarrDeprecationWarning, stacklevel=2, ) diff --git a/src/zarr/errors.py b/src/zarr/errors.py index 441cdab9a3..696ae37f4c 100644 --- a/src/zarr/errors.py +++ b/src/zarr/errors.py @@ -57,3 +57,9 @@ class NodeTypeValidationError(MetadataValidationError): This can be raised when the value is invalid or unexpected given the context, for example an 'array' node when we expected a 'group'. """ + + +class ZarrFutureWarning(FutureWarning): ... + + +class ZarrDeprecationWarning(DeprecationWarning): ... diff --git a/src/zarr/storage/__init__.py b/src/zarr/storage/__init__.py index 6721139375..00df50214f 100644 --- a/src/zarr/storage/__init__.py +++ b/src/zarr/storage/__init__.py @@ -3,6 +3,7 @@ from types import ModuleType from typing import Any +from zarr.errors import ZarrDeprecationWarning from zarr.storage._common import StoreLike, StorePath from zarr.storage._fsspec import FsspecStore from zarr.storage._local import LocalStore @@ -33,7 +34,7 @@ def __setattr__(self, attr: str, value: Any) -> None: "setting zarr.storage.default_compressor is deprecated, use " "zarr.config to configure array.v2_default_compressor " "e.g. config.set({'codecs.zstd':'numcodecs.Zstd', 'array.v2_default_compressor.numeric': 'zstd'})", - DeprecationWarning, + ZarrDeprecationWarning, stacklevel=1, ) else: diff --git a/tests/test_api.py b/tests/test_api.py index 640478e9c1..3317fd9b58 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -38,7 +38,7 @@ save_group, ) from zarr.core.buffer import NDArrayLike -from zarr.errors import MetadataValidationError +from zarr.errors import MetadataValidationError, ZarrDeprecationWarning, ZarrFutureWarning from zarr.storage import MemoryStore from zarr.storage._utils import normalize_path from zarr.testing.utils import gpu_test @@ -408,7 +408,7 @@ def test_tree() -> None: g3.create_group("baz") g5 = g3.create_group("qux") g5.create_array("baz", shape=(100,), chunks=(10,), dtype="float64") - with pytest.warns(DeprecationWarning): + with pytest.warns(ZarrDeprecationWarning): assert repr(zarr.tree(g1)) == repr(g1.tree()) assert str(zarr.tree(g1)) == str(g1.tree()) @@ -1082,7 +1082,7 @@ def test_tree() -> None: def test_open_positional_args_deprecated() -> None: store = MemoryStore() - with pytest.warns(FutureWarning, match="pass"): + with pytest.warns(ZarrFutureWarning, match="pass"): zarr.api.synchronous.open(store, "w", shape=(1,)) @@ -1090,9 +1090,9 @@ def test_save_array_positional_args_deprecated() -> None: store = MemoryStore() with warnings.catch_warnings(): warnings.filterwarnings( - "ignore", message="zarr_version is deprecated", category=DeprecationWarning + "ignore", message="zarr_version is deprecated", category=ZarrDeprecationWarning ) - with pytest.warns(FutureWarning, match="pass"): + with pytest.warns(ZarrFutureWarning, match="pass"): save_array( store, np.ones( @@ -1104,13 +1104,13 @@ def test_save_array_positional_args_deprecated() -> None: def test_group_positional_args_deprecated() -> None: store = MemoryStore() - with pytest.warns(FutureWarning, match="pass"): + with pytest.warns(ZarrFutureWarning, match="pass"): group(store, True) def test_open_group_positional_args_deprecated() -> None: store = MemoryStore() - with pytest.warns(FutureWarning, match="pass"): + with pytest.warns(ZarrFutureWarning, match="pass"): open_group(store, "w") diff --git a/tests/test_array.py b/tests/test_array.py index 32f7887007..49f48b5d15 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -47,7 +47,12 @@ from zarr.core.indexing import BasicIndexer, ceildiv from zarr.core.metadata.v3 import ArrayV3Metadata, DataType from zarr.core.sync import sync -from zarr.errors import ContainsArrayError, ContainsGroupError +from zarr.errors import ( + ContainsArrayError, + ContainsGroupError, + ZarrDeprecationWarning, + ZarrFutureWarning, +) from zarr.storage import LocalStore, MemoryStore, StorePath if TYPE_CHECKING: @@ -229,11 +234,11 @@ def test_array_v3_fill_value(store: MemoryStore, fill_value: int, dtype_str: str async def test_create_deprecated() -> None: - with pytest.warns(DeprecationWarning): - with pytest.warns(FutureWarning, match=re.escape("Pass shape=(2, 2) as keyword args")): + with pytest.warns(ZarrDeprecationWarning): + with pytest.warns(ZarrFutureWarning, match=re.escape("Pass shape=(2, 2) as keyword args")): await zarr.AsyncArray.create(MemoryStore(), (2, 2), dtype="f8") # type: ignore[call-overload] - with pytest.warns(DeprecationWarning): - with pytest.warns(FutureWarning, match=re.escape("Pass shape=(2, 2) as keyword args")): + with pytest.warns(ZarrDeprecationWarning): + with pytest.warns(ZarrFutureWarning, match=re.escape("Pass shape=(2, 2) as keyword args")): zarr.Array.create(MemoryStore(), (2, 2), dtype="f8") @@ -241,34 +246,34 @@ def test_selection_positional_args_deprecated() -> None: store = MemoryStore() arr = zarr.create_array(store, shape=(2, 2), dtype="f8") - with pytest.warns(FutureWarning, match="Pass out"): + with pytest.warns(ZarrFutureWarning, match="Pass out"): arr.get_basic_selection(..., NDBuffer(array=np.empty((2, 2)))) - with pytest.warns(FutureWarning, match="Pass fields"): + with pytest.warns(ZarrFutureWarning, match="Pass fields"): arr.set_basic_selection(..., 1, None) - with pytest.warns(FutureWarning, match="Pass out"): + with pytest.warns(ZarrFutureWarning, match="Pass out"): arr.get_orthogonal_selection(..., NDBuffer(array=np.empty((2, 2)))) - with pytest.warns(FutureWarning, match="Pass"): + with pytest.warns(ZarrFutureWarning, match="Pass"): arr.set_orthogonal_selection(..., 1, None) - with pytest.warns(FutureWarning, match="Pass"): + with pytest.warns(ZarrFutureWarning, match="Pass"): arr.get_mask_selection(np.zeros((2, 2), dtype=bool), NDBuffer(array=np.empty((0,)))) - with pytest.warns(FutureWarning, match="Pass"): + with pytest.warns(ZarrFutureWarning, match="Pass"): arr.set_mask_selection(np.zeros((2, 2), dtype=bool), 1, None) - with pytest.warns(FutureWarning, match="Pass"): + with pytest.warns(ZarrFutureWarning, match="Pass"): arr.get_coordinate_selection(([0, 1], [0, 1]), NDBuffer(array=np.empty((2,)))) - with pytest.warns(FutureWarning, match="Pass"): + with pytest.warns(ZarrFutureWarning, match="Pass"): arr.set_coordinate_selection(([0, 1], [0, 1]), 1, None) - with pytest.warns(FutureWarning, match="Pass"): + with pytest.warns(ZarrFutureWarning, match="Pass"): arr.get_block_selection((0, slice(None)), NDBuffer(array=np.empty((2, 2)))) - with pytest.warns(FutureWarning, match="Pass"): + with pytest.warns(ZarrFutureWarning, match="Pass"): arr.set_block_selection((0, slice(None)), 1, None) diff --git a/tests/test_group.py b/tests/test_group.py index b4dace2568..2f447b718f 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -37,7 +37,13 @@ ) from zarr.core.metadata.v3 import ArrayV3Metadata from zarr.core.sync import _collect_aiterator, sync -from zarr.errors import ContainsArrayError, ContainsGroupError, MetadataValidationError +from zarr.errors import ( + ContainsArrayError, + ContainsGroupError, + MetadataValidationError, + ZarrDeprecationWarning, + ZarrFutureWarning, +) from zarr.storage import LocalStore, MemoryStore, StorePath, ZipStore from zarr.storage._common import make_store_path from zarr.storage._utils import _join_paths, normalize_path @@ -646,7 +652,7 @@ def test_group_create_array( array = group.create_array(name=name, shape=shape, dtype=dtype) array[:] = data elif method == "array": - with pytest.warns(DeprecationWarning): + with pytest.warns(ZarrDeprecationWarning): array = group.array(name=name, data=data, shape=shape, dtype=dtype) else: raise AssertionError @@ -657,7 +663,7 @@ def test_group_create_array( a = group.create_array(name=name, shape=shape, dtype=dtype) a[:] = data elif method == "array": - with pytest.raises(ContainsArrayError), pytest.warns(DeprecationWarning): + with pytest.raises(ContainsArrayError), pytest.warns(ZarrDeprecationWarning): a = group.array(name=name, shape=shape, dtype=dtype) a[:] = data @@ -1181,22 +1187,22 @@ def test_create_dataset_with_data(store: Store, zarr_format: ZarrFormat) -> None """ root = Group.from_store(store=store, zarr_format=zarr_format) arr = np.random.random((5, 5)) - with pytest.warns(DeprecationWarning): + with pytest.warns(ZarrDeprecationWarning): data = root.create_dataset("random", data=arr, shape=arr.shape) np.testing.assert_array_equal(np.asarray(data), arr) async def test_create_dataset(store: Store, zarr_format: ZarrFormat) -> None: root = await AsyncGroup.from_store(store=store, zarr_format=zarr_format) - with pytest.warns(DeprecationWarning): + with pytest.warns(ZarrDeprecationWarning): foo = await root.create_dataset("foo", shape=(10,), dtype="uint8") assert foo.shape == (10,) - with pytest.raises(ContainsArrayError), pytest.warns(DeprecationWarning): + with pytest.raises(ContainsArrayError), pytest.warns(ZarrDeprecationWarning): await root.create_dataset("foo", shape=(100,), dtype="int8") _ = await root.create_group("bar") - with pytest.raises(ContainsGroupError), pytest.warns(DeprecationWarning): + with pytest.raises(ContainsGroupError), pytest.warns(ZarrDeprecationWarning): await root.create_dataset("bar", shape=(100,), dtype="int8") @@ -1452,14 +1458,14 @@ def test_group_deprecated_positional_args(method: str) -> None: kwargs = {} root = zarr.group() - with pytest.warns(FutureWarning, match=r"Pass name=.* as keyword args."): + with pytest.warns(ZarrFutureWarning, match=r"Pass name=.* as keyword args."): arr = getattr(root, method)("foo", shape=1, **kwargs) assert arr.shape == (1,) method += "_like" data = np.ones(1) - with pytest.warns(FutureWarning, match=r"Pass name=.*, data=.* as keyword args."): + with pytest.warns(ZarrFutureWarning, match=r"Pass name=.*, data=.* as keyword args."): arr = getattr(root, method)("foo_like", data, **kwargs) assert arr.shape == data.shape diff --git a/tests/test_sync.py b/tests/test_sync.py index 13b475f8da..0061d41727 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -15,6 +15,7 @@ loop, sync, ) +from zarr.errors import ZarrFutureWarning from zarr.storage import MemoryStore @@ -145,7 +146,7 @@ def bar(self) -> list[int]: def test_open_positional_args_deprecate(): store = MemoryStore() - with pytest.warns(FutureWarning, match="pass"): + with pytest.warns(ZarrFutureWarning, match="pass"): zarr.open(store, "w", shape=(1,)) diff --git a/tests/test_v2.py b/tests/test_v2.py index 8f0e1b2d29..465628cef7 100644 --- a/tests/test_v2.py +++ b/tests/test_v2.py @@ -17,6 +17,7 @@ from zarr.core.buffer.core import default_buffer_prototype from zarr.core.metadata.v2 import _parse_structured_fill_value from zarr.core.sync import sync +from zarr.errors import ZarrDeprecationWarning from zarr.storage import MemoryStore, StorePath @@ -259,7 +260,7 @@ def test_v2_non_contiguous(numpy_order: Literal["C", "F"], zarr_order: Literal[" def test_default_compressor_deprecation_warning(): - with pytest.warns(DeprecationWarning, match="default_compressor is deprecated"): + with pytest.warns(ZarrDeprecationWarning, match="default_compressor is deprecated"): zarr.storage.default_compressor = "zarr.codecs.zstd.ZstdCodec()" From 7c644eb7bad9399ae60d72e724fc4d0b44d88cb6 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Mon, 30 Jun 2025 10:45:31 +0200 Subject: [PATCH 02/17] make unstablespecificationwarning an instance of zarrfuturewarning --- src/zarr/core/dtype/common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zarr/core/dtype/common.py b/src/zarr/core/dtype/common.py index 6f61b6775e..ed7cf453e8 100644 --- a/src/zarr/core/dtype/common.py +++ b/src/zarr/core/dtype/common.py @@ -14,6 +14,7 @@ ) from zarr.core.common import NamedConfig +from zarr.errors import ZarrFutureWarning EndiannessStr = Literal["little", "big"] ENDIANNESS_STR: Final = "little", "big" @@ -205,7 +206,7 @@ class HasObjectCodec: object_codec_id: ClassVar[str] -class UnstableSpecificationWarning(FutureWarning): ... +class UnstableSpecificationWarning(ZarrFutureWarning): ... def v3_unstable_dtype_warning(dtype: object) -> None: From c6040756f60ad695e9f5e9c95101232db1f2a983 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Mon, 30 Jun 2025 11:03:32 +0200 Subject: [PATCH 03/17] use pytest.warns instead of pytest.raises --- tests/test_dtype/test_npy/test_bytes.py | 2 +- tests/test_dtype/test_npy/test_string.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_dtype/test_npy/test_bytes.py b/tests/test_dtype/test_npy/test_bytes.py index 3f1ba9315e..1fa713a745 100644 --- a/tests/test_dtype/test_npy/test_bytes.py +++ b/tests/test_dtype/test_npy/test_bytes.py @@ -150,7 +150,7 @@ def test_unstable_dtype_warning( Test that we get a warning when serializing a dtype without a zarr v3 spec to json when zarr_format is 3 """ - with pytest.raises(UnstableSpecificationWarning): + with pytest.warns(UnstableSpecificationWarning): zdtype.to_json(zarr_format=3) diff --git a/tests/test_dtype/test_npy/test_string.py b/tests/test_dtype/test_npy/test_string.py index 7c3c6a8cd4..a470cb4d12 100644 --- a/tests/test_dtype/test_npy/test_string.py +++ b/tests/test_dtype/test_npy/test_string.py @@ -131,7 +131,7 @@ def test_unstable_dtype_warning(zdtype: FixedLengthUTF32 | VariableLengthUTF8) - Test that we get a warning when serializing a dtype without a zarr v3 spec to json when zarr_format is 3 """ - with pytest.raises(UnstableSpecificationWarning): + with pytest.warns(UnstableSpecificationWarning): zdtype.to_json(zarr_format=3) From 64e14059c314e4e17e9263e4aace4f2e4238e5a5 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Thu, 31 Jul 2025 23:58:39 +0200 Subject: [PATCH 04/17] changelog --- changes/3098.misc.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/3098.misc.rst diff --git a/changes/3098.misc.rst b/changes/3098.misc.rst new file mode 100644 index 0000000000..0f9684c14e --- /dev/null +++ b/changes/3098.misc.rst @@ -0,0 +1,2 @@ +Define Zarr-specific warning classes ``ZarrDeprecationWarning`` and ``ZarrFutureWarning``, that +subclass ``DeprecationWarning`` and ``FutureWarning``, respectively. \ No newline at end of file From d322cad3b4c68aa0e600911c55c2c9d13670d119 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 10:56:40 +0200 Subject: [PATCH 05/17] ensure that all deprecations are ZarrDeprecations --- src/zarr/core/array.py | 4 ++-- src/zarr/core/group.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py index 99b406400d..44567adf2e 100644 --- a/src/zarr/core/array.py +++ b/src/zarr/core/array.py @@ -441,7 +441,7 @@ async def create( ) -> AsyncArray[ArrayV3Metadata] | AsyncArray[ArrayV2Metadata]: ... @classmethod - @deprecated("Use zarr.api.asynchronous.create_array instead.") + @deprecated("Use zarr.api.asynchronous.create_array instead.", category=ZarrDeprecationWarning) async def create( cls, store: StoreLike, @@ -1855,7 +1855,7 @@ class Array: _async_array: AsyncArray[ArrayV3Metadata] | AsyncArray[ArrayV2Metadata] @classmethod - @deprecated("Use zarr.create_array instead.") + @deprecated("Use zarr.create_array instead.", category=ZarrDeprecationWarning) def create( cls, store: StoreLike, diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index a203c790a3..a8511482d5 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -2828,7 +2828,7 @@ def move(self, source: str, dest: str) -> None: """ return self._sync(self._async_group.move(source, dest)) - @deprecated("Use Group.create_array instead.") + @deprecated("Use Group.create_array instead.", category=ZarrDeprecationWarning) def array( self, name: str, From 31e63aa59dc9cc72cf1a2cd86a4ae43abd6a151b Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 11:14:36 +0200 Subject: [PATCH 06/17] add docstrings and export warnings --- src/zarr/errors.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/zarr/errors.py b/src/zarr/errors.py index fea18e7720..39cd94f3a9 100644 --- a/src/zarr/errors.py +++ b/src/zarr/errors.py @@ -8,6 +8,8 @@ "GroupNotFoundError", "MetadataValidationError", "NodeTypeValidationError", + "ZarrDeprecationWarning", + "ZarrFutureWarning", ] @@ -61,14 +63,20 @@ class MetadataValidationError(BaseZarrError): class NodeTypeValidationError(MetadataValidationError): """ - Specialized exception when the node_type of the metadata document is incorrect.. + Specialized exception when the node_type of the metadata document is incorrect. This can be raised when the value is invalid or unexpected given the context, for example an 'array' node when we expected a 'group'. """ -class ZarrFutureWarning(FutureWarning): ... +class ZarrFutureWarning(FutureWarning): + """ + A warning raised to indicate when a construct will change semantically in the future. + """ -class ZarrDeprecationWarning(DeprecationWarning): ... +class ZarrDeprecationWarning(DeprecationWarning): + """ + A warning raised to indicate that a construct will be removed in a future release. + """ From 917fd3d70bcdd2edc90f690aa8eb9e55f692c64a Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 11:44:36 +0200 Subject: [PATCH 07/17] fix imports --- tests/test_api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 8eb702b589..698fdab2e7 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -7,9 +7,8 @@ import zarr.codecs import zarr.storage from zarr.core.array import init_array +from zarr.storage import LocalStore, ZipStore from zarr.storage._common import StorePath -from zarr.storage._local import LocalStore -from zarr.storage._zip import ZipStore if TYPE_CHECKING: from collections.abc import Callable From 9055d3b2e15bc2d5e8511199a0c73a78eebcc00b Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 14:51:35 +0200 Subject: [PATCH 08/17] handle warnings in tests explicitly; make userwarnings ZarrUserWarning --- pyproject.toml | 10 -- src/zarr/api/asynchronous.py | 42 +++--- src/zarr/core/array.py | 8 +- src/zarr/core/buffer/gpu.py | 2 + src/zarr/core/chunk_grids.py | 3 +- src/zarr/core/codec_pipeline.py | 2 + src/zarr/core/common.py | 5 +- src/zarr/core/group.py | 5 +- src/zarr/core/metadata/v2.py | 3 +- src/zarr/errors.py | 12 ++ src/zarr/registry.py | 2 + src/zarr/storage/_fsspec.py | 4 +- src/zarr/testing/__init__.py | 6 +- tests/test_api.py | 6 +- tests/test_array.py | 45 +++--- tests/test_codecs/test_codecs.py | 107 ++++---------- tests/test_codecs/test_sharding.py | 31 ++-- tests/test_config.py | 5 +- tests/test_group.py | 180 ++++++++++++++++++++--- tests/test_metadata/test_consolidated.py | 54 ++++++- tests/test_metadata/test_v2.py | 3 +- tests/test_store/test_fsspec.py | 3 +- 22 files changed, 348 insertions(+), 190 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c0daacf5a8..73d032c17c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -403,16 +403,6 @@ addopts = [ ] filterwarnings = [ "error", - # TODO: explicitly filter or catch the warnings below where we expect them to be emitted in the tests - "ignore:Consolidated metadata is currently not part in the Zarr format 3 specification.*:UserWarning", - "ignore:Creating a zarr.buffer.gpu.Buffer with an array that does not support the __cuda_array_interface__.*:UserWarning", - "ignore:Automatic shard shape inference is experimental and may change without notice.*:UserWarning", - "ignore:The codec .* is currently not part in the Zarr format 3 specification.*:UserWarning", - "ignore:The dtype .* is currently not part in the Zarr format 3 specification.*:UserWarning", - "ignore:Use zarr.create_array instead.:zarr.errors.ZarrDeprecationWarning", - "ignore:Duplicate name.*:UserWarning", - "ignore:The `compressor` argument is deprecated. Use `compressors` instead.:UserWarning", - "ignore:Numcodecs codecs are not in the Zarr version 3 specification and may not be supported by other zarr implementations.:UserWarning", "ignore:Unclosed client session None: ) warnings.warn( msg, + category=ZarrUserWarning, stacklevel=2, ) self._data = cp.asarray(array_like) diff --git a/src/zarr/core/chunk_grids.py b/src/zarr/core/chunk_grids.py index 6a3d6816a6..7fa1fc7e38 100644 --- a/src/zarr/core/chunk_grids.py +++ b/src/zarr/core/chunk_grids.py @@ -22,6 +22,7 @@ parse_named_configuration, parse_shapelike, ) +from zarr.errors import ZarrUserWarning if TYPE_CHECKING: from collections.abc import Iterator @@ -233,7 +234,7 @@ def _auto_partition( if shard_shape == "auto": warnings.warn( "Automatic shard shape inference is experimental and may change without notice.", - UserWarning, + ZarrUserWarning, stacklevel=2, ) _shards_out = () diff --git a/src/zarr/core/codec_pipeline.py b/src/zarr/core/codec_pipeline.py index 23c27e40c6..3bc3c1cfc7 100644 --- a/src/zarr/core/codec_pipeline.py +++ b/src/zarr/core/codec_pipeline.py @@ -17,6 +17,7 @@ from zarr.core.common import ChunkCoords, concurrent_map from zarr.core.config import config from zarr.core.indexing import SelectorTuple, is_scalar +from zarr.errors import ZarrUserWarning from zarr.registry import register_pipeline if TYPE_CHECKING: @@ -501,6 +502,7 @@ def codecs_from_list( warn( "Combining a `sharding_indexed` codec disables partial reads and " "writes, which may lead to inefficient performance.", + category=ZarrUserWarning, stacklevel=3, ) diff --git a/src/zarr/core/common.py b/src/zarr/core/common.py index 33590c83a5..4c0247426e 100644 --- a/src/zarr/core/common.py +++ b/src/zarr/core/common.py @@ -23,6 +23,7 @@ from typing_extensions import ReadOnly from zarr.core.config import config as zarr_config +from zarr.errors import ZarrRuntimeWarning if TYPE_CHECKING: from collections.abc import Awaitable, Callable, Iterator @@ -205,7 +206,7 @@ def _warn_write_empty_chunks_kwarg() -> None: "argument, as in `config={'write_empty_chunks': True}`," "or change the global 'array.write_empty_chunks' configuration variable." ) - warnings.warn(msg, RuntimeWarning, stacklevel=2) + warnings.warn(msg, ZarrRuntimeWarning, stacklevel=2) def _warn_order_kwarg() -> None: @@ -216,7 +217,7 @@ def _warn_order_kwarg() -> None: "argument, as in `config={'order': 'C'}`," "or change the global 'array.order' configuration variable." ) - warnings.warn(msg, RuntimeWarning, stacklevel=2) + warnings.warn(msg, ZarrRuntimeWarning, stacklevel=2) def _default_zarr_format() -> ZarrFormat: diff --git a/src/zarr/core/group.py b/src/zarr/core/group.py index a8511482d5..fe52297ecd 100644 --- a/src/zarr/core/group.py +++ b/src/zarr/core/group.py @@ -55,6 +55,7 @@ ContainsGroupError, MetadataValidationError, ZarrDeprecationWarning, + ZarrUserWarning, ) from zarr.storage import StoreLike, StorePath from zarr.storage._common import ensure_no_existing_node, make_store_path @@ -553,7 +554,7 @@ async def open( if zarr_json_bytes is not None and zgroup_bytes is not None: # warn and favor v3 msg = f"Both zarr.json (Zarr format 3) and .zgroup (Zarr format 2) metadata objects exist at {store_path}. Zarr format 3 will be used." - warnings.warn(msg, stacklevel=1) + warnings.warn(msg, category=ZarrUserWarning, stacklevel=1) if zarr_json_bytes is None and zgroup_bytes is None: raise FileNotFoundError( f"could not find zarr.json or .zgroup objects in {store_path}" @@ -3380,7 +3381,7 @@ async def _iter_members( # in which case `key` cannot be the name of a sub-array or sub-group. warnings.warn( f"Object at {e.args[0]} is not recognized as a component of a Zarr hierarchy.", - UserWarning, + ZarrUserWarning, stacklevel=1, ) continue diff --git a/src/zarr/core/metadata/v2.py b/src/zarr/core/metadata/v2.py index 17af3538a9..9ad6b3bc42 100644 --- a/src/zarr/core/metadata/v2.py +++ b/src/zarr/core/metadata/v2.py @@ -11,6 +11,7 @@ from zarr.core.chunk_grids import RegularChunkGrid from zarr.core.dtype import get_data_type_from_json from zarr.core.dtype.common import OBJECT_CODEC_IDS, DTypeSpec_V2 +from zarr.errors import ZarrUserWarning if TYPE_CHECKING: from typing import Literal, Self @@ -188,7 +189,7 @@ def from_dict(cls, data: dict[str, Any]) -> ArrayV2Metadata: "This is contrary to the Zarr V2 specification, and will cause an error in the future. " "Use None (or Null in a JSON document) instead of an empty list of filters." ) - warnings.warn(msg, UserWarning, stacklevel=1) + warnings.warn(msg, ZarrUserWarning, stacklevel=1) _data["filters"] = None _data = {k: v for k, v in _data.items() if k in expected} diff --git a/src/zarr/errors.py b/src/zarr/errors.py index 39cd94f3a9..6be52240b3 100644 --- a/src/zarr/errors.py +++ b/src/zarr/errors.py @@ -80,3 +80,15 @@ class ZarrDeprecationWarning(DeprecationWarning): """ A warning raised to indicate that a construct will be removed in a future release. """ + + +class ZarrUserWarning(UserWarning): + """ + A warning raised to report problems with user code. + """ + + +class ZarrRuntimeWarning(RuntimeWarning): + """ + A warning for dubious runtime behavior. + """ diff --git a/src/zarr/registry.py b/src/zarr/registry.py index 189d42abed..fc3ffd7f7c 100644 --- a/src/zarr/registry.py +++ b/src/zarr/registry.py @@ -7,6 +7,7 @@ from zarr.core.config import BadConfigError, config from zarr.core.dtype import data_type_registry +from zarr.errors import ZarrUserWarning if TYPE_CHECKING: from importlib.metadata import EntryPoint @@ -160,6 +161,7 @@ def get_codec_class(key: str, reload_config: bool = False) -> type[Codec]: warnings.warn( f"Codec '{key}' not configured in config. Selecting any implementation.", stacklevel=2, + category=ZarrUserWarning, ) return list(codec_classes.values())[-1] selected_codec_cls = codec_classes[config_entry] diff --git a/src/zarr/storage/_fsspec.py b/src/zarr/storage/_fsspec.py index e169eededc..bbb934cc7d 100644 --- a/src/zarr/storage/_fsspec.py +++ b/src/zarr/storage/_fsspec.py @@ -15,6 +15,7 @@ SuffixByteRequest, ) from zarr.core.buffer import Buffer +from zarr.errors import ZarrUserWarning from zarr.storage._common import _dereference_path if TYPE_CHECKING: @@ -101,7 +102,7 @@ class FsspecStore(Store): Warns ----- - UserWarning + ZarrUserWarning If the file system (fs) was not created with `asynchronous=True`. See Also @@ -137,6 +138,7 @@ def __init__( if not self.fs.asynchronous: warnings.warn( f"fs ({fs}) was not created with `asynchronous=True`, this may lead to surprising behavior", + category=ZarrUserWarning, stacklevel=2, ) if "://" in path and not path.startswith("http"): diff --git a/src/zarr/testing/__init__.py b/src/zarr/testing/__init__.py index 0b4d8cf417..21a3572846 100644 --- a/src/zarr/testing/__init__.py +++ b/src/zarr/testing/__init__.py @@ -1,10 +1,14 @@ import importlib.util import warnings +from zarr.errors import ZarrUserWarning + if importlib.util.find_spec("pytest") is not None: from zarr.testing.store import StoreTests else: - warnings.warn("pytest not installed, skipping test suite", stacklevel=2) + warnings.warn( + "pytest not installed, skipping test suite", category=ZarrUserWarning, stacklevel=2 + ) from zarr.testing.utils import assert_bytes_equal diff --git a/tests/test_api.py b/tests/test_api.py index 698fdab2e7..12acf80589 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -42,7 +42,7 @@ save_group, ) from zarr.core.buffer import NDArrayLike -from zarr.errors import MetadataValidationError, ZarrDeprecationWarning +from zarr.errors import MetadataValidationError, ZarrDeprecationWarning, ZarrUserWarning from zarr.storage import MemoryStore from zarr.storage._utils import normalize_path from zarr.testing.utils import gpu_test @@ -170,7 +170,7 @@ def test_v2_and_v3_exist_at_same_path(store: Store) -> None: zarr.create_array(store, shape=(10,), dtype="uint8", zarr_format=3) zarr.create_array(store, shape=(10,), dtype="uint8", zarr_format=2) msg = f"Both zarr.json (Zarr format 3) and .zarray (Zarr format 2) metadata objects exist at {store}. Zarr v3 will be used." - with pytest.warns(UserWarning, match=re.escape(msg)): + with pytest.warns(ZarrUserWarning, match=re.escape(msg)): zarr.open(store=store) @@ -1351,7 +1351,7 @@ def test_no_overwrite_open(tmp_path: Path, open_func: Callable, mode: str) -> No existing_fpath = add_empty_file(tmp_path) assert existing_fpath.exists() - with contextlib.suppress(FileExistsError, FileNotFoundError, UserWarning): + with contextlib.suppress(FileExistsError, FileNotFoundError, ZarrUserWarning): open_func(store=store, mode=mode) if mode == "w": assert not existing_fpath.exists() diff --git a/tests/test_array.py b/tests/test_array.py index b112408825..46b78de7bf 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -66,6 +66,7 @@ from zarr.errors import ( ContainsArrayError, ContainsGroupError, + ZarrUserWarning, ) from zarr.storage import LocalStore, MemoryStore, StorePath @@ -925,13 +926,16 @@ def test_auto_partition_auto_shards( expected_shards += (2 * cs,) else: expected_shards += (cs,) - - auto_shards, _ = _auto_partition( - array_shape=array_shape, - chunk_shape=chunk_shape, - shard_shape="auto", - item_size=dtype.itemsize, - ) + with pytest.warns( + ZarrUserWarning, + match="Automatic shard shape inference is experimental and may change without notice.", + ): + auto_shards, _ = _auto_partition( + array_shape=array_shape, + chunk_shape=chunk_shape, + shard_shape="auto", + item_size=dtype.itemsize, + ) assert auto_shards == expected_shards @@ -1634,7 +1638,7 @@ async def test_from_array_arraylike( def test_from_array_F_order() -> None: arr = zarr.create_array(store={}, data=np.array([1]), order="F", zarr_format=2) with pytest.warns( - UserWarning, + ZarrUserWarning, match="The existing order='F' of the source Zarr format 2 array will be ignored.", ): zarr.from_array(store={}, data=arr, zarr_format=3) @@ -1682,21 +1686,24 @@ def test_roundtrip_numcodecs() -> None: # Create the array with the correct codecs root = zarr.group(store) - root.create_array( - "test", - shape=(720, 1440), - chunks=(720, 1440), - dtype="float64", - compressors=compressors, - filters=filters, - fill_value=-9.99, - dimension_names=["lat", "lon"], - ) + warn_msg = "Numcodecs codecs are not in the Zarr version 3 specification and may not be supported by other zarr implementations." + with pytest.warns(UserWarning, match=warn_msg): + root.create_array( + "test", + shape=(720, 1440), + chunks=(720, 1440), + dtype="float64", + compressors=compressors, + filters=filters, + fill_value=-9.99, + dimension_names=["lat", "lon"], + ) BYTES_CODEC = {"name": "bytes", "configuration": {"endian": "little"}} # Read in the array again and check compressor config root = zarr.open_group(store) - metadata = root["test"].metadata.to_dict() + with pytest.warns(UserWarning, match=warn_msg): + metadata = root["test"].metadata.to_dict() expected = (*filters, BYTES_CODEC, *compressors) assert metadata["codecs"] == expected diff --git a/tests/test_codecs/test_codecs.py b/tests/test_codecs/test_codecs.py index d52a9e1c44..ac24e7149a 100644 --- a/tests/test_codecs/test_codecs.py +++ b/tests/test_codecs/test_codecs.py @@ -20,9 +20,12 @@ from zarr.core.buffer import default_buffer_prototype from zarr.core.indexing import BasicSelection, morton_order_iter from zarr.core.metadata.v3 import ArrayV3Metadata +from zarr.dtype import UInt8 +from zarr.errors import ZarrUserWarning from zarr.storage import StorePath if TYPE_CHECKING: + from zarr.abc.codec import Codec from zarr.abc.store import Store from zarr.core.buffer.core import NDArrayLikeOrScalar from zarr.core.common import ChunkCoords, MemoryOrder @@ -290,97 +293,37 @@ async def test_dimension_names(store: Store) -> None: assert "dimension_names" not in json.loads(zarr_json_buffer.to_bytes()) -@pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) -def test_invalid_metadata(store: Store) -> None: - spath2 = StorePath(store, "invalid_codec_order") - with pytest.raises(TypeError): - Array.create( - spath2, - shape=(16, 16), - chunk_shape=(16, 16), - dtype=np.dtype("uint8"), - fill_value=0, - codecs=[ - BytesCodec(), - TransposeCodec(order=order_from_dim("F", 2)), - ], - ) - spath3 = StorePath(store, "invalid_order") - with pytest.raises(TypeError): - Array.create( - spath3, - shape=(16, 16), - chunk_shape=(16, 16), - dtype=np.dtype("uint8"), - fill_value=0, - codecs=[ - TransposeCodec(order="F"), # type: ignore[arg-type] - BytesCodec(), - ], - ) - spath4 = StorePath(store, "invalid_missing_bytes_codec") - with pytest.raises(ValueError): - Array.create( - spath4, - shape=(16, 16), - chunk_shape=(16, 16), - dtype=np.dtype("uint8"), - fill_value=0, - codecs=[ - TransposeCodec(order=order_from_dim("F", 2)), - ], - ) - spath5 = StorePath(store, "invalid_inner_chunk_shape") - with pytest.raises(ValueError): - Array.create( - spath5, - shape=(16, 16), - chunk_shape=(16, 16), - dtype=np.dtype("uint8"), - fill_value=0, - codecs=[ - ShardingCodec(chunk_shape=(8,)), - ], - ) - spath6 = StorePath(store, "invalid_inner_chunk_shape") +@pytest.mark.parametrize( + "codecs", + [ + (BytesCodec(), TransposeCodec(order=order_from_dim("F", 2))), + (TransposeCodec(order=order_from_dim("F", 2)),), + ], +) +def test_invalid_metadata(codecs: tuple[Codec, ...]) -> None: + shape = (16,) + chunks = (16,) + data_type = UInt8() with pytest.raises(ValueError): - Array.create( - spath6, - shape=(16, 16), - chunk_shape=(16, 16), - dtype=np.dtype("uint8"), - fill_value=0, - codecs=[ - ShardingCodec(chunk_shape=(8, 7)), - ], - ) - spath7 = StorePath(store, "warning_inefficient_codecs") - with pytest.warns( - UserWarning, - match="Combining a `sharding_indexed` codec disables partial reads and writes, which may lead to inefficient performance", - ): - Array.create( - spath7, - shape=(16, 16), - chunk_shape=(16, 16), - dtype=np.dtype("uint8"), + ArrayV3Metadata( + shape=shape, + chunk_grid={"name": "regular", "configuration": {"chunk_shape": chunks}}, + chunk_key_encoding={"name": "default", "configuration": {"separator": "/"}}, fill_value=0, - codecs=[ - ShardingCodec(chunk_shape=(8, 8)), - GzipCodec(), - ], + data_type=data_type, + codecs=codecs, + attributes={}, + dimension_names=None, ) -@pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) -def test_invalid_metadata_create_array(store: Store) -> None: - spath = StorePath(store, "warning_inefficient_codecs") +def test_invalid_metadata_create_array() -> None: with pytest.warns( - UserWarning, + ZarrUserWarning, match="codec disables partial reads and writes, which may lead to inefficient performance", ): zarr.create_array( - spath, + {}, shape=(16, 16), chunks=(16, 16), dtype=np.dtype("uint8"), diff --git a/tests/test_codecs/test_sharding.py b/tests/test_codecs/test_sharding.py index 403fd80e81..eb80545ff3 100644 --- a/tests/test_codecs/test_sharding.py +++ b/tests/test_codecs/test_sharding.py @@ -17,7 +17,8 @@ TransposeCodec, ) from zarr.core.buffer import NDArrayLike, default_buffer_prototype -from zarr.storage import StorePath +from zarr.errors import ZarrUserWarning +from zarr.storage import StorePath, ZipStore from ..conftest import ArrayRequest from .test_codecs import _AsyncArrayProxy, order_from_dim @@ -228,7 +229,11 @@ def test_sharding_partial_overwrite( assert np.array_equal(data, read_data) data += 10 - a[:10, :10, :10] = data + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name: "): + a[:10, :10, :10] = data + else: + a[:10, :10, :10] = data read_data = a[0:10, 0:10, 0:10] assert np.array_equal(data, read_data) @@ -257,22 +262,22 @@ def test_nested_sharding( ) -> None: data = array_fixture spath = StorePath(store) - a = Array.create( - spath, - shape=data.shape, - chunk_shape=(64, 64, 64), - dtype=data.dtype, - fill_value=0, - codecs=[ - ShardingCodec( + msg = "Combining a `sharding_indexed` codec disables partial reads and writes, which may lead to inefficient performance." + with pytest.warns(ZarrUserWarning, match=msg): + a = zarr.create_array( + spath, + shape=data.shape, + chunks=(64, 64, 64), + dtype=data.dtype, + fill_value=0, + serializer=ShardingCodec( chunk_shape=(32, 32, 32), codecs=[ ShardingCodec(chunk_shape=(16, 16, 16), index_location=inner_index_location) ], index_location=outer_index_location, - ) - ], - ) + ), + ) a[:, :, :] = data diff --git a/tests/test_config.py b/tests/test_config.py index da5b2cc488..0c029dda3a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -24,6 +24,7 @@ from zarr.core.codec_pipeline import BatchedCodecPipeline from zarr.core.config import BadConfigError, config from zarr.core.indexing import SelectorTuple +from zarr.errors import ZarrUserWarning from zarr.registry import ( fully_qualified_name, get_buffer_class, @@ -287,7 +288,9 @@ class NewCodec2(BytesCodec): # warning because multiple implementations are available but none is selected in the config register_codec("new_codec", NewCodec2) - with pytest.warns(UserWarning, match="not configured in config. Selecting any implementation"): + with pytest.warns( + ZarrUserWarning, match="not configured in config. Selecting any implementation" + ): get_codec_class("new_codec") # no warning if multiple implementations are available and one is selected in the config diff --git a/tests/test_group.py b/tests/test_group.py index d6fc7f8996..2e3993ef6f 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -44,6 +44,7 @@ ContainsGroupError, MetadataValidationError, ZarrDeprecationWarning, + ZarrUserWarning, ) from zarr.storage import LocalStore, MemoryStore, StorePath, ZipStore from zarr.storage._common import make_store_path @@ -208,11 +209,17 @@ def test_group_members(store: Store, zarr_format: ZarrFormat, consolidated_metad # this warning shows up when extra objects show up in the hierarchy warn_context = pytest.warns( - UserWarning, match=r"Object at .* is not recognized as a component of a Zarr hierarchy." + ZarrUserWarning, + match=r"(?:Object at .* is not recognized as a component of a Zarr hierarchy.)|(?:Consolidated metadata is currently not part in the Zarr format 3 specification.)", ) if consolidated_metadata: - with warn_context: - zarr.consolidate_metadata(store=store, zarr_format=zarr_format) + if isinstance(store, ZipStore): + with warn_context: + with pytest.warns(UserWarning, match="Duplicate name: "): + zarr.consolidate_metadata(store=store, zarr_format=zarr_format) + else: + with warn_context: + zarr.consolidate_metadata(store=store, zarr_format=zarr_format) # now that we've consolidated the store, we shouldn't get the warnings from the unrecognized objects anymore # we use a nullcontext to handle these cases warn_context = contextlib.nullcontext() @@ -272,7 +279,11 @@ def test_group(store: Store, zarr_format: ZarrFormat) -> None: assert dict(bar2.attrs) == {"baz": "qux"} # update a group's attributes - bar2.attrs.update({"name": "bar"}) + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name: "): + bar2.attrs.update({"name": "bar"}) + else: + bar2.attrs.update({"name": "bar"}) # bar.attrs was modified in-place assert dict(bar2.attrs) == {"baz": "qux", "name": "bar"} @@ -345,7 +356,30 @@ def test_group_getitem(store: Store, zarr_format: ZarrFormat, consolidated: bool subsubarray = subgroup.create_array(name="subarray", shape=(10,), chunks=(10,), dtype="uint8") if consolidated: - group = zarr.api.synchronous.consolidate_metadata(store=store, zarr_format=zarr_format) + if zarr_format == 3: + with pytest.warns( # noqa: PT031 + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name: "): + group = zarr.api.synchronous.consolidate_metadata( + store=store, zarr_format=zarr_format + ) + else: + group = zarr.api.synchronous.consolidate_metadata( + store=store, zarr_format=zarr_format + ) + else: + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name: "): + group = zarr.api.synchronous.consolidate_metadata( + store=store, zarr_format=zarr_format + ) + else: + group = zarr.api.synchronous.consolidate_metadata( + store=store, zarr_format=zarr_format + ) # we're going to assume that `group.metadata` is correct, and reuse that to focus # on indexing in this test. Other tests verify the correctness of group.metadata object.__setattr__( @@ -403,8 +437,11 @@ def test_group_get_with_default(store: Store, zarr_format: ZarrFormat) -> None: # now with a group subgroup = group.require_group("subgroup") - subgroup.attrs["foo"] = "bar" - + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name: "): + subgroup.attrs["foo"] = "bar" + else: + subgroup.attrs["foo"] = "bar" result = group.get("subgroup", 8) assert result.attrs["foo"] == "bar" @@ -422,7 +459,22 @@ def test_group_delitem(store: Store, zarr_format: ZarrFormat, consolidated: bool subarray = group.create_array(name="subarray", shape=(10,), chunks=(10,), dtype="uint8") if consolidated: - group = zarr.api.synchronous.consolidate_metadata(store=store, zarr_format=zarr_format) + if zarr_format == 3: + with pytest.warns( # noqa: PT031 + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name: "): + group = zarr.api.synchronous.consolidate_metadata( + store=store, zarr_format=zarr_format + ) + else: + group = zarr.api.synchronous.consolidate_metadata( + store=store, zarr_format=zarr_format + ) + else: + group = zarr.api.synchronous.consolidate_metadata(store=store, zarr_format=zarr_format) object.__setattr__( subgroup.metadata, "consolidated_metadata", ConsolidatedMetadata(metadata={}) ) @@ -517,7 +569,22 @@ def test_group_child_iterators(store: Store, zarr_format: ZarrFormat, consolidat expected_arrays = list(zip(expected_array_keys, expected_array_values, strict=False)) if consolidate: - group = zarr.consolidate_metadata(store) + if zarr_format == 3: + with pytest.warns( # noqa: PT031 + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name: "): + group = zarr.consolidate_metadata(store) + else: + group = zarr.consolidate_metadata(store) + else: + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name: "): + group = zarr.consolidate_metadata(store) + else: + group = zarr.consolidate_metadata(store) if zarr_format == 2: metadata = { "subarray": { @@ -613,7 +680,11 @@ def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None: group = Group.from_store(store, zarr_format=zarr_format, attributes=attrs) assert group.attrs == attrs new_attrs = {"bar": 100} - new_group = group.update_attributes(new_attrs) + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name: "): + new_group = group.update_attributes(new_attrs) + else: + new_group = group.update_attributes(new_attrs) updated_attrs = attrs.copy() updated_attrs.update(new_attrs) @@ -628,7 +699,11 @@ async def test_group_update_attributes_async(store: Store, zarr_format: ZarrForm group = Group.from_store(store, zarr_format=zarr_format, attributes=attrs) assert group.attrs == attrs new_attrs = {"bar": 100} - new_group = await group.update_attributes_async(new_attrs) + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name: "): + new_group = await group.update_attributes_async(new_attrs) + else: + new_group = await group.update_attributes_async(new_attrs) assert new_group.attrs == new_attrs @@ -654,7 +729,11 @@ def test_group_create_array( array[:] = data elif method == "array": with pytest.warns(ZarrDeprecationWarning, match=r"Group\.create_array instead\."): - array = group.array(name=name, data=data, shape=shape, dtype=dtype) + with pytest.warns( + ZarrUserWarning, + match="The `compressor` argument is deprecated. Use `compressors` instead.", + ): + array = group.array(name=name, data=data, shape=shape, dtype=dtype) else: raise AssertionError @@ -666,7 +745,11 @@ def test_group_create_array( elif method == "array": with pytest.raises(ContainsArrayError): # noqa: PT012 with pytest.warns(ZarrDeprecationWarning, match=r"Group\.create_array instead\."): - a = group.array(name=name, shape=shape, dtype=dtype) + with pytest.warns( + ZarrUserWarning, + match="The `compressor` argument is deprecated. Use `compressors` instead.", + ): + a = group.array(name=name, shape=shape, dtype=dtype) a[:] = data assert array.path == normalize_path(name) @@ -1029,7 +1112,11 @@ async def test_asyncgroup_update_attributes(store: Store, zarr_format: ZarrForma store=store, zarr_format=zarr_format, attributes=attributes_old ) - agroup_new_attributes = await agroup.update_attributes(attributes_new) + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name"): + agroup_new_attributes = await agroup.update_attributes(attributes_new) + else: + agroup_new_attributes = await agroup.update_attributes(attributes_new) attributes_updated = attributes_old.copy() attributes_updated.update(attributes_new) assert agroup_new_attributes.attrs == attributes_updated @@ -1104,8 +1191,16 @@ async def test_group_members_async(store: Store, consolidated_metadata: bool) -> assert all_children == expected if consolidated_metadata: - await zarr.api.asynchronous.consolidate_metadata(store=store) - group = await zarr.api.asynchronous.open_group(store=store) + with pytest.warns( # noqa: PT031 + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name"): + await zarr.api.asynchronous.consolidate_metadata(store=store) + else: + await zarr.api.asynchronous.consolidate_metadata(store=store) + group = await zarr.api.asynchronous.open_group(store=store) nmembers = await group.nmembers(max_depth=None) assert nmembers == 6 @@ -1258,8 +1353,25 @@ async def test_members_name(store: Store, consolidate: bool, zarr_format: ZarrFo b.create_array("array", shape=(1,), dtype="uint8") if consolidate: - group = zarr.api.synchronous.consolidate_metadata(store) - + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name"): # noqa: PT031 + if zarr_format == 3: + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + group = zarr.api.synchronous.consolidate_metadata(store) + else: + group = zarr.api.synchronous.consolidate_metadata(store) + else: + if zarr_format == 3: + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + group = zarr.api.synchronous.consolidate_metadata(store) + else: + group = zarr.api.synchronous.consolidate_metadata(store) result = group["a"]["b"] assert result.name == "/a/b" @@ -1308,7 +1420,15 @@ async def test_group_getitem_consolidated(self, store: Store) -> None: x1 = await x0.create_group("x1") await x1.create_group("x2") - await zarr.api.asynchronous.consolidate_metadata(store) + with pytest.warns( # noqa: PT031 + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name"): + await zarr.api.asynchronous.consolidate_metadata(store) + else: + await zarr.api.asynchronous.consolidate_metadata(store) # On disk, we've consolidated all the metadata in the root zarr.json group = await zarr.api.asynchronous.open(store=store) @@ -1365,7 +1485,15 @@ async def test_group_delitem_consolidated(self, store: Store) -> None: x2 = await x1.create_group("x2") await x2.create_array("data", shape=(1,), dtype="uint8") - await zarr.api.asynchronous.consolidate_metadata(store) + with pytest.warns( # noqa: PT031 + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + if isinstance(store, ZipStore): + with pytest.warns(UserWarning, match="Duplicate name"): + await zarr.api.asynchronous.consolidate_metadata(store) + else: + await zarr.api.asynchronous.consolidate_metadata(store) group = await zarr.api.asynchronous.open_consolidated(store=store) assert len(group.metadata.consolidated_metadata.metadata) == 2 @@ -1389,7 +1517,11 @@ def test_open_consolidated_raises(self, store: Store) -> None: # Now create consolidated metadata... root.create_group("g0") - zarr.consolidate_metadata(store) + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + zarr.consolidate_metadata(store) # and explicitly ignore it. group = zarr.open_group(store=store, use_consolidated=False) @@ -1409,7 +1541,11 @@ async def test_open_consolidated_raises_async(self, store: Store) -> None: # Now create consolidated metadata... await root.create_group("g0") - await zarr.api.asynchronous.consolidate_metadata(store) + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + await zarr.api.asynchronous.consolidate_metadata(store) # and explicitly ignore it. group = await zarr.api.asynchronous.open_group(store=store, use_consolidated=False) diff --git a/tests/test_metadata/test_consolidated.py b/tests/test_metadata/test_consolidated.py index ea2f834bb6..c6bc5af2b4 100644 --- a/tests/test_metadata/test_consolidated.py +++ b/tests/test_metadata/test_consolidated.py @@ -22,6 +22,7 @@ from zarr.core.group import ConsolidatedMetadata, GroupMetadata from zarr.core.metadata import ArrayV3Metadata from zarr.core.metadata.v2 import ArrayV2Metadata +from zarr.errors import ZarrUserWarning from zarr.storage import StorePath if TYPE_CHECKING: @@ -67,7 +68,11 @@ async def test_consolidated(self, memory_store_with_hierarchy: Store) -> None: # arrays under arrays # single array # etc. - await consolidate_metadata(memory_store_with_hierarchy) + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + await consolidate_metadata(memory_store_with_hierarchy) group2 = await AsyncGroup.open(memory_store_with_hierarchy) array_metadata = { @@ -215,7 +220,11 @@ def test_consolidated_sync(self, memory_store): g.create_array(name="lon", shape=(2,), dtype=dtype) g.create_array(name="time", shape=(3,), dtype=dtype) - zarr.api.synchronous.consolidate_metadata(memory_store) + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + zarr.api.synchronous.consolidate_metadata(memory_store) group2 = zarr.api.synchronous.Group.open(memory_store) array_metadata = { @@ -298,7 +307,11 @@ async def test_not_writable_raises(self, memory_store: zarr.storage.MemoryStore) await consolidate_metadata(read_store) async def test_non_root_node(self, memory_store_with_hierarchy: Store) -> None: - await consolidate_metadata(memory_store_with_hierarchy, path="child") + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + await consolidate_metadata(memory_store_with_hierarchy, path="child") root = await AsyncGroup.open(memory_store_with_hierarchy) child = await AsyncGroup.open(StorePath(memory_store_with_hierarchy) / "child") @@ -553,7 +566,14 @@ async def test_use_consolidated_false( await g.create_group(name="a") # test a stale read - await zarr.api.asynchronous.consolidate_metadata(memory_store) + if zarr_format == 3: + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + await zarr.api.asynchronous.consolidate_metadata(memory_store) + else: + await zarr.api.asynchronous.consolidate_metadata(memory_store) await g.create_group(name="b") stale = await zarr.api.asynchronous.open_group(store=memory_store) @@ -568,7 +588,14 @@ async def test_use_consolidated_false( assert len([x async for x in good.members()]) == 2 # reconsolidate - await zarr.api.asynchronous.consolidate_metadata(memory_store) + if zarr_format == 3: + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + await zarr.api.asynchronous.consolidate_metadata(memory_store) + else: + await zarr.api.asynchronous.consolidate_metadata(memory_store) good = await zarr.api.asynchronous.open_group(store=memory_store) assert len([x async for x in good.members()]) == 2 @@ -584,7 +611,11 @@ async def test_stale_child_metadata_ignored(self, memory_store: zarr.storage.Mem await zarr.api.asynchronous.consolidate_metadata(memory_store, path="foo") await root.create_group("foo/bar/spam") - await zarr.api.asynchronous.consolidate_metadata(memory_store) + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + await zarr.api.asynchronous.consolidate_metadata(memory_store) reopened = await zarr.api.asynchronous.open_consolidated(store=memory_store, zarr_format=3) result = [x[0] async for x in reopened.members(max_depth=None)] @@ -608,7 +639,7 @@ async def test_use_consolidated_for_children_members( # Now according to the consolidated metadata, "a" has children ["b"] # but according to the unconsolidated metadata, "a" has children ["b", "c"] group = await zarr.api.asynchronous.open_group(store=memory_store, path="a") - with pytest.warns(UserWarning, match="Object at 'c' not found"): + with pytest.warns(ZarrUserWarning, match="Object at 'c' not found"): result = sorted([x[0] async for x in group.members(max_depth=None)]) expected = ["b"] assert result == expected @@ -626,7 +657,14 @@ async def test_consolidated_metadata_encodes_special_chars( ): root = await group(store=memory_store, zarr_format=zarr_format) _time = await root.create_array("time", shape=(12,), dtype=np.float64, fill_value=fill_value) - await zarr.api.asynchronous.consolidate_metadata(memory_store) + if zarr_format == 3: + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + await zarr.api.asynchronous.consolidate_metadata(memory_store) + else: + await zarr.api.asynchronous.consolidate_metadata(memory_store) root = await group(store=memory_store, zarr_format=zarr_format) root_buffer = root.metadata.to_buffer_dict(default_buffer_prototype()) diff --git a/tests/test_metadata/test_v2.py b/tests/test_metadata/test_v2.py index a2894529aa..e18841f1f3 100644 --- a/tests/test_metadata/test_v2.py +++ b/tests/test_metadata/test_v2.py @@ -15,6 +15,7 @@ from zarr.core.group import ConsolidatedMetadata, GroupMetadata from zarr.core.metadata import ArrayV2Metadata from zarr.core.metadata.v2 import parse_zarr_format +from zarr.errors import ZarrUserWarning if TYPE_CHECKING: from typing import Any @@ -93,7 +94,7 @@ def test_filters_empty_tuple_warns() -> None: "fill_value": 0, } with pytest.warns( - UserWarning, match="Found an empty list of filters in the array metadata document." + ZarrUserWarning, match="Found an empty list of filters in the array metadata document." ): meta = ArrayV2Metadata.from_dict(metadata_dict) assert meta.filters is None diff --git a/tests/test_store/test_fsspec.py b/tests/test_store/test_fsspec.py index 026b25f8fc..e896f90b45 100644 --- a/tests/test_store/test_fsspec.py +++ b/tests/test_store/test_fsspec.py @@ -14,6 +14,7 @@ from zarr.abc.store import OffsetByteRequest from zarr.core.buffer import Buffer, cpu, default_buffer_prototype from zarr.core.sync import _collect_aiterator, sync +from zarr.errors import ZarrUserWarning from zarr.storage import FsspecStore from zarr.storage._fsspec import _make_async from zarr.testing.store import StoreTests @@ -258,7 +259,7 @@ def test_init_warns_if_fs_asynchronous_is_false(self) -> None: f"s3://{test_bucket_name}", endpoint_url=endpoint_url, anon=False, asynchronous=False ) store_kwargs = {"fs": fs, "path": path} - with pytest.warns(UserWarning, match=r".* was not created with `asynchronous=True`.*"): + with pytest.warns(ZarrUserWarning, match=r".* was not created with `asynchronous=True`.*"): self.store_cls(**store_kwargs) async def test_empty_nonexistent_path(self, store_kwargs: dict[str, Any]) -> None: From fee48d39d76d0bf13b3095177ff49a54e1dc1084 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 15:03:37 +0200 Subject: [PATCH 09/17] fix doctests by making them more realistic --- docs/user-guide/arrays.rst | 2 ++ docs/user-guide/consolidated_metadata.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/user-guide/arrays.rst b/docs/user-guide/arrays.rst index 67b134d442..257fac450c 100644 --- a/docs/user-guide/arrays.rst +++ b/docs/user-guide/arrays.rst @@ -238,6 +238,8 @@ built-in delta filter:: >>> import lzma >>> from numcodecs.zarr3 import LZMA + >>> import warnings + >>> warnings.filterwarnings("ignore", category=UserWarning) >>> >>> lzma_filters = [dict(id=lzma.FILTER_DELTA, dist=4), dict(id=lzma.FILTER_LZMA2, preset=1)] >>> compressors = LZMA(filters=lzma_filters) diff --git a/docs/user-guide/consolidated_metadata.rst b/docs/user-guide/consolidated_metadata.rst index 4cd72dbc74..fb674b3f4c 100644 --- a/docs/user-guide/consolidated_metadata.rst +++ b/docs/user-guide/consolidated_metadata.rst @@ -27,6 +27,8 @@ In Python, the consolidated metadata is available on the ``.consolidated_metadat attribute of the ``GroupMetadata`` object. >>> import zarr + >>> import warnings + >>> warnings.filterwarnings("ignore", category=UserWarning) >>> >>> store = zarr.storage.MemoryStore() >>> group = zarr.create_group(store=store) From 6a208eb52d1db33e68c94f9ec23533055ea90adc Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 15:08:45 +0200 Subject: [PATCH 10/17] lint and fix typo --- src/zarr/codecs/transpose.py | 4 ++-- tests/test_codecs/test_codecs.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/zarr/codecs/transpose.py b/src/zarr/codecs/transpose.py index c87804685c..d6310d38a4 100644 --- a/src/zarr/codecs/transpose.py +++ b/src/zarr/codecs/transpose.py @@ -56,7 +56,7 @@ def validate( ) -> None: if len(self.order) != len(shape): raise ValueError( - f"The `order` tuple needs have as many entries as there are dimensions in the array. Got {self.order}." + f"The `order` tuple must have as many entries as there are dimensions in the array. Got {self.order}." ) if len(self.order) != len(set(self.order)): raise ValueError( @@ -71,7 +71,7 @@ def evolve_from_array_spec(self, array_spec: ArraySpec) -> Self: ndim = array_spec.ndim if len(self.order) != ndim: raise ValueError( - f"The `order` tuple needs have as many entries as there are dimensions in the array. Got {self.order}." + f"The `order` tuple must have as many entries as there are dimensions in the array. Got {self.order}." ) if len(self.order) != len(set(self.order)): raise ValueError( diff --git a/tests/test_codecs/test_codecs.py b/tests/test_codecs/test_codecs.py index ac24e7149a..a2dad41a1b 100644 --- a/tests/test_codecs/test_codecs.py +++ b/tests/test_codecs/test_codecs.py @@ -304,11 +304,11 @@ def test_invalid_metadata(codecs: tuple[Codec, ...]) -> None: shape = (16,) chunks = (16,) data_type = UInt8() - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="The `order` tuple must have as many entries"): ArrayV3Metadata( shape=shape, chunk_grid={"name": "regular", "configuration": {"chunk_shape": chunks}}, - chunk_key_encoding={"name": "default", "configuration": {"separator": "/"}}, + chunk_key_encoding={"name": "default", "configuration": {"separator": "/"}}, # type: ignore[arg-type] fill_value=0, data_type=data_type, codecs=codecs, From a2356602b296e2923cbb9438ad348242c10131a1 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 15:17:10 +0200 Subject: [PATCH 11/17] move unstable spec warning to errors --- src/zarr/core/dtype/common.py | 5 +---- src/zarr/errors.py | 3 +++ tests/test_dtype/test_npy/test_bytes.py | 2 +- tests/test_dtype/test_npy/test_string.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/zarr/core/dtype/common.py b/src/zarr/core/dtype/common.py index 040201aa2a..652b5fdbe3 100644 --- a/src/zarr/core/dtype/common.py +++ b/src/zarr/core/dtype/common.py @@ -16,7 +16,7 @@ from typing_extensions import ReadOnly from zarr.core.common import NamedConfig -from zarr.errors import ZarrFutureWarning +from zarr.errors import UnstableSpecificationWarning EndiannessStr = Literal["little", "big"] ENDIANNESS_STR: Final = "little", "big" @@ -217,9 +217,6 @@ class HasObjectCodec: object_codec_id: ClassVar[str] -class UnstableSpecificationWarning(ZarrFutureWarning): ... - - def v3_unstable_dtype_warning(dtype: object) -> None: """ Emit this warning when a data type does not have a stable zarr v3 spec diff --git a/src/zarr/errors.py b/src/zarr/errors.py index 6be52240b3..6cc70f1888 100644 --- a/src/zarr/errors.py +++ b/src/zarr/errors.py @@ -92,3 +92,6 @@ class ZarrRuntimeWarning(RuntimeWarning): """ A warning for dubious runtime behavior. """ + + +class UnstableSpecificationWarning(ZarrFutureWarning): ... diff --git a/tests/test_dtype/test_npy/test_bytes.py b/tests/test_dtype/test_npy/test_bytes.py index 233a6bbd6b..39b5416635 100644 --- a/tests/test_dtype/test_npy/test_bytes.py +++ b/tests/test_dtype/test_npy/test_bytes.py @@ -2,8 +2,8 @@ import pytest from tests.test_dtype.test_wrapper import BaseTestZDType -from zarr.core.dtype.common import UnstableSpecificationWarning from zarr.core.dtype.npy.bytes import NullTerminatedBytes, RawBytes, VariableLengthBytes +from zarr.errors import UnstableSpecificationWarning class TestNullTerminatedBytes(BaseTestZDType): diff --git a/tests/test_dtype/test_npy/test_string.py b/tests/test_dtype/test_npy/test_string.py index c5b3713dca..19d202d164 100644 --- a/tests/test_dtype/test_npy/test_string.py +++ b/tests/test_dtype/test_npy/test_string.py @@ -5,8 +5,8 @@ from tests.test_dtype.test_wrapper import BaseTestZDType from zarr.core.dtype import FixedLengthUTF32 -from zarr.core.dtype.common import UnstableSpecificationWarning from zarr.core.dtype.npy.string import _NUMPY_SUPPORTS_VLEN_STRING, VariableLengthUTF8 +from zarr.errors import UnstableSpecificationWarning if _NUMPY_SUPPORTS_VLEN_STRING: From 33a9184ba9e3a96cfdd6241f6564df8c941684eb Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 15:24:06 +0200 Subject: [PATCH 12/17] handle warnings in gpu tests --- tests/test_buffer.py | 27 +++++++++++++++------------ tests/test_store/test_memory.py | 5 ++++- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/tests/test_buffer.py b/tests/test_buffer.py index bbfa25d138..58e689c96b 100644 --- a/tests/test_buffer.py +++ b/tests/test_buffer.py @@ -13,6 +13,7 @@ from zarr.codecs.gzip import GzipCodec from zarr.codecs.transpose import TransposeCodec from zarr.codecs.zstd import ZstdCodec +from zarr.errors import ZarrUserWarning from zarr.storage import MemoryStore, StorePath from zarr.testing.buffer import ( NDBufferUsingTestNDArrayLike, @@ -138,12 +139,13 @@ async def test_codecs_use_of_gpu_prototype() -> None: filters=[TransposeCodec(order=(1, 0))], ) expect[:] = cp.arange(100).reshape(10, 10) - - await a.setitem( - selection=(slice(0, 10), slice(0, 10)), - value=expect[:], - prototype=gpu.buffer_prototype, - ) + msg = "Creating a zarr.buffer.gpu.Buffer with an array that does not support the __cuda_array_interface__ for zero-copy transfers, falling back to slow copy based path" + with pytest.warns(ZarrUserWarning, match=msg): + await a.setitem( + selection=(slice(0, 10), slice(0, 10)), + value=expect[:], + prototype=gpu.buffer_prototype, + ) got = await a.getitem(selection=(slice(0, 10), slice(0, 10)), prototype=gpu.buffer_prototype) assert isinstance(got, cp.ndarray) assert cp.array_equal(expect, got) @@ -164,12 +166,13 @@ async def test_sharding_use_of_gpu_prototype() -> None: fill_value=0, ) expect[:] = cp.arange(100).reshape(10, 10) - - await a.setitem( - selection=(slice(0, 10), slice(0, 10)), - value=expect[:], - prototype=gpu.buffer_prototype, - ) + msg = "Creating a zarr.buffer.gpu.Buffer with an array that does not support the __cuda_array_interface__ for zero-copy transfers, falling back to slow copy based path" + with pytest.warns(ZarrUserWarning, match=msg): + await a.setitem( + selection=(slice(0, 10), slice(0, 10)), + value=expect[:], + prototype=gpu.buffer_prototype, + ) got = await a.getitem( selection=(slice(0, 10), slice(0, 10)), prototype=gpu.buffer_prototype ) diff --git a/tests/test_store/test_memory.py b/tests/test_store/test_memory.py index 4fc3f6e698..0b6bae757d 100644 --- a/tests/test_store/test_memory.py +++ b/tests/test_store/test_memory.py @@ -9,6 +9,7 @@ import zarr from zarr.core.buffer import Buffer, cpu, gpu +from zarr.errors import ZarrUserWarning from zarr.storage import GpuMemoryStore, MemoryStore from zarr.testing.store import StoreTests from zarr.testing.utils import gpu_test @@ -130,6 +131,8 @@ def test_from_dict(self) -> None: "a": gpu.Buffer.from_bytes(b"aaaa"), "b": cpu.Buffer.from_bytes(b"bbbb"), } - result = GpuMemoryStore.from_dict(d) + msg = "Creating a zarr.buffer.gpu.Buffer with an array that does not support the __cuda_array_interface__ for zero-copy transfers, falling back to slow copy based path" + with pytest.warns(ZarrUserWarning, match=msg): + result = GpuMemoryStore.from_dict(d) for v in result._store_dict.values(): assert type(v) is gpu.Buffer From fc72752142f7ced482292c350158e50eddf772b8 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 15:32:07 +0200 Subject: [PATCH 13/17] handle more warnings --- tests/test_buffer.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_buffer.py b/tests/test_buffer.py index 58e689c96b..b50e5abb67 100644 --- a/tests/test_buffer.py +++ b/tests/test_buffer.py @@ -146,7 +146,10 @@ async def test_codecs_use_of_gpu_prototype() -> None: value=expect[:], prototype=gpu.buffer_prototype, ) - got = await a.getitem(selection=(slice(0, 10), slice(0, 10)), prototype=gpu.buffer_prototype) + with pytest.warns(ZarrUserWarning, match=msg): + got = await a.getitem( + selection=(slice(0, 10), slice(0, 10)), prototype=gpu.buffer_prototype + ) assert isinstance(got, cp.ndarray) assert cp.array_equal(expect, got) @@ -173,9 +176,10 @@ async def test_sharding_use_of_gpu_prototype() -> None: value=expect[:], prototype=gpu.buffer_prototype, ) - got = await a.getitem( - selection=(slice(0, 10), slice(0, 10)), prototype=gpu.buffer_prototype - ) + with pytest.warns(ZarrUserWarning, match=msg): + got = await a.getitem( + selection=(slice(0, 10), slice(0, 10)), prototype=gpu.buffer_prototype + ) assert isinstance(got, cp.ndarray) assert cp.array_equal(expect, got) From 42b4cb555750024d1de80b8bca46a3be17567d38 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 15:47:17 +0200 Subject: [PATCH 14/17] handle another warning --- tests/test_metadata/test_consolidated.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_metadata/test_consolidated.py b/tests/test_metadata/test_consolidated.py index e4c37adfd1..e23444cf93 100644 --- a/tests/test_metadata/test_consolidated.py +++ b/tests/test_metadata/test_consolidated.py @@ -497,7 +497,14 @@ async def test_to_dict_order( await child.create_array("d", shape=(1,), dtype=dtype) # Consolidate metadata and re-open store - await zarr.api.asynchronous.consolidate_metadata(memory_store) + if zarr_format == 3: + with pytest.warns( + ZarrUserWarning, + match="Consolidated metadata is currently not part in the Zarr format 3 specification.", + ): + await zarr.api.asynchronous.consolidate_metadata(memory_store) + else: + await zarr.api.asynchronous.consolidate_metadata(memory_store) g2 = await zarr.api.asynchronous.open_group(store=memory_store) assert list(g2.metadata.consolidated_metadata.metadata) == ["a", "b", "c"] From 713bda9974c9d3c949b2fe8f14d8db21af0ddec5 Mon Sep 17 00:00:00 2001 From: Davis Vann Bennett Date: Fri, 1 Aug 2025 16:06:46 +0200 Subject: [PATCH 15/17] Fix exports --- src/zarr/errors.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/zarr/errors.py b/src/zarr/errors.py index 6cc70f1888..b5f1f00c38 100644 --- a/src/zarr/errors.py +++ b/src/zarr/errors.py @@ -8,8 +8,10 @@ "GroupNotFoundError", "MetadataValidationError", "NodeTypeValidationError", + "UnstableSpecificationWarning", "ZarrDeprecationWarning", "ZarrFutureWarning", + "ZarrRuntimeWarning", ] @@ -76,6 +78,12 @@ class ZarrFutureWarning(FutureWarning): """ +class UnstableSpecificationWarning(ZarrFutureWarning): + """ + A warning raised to indicate that a feature is outside the Zarr specification. + """ + + class ZarrDeprecationWarning(DeprecationWarning): """ A warning raised to indicate that a construct will be removed in a future release. @@ -92,6 +100,3 @@ class ZarrRuntimeWarning(RuntimeWarning): """ A warning for dubious runtime behavior. """ - - -class UnstableSpecificationWarning(ZarrFutureWarning): ... From f483eb8287e154030d8a065171c9f49b00a68eb7 Mon Sep 17 00:00:00 2001 From: Davis Bennett Date: Sat, 2 Aug 2025 08:28:14 +0200 Subject: [PATCH 16/17] Update src/zarr/errors.py Co-authored-by: David Stansby --- src/zarr/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zarr/errors.py b/src/zarr/errors.py index b5f1f00c38..626eb669ad 100644 --- a/src/zarr/errors.py +++ b/src/zarr/errors.py @@ -86,7 +86,7 @@ class UnstableSpecificationWarning(ZarrFutureWarning): class ZarrDeprecationWarning(DeprecationWarning): """ - A warning raised to indicate that a construct will be removed in a future release. + A warning raised to indicate that a feature will be removed in a future release. """ From d02980003343ead8d54ca7049cc43e529dcc3915 Mon Sep 17 00:00:00 2001 From: Davis Bennett Date: Sat, 2 Aug 2025 09:04:21 +0200 Subject: [PATCH 17/17] Update changes/3098.misc.rst Co-authored-by: David Stansby --- changes/3098.misc.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changes/3098.misc.rst b/changes/3098.misc.rst index 0f9684c14e..21da355add 100644 --- a/changes/3098.misc.rst +++ b/changes/3098.misc.rst @@ -1,2 +1,2 @@ -Define Zarr-specific warning classes ``ZarrDeprecationWarning`` and ``ZarrFutureWarning``, that -subclass ``DeprecationWarning`` and ``FutureWarning``, respectively. \ No newline at end of file +Define Zarr-specific warning classes `ZarrDeprecationWarning` and `ZarrFutureWarning`, that +subclass `DeprecationWarning` and `FutureWarning`, respectively. \ No newline at end of file