2929import math
3030from dataclasses import dataclass , replace
3131from functools import cached_property
32+ from importlib .metadata import version
3233from typing import Any , Self
3334from warnings import warn
3435
3536import numpy as np
37+ from packaging .version import Version
3638
3739import numcodecs
3840
3941try :
40- import zarr
42+ import zarr # noqa: F401
4143
42- if zarr . __version__ < "3.0.0" : # pragma: no cover
44+ if Version ( version ( ' zarr' )) < Version ( "3.0.0" ) : # pragma: no cover
4345 raise ImportError ("zarr 3.0.0 or later is required to use the numcodecs zarr integration." )
4446except ImportError as e : # pragma: no cover
4547 raise ImportError (
5658CODEC_PREFIX = "numcodecs."
5759
5860
61+ def _from_zarr_dtype (dtype : Any ) -> np .dtype :
62+ """
63+ Get a numpy data type from an array spec, depending on the zarr version.
64+ """
65+ if Version (version ('zarr' )) >= Version ("3.1.0" ):
66+ return dtype .to_native_dtype ()
67+ return dtype # pragma: no cover
68+
69+
70+ def _to_zarr_dtype (dtype : np .dtype ) -> Any :
71+ if Version (version ('zarr' )) >= Version ("3.1.0" ):
72+ from zarr .dtype import parse_data_type
73+
74+ return parse_data_type (dtype , zarr_format = 3 )
75+ return dtype # pragma: no cover
76+
77+
5978def _expect_name_prefix (codec_name : str ) -> str :
6079 if not codec_name .startswith (CODEC_PREFIX ):
6180 raise ValueError (
@@ -224,15 +243,17 @@ class LZMA(_NumcodecsBytesBytesCodec, codec_name="lzma"):
224243class Shuffle (_NumcodecsBytesBytesCodec , codec_name = "shuffle" ):
225244 def evolve_from_array_spec (self , array_spec : ArraySpec ) -> Shuffle :
226245 if self .codec_config .get ("elementsize" ) is None :
227- return Shuffle (** {** self .codec_config , "elementsize" : array_spec .dtype .itemsize })
246+ dtype = _from_zarr_dtype (array_spec .dtype )
247+ return Shuffle (** {** self .codec_config , "elementsize" : dtype .itemsize })
228248 return self # pragma: no cover
229249
230250
231251# array-to-array codecs ("filters")
232252class Delta (_NumcodecsArrayArrayCodec , codec_name = "delta" ):
233253 def resolve_metadata (self , chunk_spec : ArraySpec ) -> ArraySpec :
234254 if astype := self .codec_config .get ("astype" ):
235- return replace (chunk_spec , dtype = np .dtype (astype )) # type: ignore[call-overload]
255+ dtype = _to_zarr_dtype (np .dtype (astype )) # type: ignore[call-overload]
256+ return replace (chunk_spec , dtype = dtype )
236257 return chunk_spec
237258
238259
@@ -243,12 +264,14 @@ class BitRound(_NumcodecsArrayArrayCodec, codec_name="bitround"):
243264class FixedScaleOffset (_NumcodecsArrayArrayCodec , codec_name = "fixedscaleoffset" ):
244265 def resolve_metadata (self , chunk_spec : ArraySpec ) -> ArraySpec :
245266 if astype := self .codec_config .get ("astype" ):
246- return replace (chunk_spec , dtype = np .dtype (astype )) # type: ignore[call-overload]
267+ dtype = _to_zarr_dtype (np .dtype (astype )) # type: ignore[call-overload]
268+ return replace (chunk_spec , dtype = dtype )
247269 return chunk_spec
248270
249271 def evolve_from_array_spec (self , array_spec : ArraySpec ) -> FixedScaleOffset :
250272 if self .codec_config .get ("dtype" ) is None :
251- return FixedScaleOffset (** {** self .codec_config , "dtype" : str (array_spec .dtype )})
273+ dtype = _from_zarr_dtype (array_spec .dtype )
274+ return FixedScaleOffset (** {** self .codec_config , "dtype" : str (dtype )})
252275 return self
253276
254277
@@ -258,7 +281,8 @@ def __init__(self, **codec_config: JSON) -> None:
258281
259282 def evolve_from_array_spec (self , array_spec : ArraySpec ) -> Quantize :
260283 if self .codec_config .get ("dtype" ) is None :
261- return Quantize (** {** self .codec_config , "dtype" : str (array_spec .dtype )})
284+ dtype = _from_zarr_dtype (array_spec .dtype )
285+ return Quantize (** {** self .codec_config , "dtype" : str (dtype )})
262286 return self
263287
264288
@@ -267,21 +291,27 @@ def resolve_metadata(self, chunk_spec: ArraySpec) -> ArraySpec:
267291 return replace (
268292 chunk_spec ,
269293 shape = (1 + math .ceil (product (chunk_spec .shape ) / 8 ),),
270- dtype = np .dtype ("uint8" ),
294+ dtype = _to_zarr_dtype ( np .dtype ("uint8" ) ),
271295 )
272296
273- def validate (self , * , dtype : np .dtype [Any ], ** _kwargs ) -> None :
274- if dtype != np .dtype ("bool" ):
297+ # todo: remove this type: ignore when this class can be defined w.r.t.
298+ # a single zarr dtype API
299+ def validate (self , * , dtype : np .dtype [Any ], ** _kwargs ) -> None : # type: ignore[override]
300+ _dtype = _from_zarr_dtype (dtype )
301+ if _dtype != np .dtype ("bool" ):
275302 raise ValueError (f"Packbits filter requires bool dtype. Got { dtype } ." )
276303
277304
278305class AsType (_NumcodecsArrayArrayCodec , codec_name = "astype" ):
279306 def resolve_metadata (self , chunk_spec : ArraySpec ) -> ArraySpec :
280- return replace (chunk_spec , dtype = np .dtype (self .codec_config ["encode_dtype" ])) # type: ignore[arg-type]
307+ dtype = _to_zarr_dtype (np .dtype (self .codec_config ["encode_dtype" ])) # type: ignore[arg-type]
308+ return replace (chunk_spec , dtype = dtype )
281309
282310 def evolve_from_array_spec (self , array_spec : ArraySpec ) -> AsType :
283311 if self .codec_config .get ("decode_dtype" ) is None :
284- return AsType (** {** self .codec_config , "decode_dtype" : str (array_spec .dtype )})
312+ # TODO: remove these coverage exemptions the correct way, i.e. with tests
313+ dtype = _from_zarr_dtype (array_spec .dtype ) # pragma: no cover
314+ return AsType (** {** self .codec_config , "decode_dtype" : str (dtype )}) # pragma: no cover
285315 return self
286316
287317
0 commit comments