Skip to content

Handle zarr 3.1.0 #766

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1cedf6f
(chore) add type hints to codec abc
d-v-b Feb 9, 2025
036cce8
(chore) build project with pixi
d-v-b Feb 9, 2025
6ddd9dc
use fresh abcs
d-v-b Feb 9, 2025
2635279
remove blosc
d-v-b Feb 12, 2025
998579a
Update pyproject.toml
d-v-b Jul 3, 2025
5e7adf6
Merge branch 'main' into chore/use-pixi
d-v-b Jul 14, 2025
0b0206e
Merge branch 'main' into chore/use-pixi
d-v-b Jul 17, 2025
4dfd79d
Update pyproject.toml
d-v-b Jul 17, 2025
f843142
dtype adaptor for zarr 3.1
d-v-b Jul 17, 2025
20d089f
revert change to pyproject.toml
d-v-b Jul 17, 2025
5cad56f
Merge branch 'main' into handle-zarr-3.1.0
d-v-b Jul 17, 2025
171e5a5
versionify version
d-v-b Jul 17, 2025
ca19044
Merge branch 'handle-zarr-3.1.0' of https://github.com/d-v-b/numcodec…
d-v-b Jul 17, 2025
6c44c4e
versionify another version
d-v-b Jul 17, 2025
41e72eb
lint
d-v-b Jul 17, 2025
0e57a02
lint
d-v-b Jul 17, 2025
2557245
dodge test coverage
d-v-b Jul 17, 2025
299f19a
Update pyproject.toml
d-v-b Jul 3, 2025
29c2b97
dtype adaptor for zarr 3.1
d-v-b Jul 17, 2025
df805aa
revert change to pyproject.toml
d-v-b Jul 17, 2025
24f7887
versionify version
d-v-b Jul 17, 2025
49d502d
versionify another version
d-v-b Jul 17, 2025
16c19c6
lint
d-v-b Jul 17, 2025
2de8015
lint
d-v-b Jul 17, 2025
9137741
dodge test coverage
d-v-b Jul 17, 2025
f8ab028
use pixi + hatch for parametrized zarr python testing
d-v-b Jul 28, 2025
50d075a
Merge branch 'handle-zarr-3.1.0' of https://github.com/d-v-b/numcodec…
d-v-b Jul 28, 2025
a3fba35
Apply suggestions from code review
d-v-b Jul 28, 2025
f530db3
privatize functions
d-v-b Jul 28, 2025
7f25545
Merge branch 'handle-zarr-3.1.0' of https://github.com/d-v-b/numcodec…
d-v-b Jul 28, 2025
d9fb654
add lockfile
d-v-b Jul 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# GitHub syntax highlighting
pixi.lock linguist-language=YAML linguist-generated=true
26 changes: 26 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,29 @@ jobs:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true

test-zarr:
runs-on: ubuntu-latest
strategy:
fail-fast: false

defaults:
run:
shell: bash -el {0}

steps:
- name: Checkout source
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0 # required for version resolution

- name: Set up Pixi
uses: prefix-dev/[email protected]
with:
pixi-version: v0.49.0
cache: true

- name: Run tests
shell: "bash -l {0}"
run: pixi run -e default hatch run test:test-zarr
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,6 @@ numcodecs/version.py

# Cython generated
numcodecs/*.c
# pixi environments
.pixi/*
*.egg-info
50 changes: 39 additions & 11 deletions numcodecs/zarr3.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,19 @@
import math
from dataclasses import dataclass, replace
from functools import cached_property
from importlib.metadata import version
from typing import Any, Self
from warnings import warn

import numpy as np
from packaging.version import Version

import numcodecs

try:
import zarr
import zarr # noqa: F401

if zarr.__version__ < "3.0.0": # pragma: no cover
if Version(version('zarr')) < Version("3.0.0"): # pragma: no cover
raise ImportError("zarr 3.0.0 or later is required to use the numcodecs zarr integration.")
except ImportError as e: # pragma: no cover
raise ImportError(
Expand All @@ -56,6 +58,23 @@
CODEC_PREFIX = "numcodecs."


def _from_zarr_dtype(dtype: Any) -> np.dtype:
"""
Get a numpy data type from an array spec, depending on the zarr version.
"""
if Version(version('zarr')) >= Version("3.1.0"):
return dtype.to_native_dtype()
return dtype # pragma: no cover


def _to_zarr_dtype(dtype: np.dtype) -> Any:
if Version(version('zarr')) >= Version("3.1.0"):
from zarr.dtype import parse_data_type

return parse_data_type(dtype, zarr_format=3)
return dtype # pragma: no cover


def _expect_name_prefix(codec_name: str) -> str:
if not codec_name.startswith(CODEC_PREFIX):
raise ValueError(
Expand Down Expand Up @@ -224,15 +243,17 @@ class LZMA(_NumcodecsBytesBytesCodec, codec_name="lzma"):
class Shuffle(_NumcodecsBytesBytesCodec, codec_name="shuffle"):
def evolve_from_array_spec(self, array_spec: ArraySpec) -> Shuffle:
if self.codec_config.get("elementsize") is None:
return Shuffle(**{**self.codec_config, "elementsize": array_spec.dtype.itemsize})
dtype = _from_zarr_dtype(array_spec.dtype)
return Shuffle(**{**self.codec_config, "elementsize": dtype.itemsize})
return self # pragma: no cover


# array-to-array codecs ("filters")
class Delta(_NumcodecsArrayArrayCodec, codec_name="delta"):
def resolve_metadata(self, chunk_spec: ArraySpec) -> ArraySpec:
if astype := self.codec_config.get("astype"):
return replace(chunk_spec, dtype=np.dtype(astype)) # type: ignore[call-overload]
dtype = _to_zarr_dtype(np.dtype(astype)) # type: ignore[call-overload]
return replace(chunk_spec, dtype=dtype)
return chunk_spec


Expand All @@ -243,12 +264,14 @@ class BitRound(_NumcodecsArrayArrayCodec, codec_name="bitround"):
class FixedScaleOffset(_NumcodecsArrayArrayCodec, codec_name="fixedscaleoffset"):
def resolve_metadata(self, chunk_spec: ArraySpec) -> ArraySpec:
if astype := self.codec_config.get("astype"):
return replace(chunk_spec, dtype=np.dtype(astype)) # type: ignore[call-overload]
dtype = _to_zarr_dtype(np.dtype(astype)) # type: ignore[call-overload]
return replace(chunk_spec, dtype=dtype)
return chunk_spec

def evolve_from_array_spec(self, array_spec: ArraySpec) -> FixedScaleOffset:
if self.codec_config.get("dtype") is None:
return FixedScaleOffset(**{**self.codec_config, "dtype": str(array_spec.dtype)})
dtype = _from_zarr_dtype(array_spec.dtype)
return FixedScaleOffset(**{**self.codec_config, "dtype": str(dtype)})
return self


Expand All @@ -258,7 +281,8 @@ def __init__(self, **codec_config: JSON) -> None:

def evolve_from_array_spec(self, array_spec: ArraySpec) -> Quantize:
if self.codec_config.get("dtype") is None:
return Quantize(**{**self.codec_config, "dtype": str(array_spec.dtype)})
dtype = _from_zarr_dtype(array_spec.dtype)
return Quantize(**{**self.codec_config, "dtype": str(dtype)})
return self


Expand All @@ -267,21 +291,25 @@ def resolve_metadata(self, chunk_spec: ArraySpec) -> ArraySpec:
return replace(
chunk_spec,
shape=(1 + math.ceil(product(chunk_spec.shape) / 8),),
dtype=np.dtype("uint8"),
dtype=_to_zarr_dtype(np.dtype("uint8")),
)

def validate(self, *, dtype: np.dtype[Any], **_kwargs) -> None:
if dtype != np.dtype("bool"):
_dtype = _from_zarr_dtype(dtype)
if _dtype != np.dtype("bool"):
raise ValueError(f"Packbits filter requires bool dtype. Got {dtype}.")


class AsType(_NumcodecsArrayArrayCodec, codec_name="astype"):
def resolve_metadata(self, chunk_spec: ArraySpec) -> ArraySpec:
return replace(chunk_spec, dtype=np.dtype(self.codec_config["encode_dtype"])) # type: ignore[arg-type]
dtype = _to_zarr_dtype(np.dtype(self.codec_config["encode_dtype"])) # type: ignore[arg-type]
return replace(chunk_spec, dtype=dtype)

def evolve_from_array_spec(self, array_spec: ArraySpec) -> AsType:
if self.codec_config.get("decode_dtype") is None:
return AsType(**{**self.codec_config, "decode_dtype": str(array_spec.dtype)})
# TODO: remove these coverage exemptions the correct way, i.e. with tests
dtype = _from_zarr_dtype(array_spec.dtype) # pragma: no cover
return AsType(**{**self.codec_config, "decode_dtype": str(dtype)}) # pragma: no cover
return self


Expand Down
Loading
Loading