From 05d28b1a7ab301040a10a4a121ef8639993254a7 Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Wed, 25 Mar 2026 09:49:28 -0400 Subject: [PATCH 1/4] move warning into to_dict --- src/zarr/codecs/numcodecs/_codecs.py | 12 ++-- tests/test_cli/test_migrate_v3.py | 6 +- tests/test_codecs/test_numcodecs.py | 82 +++++++++++++++++----------- 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/zarr/codecs/numcodecs/_codecs.py b/src/zarr/codecs/numcodecs/_codecs.py index 4a3d88a84f..d9190bba9c 100644 --- a/src/zarr/codecs/numcodecs/_codecs.py +++ b/src/zarr/codecs/numcodecs/_codecs.py @@ -102,12 +102,6 @@ def __init__(self, **codec_config: JSON) -> None: ) # pragma: no cover object.__setattr__(self, "codec_config", codec_config) - warn( - "Numcodecs codecs are not in the Zarr version 3 specification and " - "may not be supported by other zarr implementations.", - category=ZarrUserWarning, - stacklevel=2, - ) @cached_property def _codec(self) -> Numcodec: @@ -119,6 +113,12 @@ def from_dict(cls, data: dict[str, JSON]) -> Self: return cls(**codec_config) def to_dict(self) -> dict[str, JSON]: + warn( + "Numcodecs codecs are not in the Zarr version 3 specification and " + "may not be supported by other zarr implementations.", + category=ZarrUserWarning, + stacklevel=2, + ) codec_config = self.codec_config.copy() codec_config.pop("id", None) return { diff --git a/tests/test_cli/test_migrate_v3.py b/tests/test_cli/test_migrate_v3.py index 8bda31d208..d9160504e1 100644 --- a/tests/test_cli/test_migrate_v3.py +++ b/tests/test_cli/test_migrate_v3.py @@ -524,8 +524,7 @@ def test_migrate_incorrect_filter(local_store: LocalStore) -> None: fill_value=0, ) - with pytest.warns(UserWarning, match=NUMCODECS_USER_WARNING): - result = runner.invoke(cli.app, ["migrate", "v3", str(local_store.root)]) + result = runner.invoke(cli.app, ["migrate", "v3", str(local_store.root)]) assert result.exit_code == 1 assert isinstance(result.exception, TypeError) @@ -548,8 +547,7 @@ def test_migrate_incorrect_compressor(local_store: LocalStore) -> None: fill_value=0, ) - with pytest.warns(UserWarning, match=NUMCODECS_USER_WARNING): - result = runner.invoke(cli.app, ["migrate", "v3", str(local_store.root)]) + result = runner.invoke(cli.app, ["migrate", "v3", str(local_store.root)]) assert result.exit_code == 1 assert isinstance(result.exception, TypeError) diff --git a/tests/test_codecs/test_numcodecs.py b/tests/test_codecs/test_numcodecs.py index ddfca71294..310ad526fa 100644 --- a/tests/test_codecs/test_numcodecs.py +++ b/tests/test_codecs/test_numcodecs.py @@ -2,6 +2,7 @@ import contextlib import pickle +import warnings from typing import TYPE_CHECKING, Any import numpy as np @@ -164,8 +165,7 @@ def test_generic_filter( a[:, :] = data.copy() with codec_conf(): - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - b = open_array(a.store, mode="r") + b = open_array(a.store, mode="r") np.testing.assert_array_equal(data, b[:, :]) @@ -183,8 +183,7 @@ def test_generic_filter_bitround() -> None: ) a[:, :] = data.copy() - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - b = open_array(a.store, mode="r") + b = open_array(a.store, mode="r") assert np.allclose(data, b[:, :], atol=0.1) @@ -202,8 +201,7 @@ def test_generic_filter_quantize() -> None: ) a[:, :] = data.copy() - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - b = open_array(a.store, mode="r") + b = open_array(a.store, mode="r") assert np.allclose(data, b[:, :], atol=0.001) @@ -222,20 +220,18 @@ def test_generic_filter_packbits() -> None: ) a[:, :] = data.copy() - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - b = open_array(a.store, mode="r") + b = open_array(a.store, mode="r") np.testing.assert_array_equal(data, b[:, :]) - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - with pytest.raises(ValueError, match=".*requires bool dtype.*"): - create_array( - {}, - shape=data.shape, - chunks=(16, 16), - dtype="uint32", - fill_value=0, - filters=[_numcodecs.PackBits()], - ) + with pytest.raises(ValueError, match=".*requires bool dtype.*"): + create_array( + {}, + shape=data.shape, + chunks=(16, 16), + dtype="uint32", + fill_value=0, + filters=[_numcodecs.PackBits()], + ) @pytest.mark.parametrize( @@ -251,8 +247,7 @@ def test_generic_filter_packbits() -> None: def test_generic_checksum(codec_class: type[_numcodecs._NumcodecsBytesBytesCodec]) -> None: # Check if the codec is available in numcodecs try: - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - codec_class()._codec # noqa: B018 + codec_class()._codec # noqa: B018 except UnknownCodecError as e: # pragma: no cover pytest.skip(f"{codec_class.codec_name} is not available in numcodecs: {e}") @@ -270,16 +265,14 @@ def test_generic_checksum(codec_class: type[_numcodecs._NumcodecsBytesBytesCodec a[:, :] = data.copy() with codec_conf(): - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - b = open_array(a.store, mode="r") + b = open_array(a.store, mode="r") np.testing.assert_array_equal(data, b[:, :]) @pytest.mark.parametrize("codec_class", [_numcodecs.PCodec, _numcodecs.ZFPY]) def test_generic_bytes_codec(codec_class: type[_numcodecs._NumcodecsArrayBytesCodec]) -> None: try: - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - codec_class()._codec # noqa: B018 + codec_class()._codec # noqa: B018 except ValueError as e: # pragma: no cover if "codec not available" in str(e): pytest.xfail(f"{codec_class.codec_name} is not available: {e}") @@ -321,21 +314,47 @@ def test_delta_astype() -> None: a[:, :] = data.copy() with codec_conf(): - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - b = open_array(a.store, mode="r") + b = open_array(a.store, mode="r") np.testing.assert_array_equal(data, b[:, :]) def test_repr() -> None: - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - codec = _numcodecs.LZ4(level=5) + codec = _numcodecs.LZ4(level=5) assert repr(codec) == "LZ4(codec_name='numcodecs.lz4', codec_config={'level': 5})" def test_to_dict() -> None: + codec = _numcodecs.LZ4(level=5) + with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): + assert codec.to_dict() == {"name": "numcodecs.lz4", "configuration": {"level": 5}} + + +def test_warn_on_write_not_read() -> None: + data = np.arange(0, 256, dtype="uint16").reshape((16, 16)) + with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - codec = _numcodecs.LZ4(level=5) - assert codec.to_dict() == {"name": "numcodecs.lz4", "configuration": {"level": 5}} + a = create_array( + {}, + shape=data.shape, + chunks=(16, 16), + dtype=data.dtype, + fill_value=0, + compressors=[_numcodecs.Zstd(level=1)], + ) + + a[:, :] = data.copy() + with codec_conf(): + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + b = open_array(a.store, mode="r") + + np.testing.assert_array_equal(data, b[:, :]) + assert not [ + warning + for warning in caught + if issubclass(warning.category, ZarrUserWarning) + and "Numcodecs codecs are not in the Zarr version 3 specification" in str(warning.message) + ] @pytest.mark.parametrize( @@ -367,8 +386,7 @@ def test_to_dict() -> None: def test_codecs_pickleable(codec_cls: type[_numcodecs._NumcodecsCodec]) -> None: # Check if the codec is available in numcodecs try: - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - codec = codec_cls() + codec = codec_cls() except UnknownCodecError as e: # pragma: no cover pytest.skip(f"{codec_cls.codec_name} is not available in numcodecs: {e}") From 76ed82d9037a5cc3ad57bd7a39127f302036bf2e Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Wed, 25 Mar 2026 12:29:28 -0400 Subject: [PATCH 2/4] remove warning entirely --- src/zarr/codecs/numcodecs/_codecs.py | 8 -- tests/test_array.py | 25 ++-- tests/test_cli/test_migrate_v3.py | 4 - tests/test_codecs/test_numcodecs.py | 179 +++++++++++---------------- 4 files changed, 80 insertions(+), 136 deletions(-) diff --git a/src/zarr/codecs/numcodecs/_codecs.py b/src/zarr/codecs/numcodecs/_codecs.py index d9190bba9c..06c085ad2a 100644 --- a/src/zarr/codecs/numcodecs/_codecs.py +++ b/src/zarr/codecs/numcodecs/_codecs.py @@ -32,7 +32,6 @@ from dataclasses import dataclass, replace from functools import cached_property from typing import TYPE_CHECKING, Any, Self -from warnings import warn import numpy as np @@ -41,7 +40,6 @@ from zarr.core.buffer.cpu import as_numpy_array_wrapper from zarr.core.common import JSON, parse_named_configuration, product from zarr.dtype import UInt8, ZDType, parse_dtype -from zarr.errors import ZarrUserWarning from zarr.registry import get_numcodec if TYPE_CHECKING: @@ -113,12 +111,6 @@ def from_dict(cls, data: dict[str, JSON]) -> Self: return cls(**codec_config) def to_dict(self) -> dict[str, JSON]: - warn( - "Numcodecs codecs are not in the Zarr version 3 specification and " - "may not be supported by other zarr implementations.", - category=ZarrUserWarning, - stacklevel=2, - ) codec_config = self.codec_config.copy() codec_config.pop("id", None) return { diff --git a/tests/test_array.py b/tests/test_array.py index 8ea79b5f10..bf6f651283 100644 --- a/tests/test_array.py +++ b/tests/test_array.py @@ -1853,24 +1853,21 @@ def test_roundtrip_numcodecs() -> None: # Create the array with the correct codecs root = zarr.group(store) - warn_msg = "Numcodecs codecs are not in the Zarr version 3 specification and may not be supported by other zarr implementations." - with pytest.warns(ZarrUserWarning, match=warn_msg): - root.create_array( - "test", - shape=(720, 1440), - chunks=(720, 1440), - dtype="float64", - compressors=compressors, # type: ignore[arg-type] - filters=filters, # type: ignore[arg-type] - fill_value=-9.99, - dimension_names=["lat", "lon"], - ) + root.create_array( + "test", + shape=(720, 1440), + chunks=(720, 1440), + dtype="float64", + compressors=compressors, # type: ignore[arg-type] + filters=filters, # type: ignore[arg-type] + 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) - with pytest.warns(ZarrUserWarning, match=warn_msg): - metadata = root["test"].metadata.to_dict() + metadata = root["test"].metadata.to_dict() expected = (*filters, BYTES_CODEC, *compressors) assert metadata["codecs"] == expected diff --git a/tests/test_cli/test_migrate_v3.py b/tests/test_cli/test_migrate_v3.py index d9160504e1..6e169e5f48 100644 --- a/tests/test_cli/test_migrate_v3.py +++ b/tests/test_cli/test_migrate_v3.py @@ -32,8 +32,6 @@ runner = typer_testing.CliRunner() -NUMCODECS_USER_WARNING = "Numcodecs codecs are not in the Zarr version 3 specification and may not be supported by other zarr implementations." - def test_migrate_array(local_store: LocalStore) -> None: shape = (10, 10) @@ -316,7 +314,6 @@ def test_migrate_compressor( assert np.all(zarr_array[:] == 1) -@pytest.mark.filterwarnings(f"ignore:{NUMCODECS_USER_WARNING}:UserWarning") def test_migrate_numcodecs_compressor(local_store: LocalStore) -> None: """Test migration of a numcodecs compressor without a zarr.codecs equivalent.""" @@ -360,7 +357,6 @@ def test_migrate_numcodecs_compressor(local_store: LocalStore) -> None: assert np.all(zarr_array[:] == 1) -@pytest.mark.filterwarnings(f"ignore:{NUMCODECS_USER_WARNING}:UserWarning") def test_migrate_filter(local_store: LocalStore) -> None: filter_v2 = numcodecs.Delta(dtype=" None: assert _is_numcodec_cls(GZip) -EXPECTED_WARNING_STR = "Numcodecs codecs are not in the Zarr version 3.*" - ALL_CODECS = tuple( filter( lambda v: issubclass(v, _numcodecs._NumcodecsCodec) and hasattr(v, "codec_name"), @@ -116,15 +112,14 @@ def test_docstring(codec_class: type[_numcodecs._NumcodecsCodec]) -> None: def test_generic_compressor(codec_class: type[_numcodecs._NumcodecsBytesBytesCodec]) -> None: data = np.arange(0, 256, dtype="uint16").reshape((16, 16)) - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - a = create_array( - {}, - shape=data.shape, - chunks=(16, 16), - dtype=data.dtype, - fill_value=0, - compressors=[codec_class()], - ) + a = create_array( + {}, + shape=data.shape, + chunks=(16, 16), + dtype=data.dtype, + fill_value=0, + compressors=[codec_class()], + ) a[:, :] = data.copy() np.testing.assert_array_equal(data, a[:, :]) @@ -151,17 +146,16 @@ def test_generic_filter( ) -> None: data = np.linspace(0, 10, 256, dtype="float32").reshape((16, 16)) - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - a = create_array( - {}, - shape=data.shape, - chunks=(16, 16), - dtype=data.dtype, - fill_value=0, - filters=[ - codec_class(**codec_config), - ], - ) + a = create_array( + {}, + shape=data.shape, + chunks=(16, 16), + dtype=data.dtype, + fill_value=0, + filters=[ + codec_class(**codec_config), + ], + ) a[:, :] = data.copy() with codec_conf(): @@ -172,15 +166,14 @@ def test_generic_filter( def test_generic_filter_bitround() -> None: data = np.linspace(0, 1, 256, dtype="float32").reshape((16, 16)) - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - a = create_array( - {}, - shape=data.shape, - chunks=(16, 16), - dtype=data.dtype, - fill_value=0, - filters=[_numcodecs.BitRound(keepbits=3)], - ) + a = create_array( + {}, + shape=data.shape, + chunks=(16, 16), + dtype=data.dtype, + fill_value=0, + filters=[_numcodecs.BitRound(keepbits=3)], + ) a[:, :] = data.copy() b = open_array(a.store, mode="r") @@ -190,15 +183,14 @@ def test_generic_filter_bitround() -> None: def test_generic_filter_quantize() -> None: data = np.linspace(0, 10, 256, dtype="float32").reshape((16, 16)) - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - a = create_array( - {}, - shape=data.shape, - chunks=(16, 16), - dtype=data.dtype, - fill_value=0, - filters=[_numcodecs.Quantize(digits=3)], - ) + a = create_array( + {}, + shape=data.shape, + chunks=(16, 16), + dtype=data.dtype, + fill_value=0, + filters=[_numcodecs.Quantize(digits=3)], + ) a[:, :] = data.copy() b = open_array(a.store, mode="r") @@ -209,15 +201,14 @@ def test_generic_filter_packbits() -> None: data = np.zeros((16, 16), dtype="bool") data[0:4, :] = True - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - a = create_array( - {}, - shape=data.shape, - chunks=(16, 16), - dtype=data.dtype, - fill_value=0, - filters=[_numcodecs.PackBits()], - ) + a = create_array( + {}, + shape=data.shape, + chunks=(16, 16), + dtype=data.dtype, + fill_value=0, + filters=[_numcodecs.PackBits()], + ) a[:, :] = data.copy() b = open_array(a.store, mode="r") @@ -253,15 +244,14 @@ def test_generic_checksum(codec_class: type[_numcodecs._NumcodecsBytesBytesCodec data = np.linspace(0, 10, 256, dtype="float32").reshape((16, 16)) - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - a = create_array( - {}, - shape=data.shape, - chunks=(16, 16), - dtype=data.dtype, - fill_value=0, - compressors=[codec_class()], - ) + a = create_array( + {}, + shape=data.shape, + chunks=(16, 16), + dtype=data.dtype, + fill_value=0, + compressors=[codec_class()], + ) a[:, :] = data.copy() with codec_conf(): @@ -283,15 +273,14 @@ def test_generic_bytes_codec(codec_class: type[_numcodecs._NumcodecsArrayBytesCo data = np.arange(0, 256, dtype="float32").reshape((16, 16)) - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - a = create_array( - {}, - shape=data.shape, - chunks=(16, 16), - dtype=data.dtype, - fill_value=0, - serializer=codec_class(), - ) + a = create_array( + {}, + shape=data.shape, + chunks=(16, 16), + dtype=data.dtype, + fill_value=0, + serializer=codec_class(), + ) a[:, :] = data.copy() np.testing.assert_array_equal(data, a[:, :]) @@ -300,17 +289,16 @@ def test_generic_bytes_codec(codec_class: type[_numcodecs._NumcodecsArrayBytesCo def test_delta_astype() -> None: data = np.linspace(0, 10, 256, dtype="i8").reshape((16, 16)) - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - a = create_array( - {}, - shape=data.shape, - chunks=(16, 16), - dtype=data.dtype, - fill_value=0, - filters=[ - _numcodecs.Delta(dtype="i8", astype="i2"), - ], - ) + a = create_array( + {}, + shape=data.shape, + chunks=(16, 16), + dtype=data.dtype, + fill_value=0, + filters=[ + _numcodecs.Delta(dtype="i8", astype="i2"), + ], + ) a[:, :] = data.copy() with codec_conf(): @@ -325,36 +313,7 @@ def test_repr() -> None: def test_to_dict() -> None: codec = _numcodecs.LZ4(level=5) - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - assert codec.to_dict() == {"name": "numcodecs.lz4", "configuration": {"level": 5}} - - -def test_warn_on_write_not_read() -> None: - data = np.arange(0, 256, dtype="uint16").reshape((16, 16)) - - with pytest.warns(ZarrUserWarning, match=EXPECTED_WARNING_STR): - a = create_array( - {}, - shape=data.shape, - chunks=(16, 16), - dtype=data.dtype, - fill_value=0, - compressors=[_numcodecs.Zstd(level=1)], - ) - - a[:, :] = data.copy() - with codec_conf(): - with warnings.catch_warnings(record=True) as caught: - warnings.simplefilter("always") - b = open_array(a.store, mode="r") - - np.testing.assert_array_equal(data, b[:, :]) - assert not [ - warning - for warning in caught - if issubclass(warning.category, ZarrUserWarning) - and "Numcodecs codecs are not in the Zarr version 3 specification" in str(warning.message) - ] + assert codec.to_dict() == {"name": "numcodecs.lz4", "configuration": {"level": 5}} @pytest.mark.parametrize( From b5b75face2a089e03f1deeb064ed26cdd6d0e19f Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Wed, 25 Mar 2026 17:07:02 -0400 Subject: [PATCH 3/4] changelog --- changes/3833.fix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/3833.fix.md diff --git a/changes/3833.fix.md b/changes/3833.fix.md new file mode 100644 index 0000000000..1f3c87b482 --- /dev/null +++ b/changes/3833.fix.md @@ -0,0 +1 @@ +Remove the warning that is emitted when any Numcodecs codec is instantiated. From d425f0c7ef49b7b5fc9b2e3ca749f6f46ed8918e Mon Sep 17 00:00:00 2001 From: Sam Levang Date: Wed, 25 Mar 2026 17:11:54 -0400 Subject: [PATCH 4/4] changelog type --- changes/{3833.fix.md => 3833.misc.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changes/{3833.fix.md => 3833.misc.md} (100%) diff --git a/changes/3833.fix.md b/changes/3833.misc.md similarity index 100% rename from changes/3833.fix.md rename to changes/3833.misc.md