Repository: zarr-developers/zarr-python

Issue:
NumPy 2.0 support
NumPy 2.0 is coming out soon ( https://github.com/numpy/numpy/issues/24300 ). NumPy 2.0.0rc1 packages for conda & wheels came out 3 weeks back ( https://github.com/numpy/numpy/issues/24300#issuecomment-2030603395 )

To prepare for NumPy 2.0, it might be worthwhile to start testing against NumPy 2 in CI

NumPy has put out [a migration guide]( https://numpy.org/devdocs/numpy_2_0_migration_guide.html#numpy-2-migration-guide ). More details are in [the release notes]( https://numpy.org/devdocs/release/2.0.0-notes.html )

Also as NumPy is tracking ecosystem support for NumPy 2.0, it would be helpful to share Zarr's current support status in issue (with any plans): https://github.com/numpy/numpy/issues/26191

Discussion:

Patch:
diff --git a/pyproject.toml b/pyproject.toml
index 8244947a..b60b78dc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -92,6 +92,9 @@ version.source = "vcs"
 build.hooks.vcs.version-file = "src/zarr/_version.py"
 
 [tool.hatch.envs.test]
+dependencies = [
+    "numpy~={matrix:numpy}",
+]
 extra-dependencies = [
     "coverage",
     "pytest",
@@ -101,18 +104,19 @@ extra-dependencies = [
     "zstandard",
     "crc32c",
     "pytest-asyncio",
-    "typing_extensions"
+    "typing_extensions",
+    "mypy"
 ]
 features = ["extra"]
 
 [[tool.hatch.envs.test.matrix]]
 python = ["3.10", "3.11"]
-numpy = ["1.24", "1.26"]
+numpy = ["1.24", "1.26", "2.0.0rc1"]
 version = ["minimal"]
 
 [[tool.hatch.envs.test.matrix]]
 python = ["3.10", "3.11"]
-numpy = ["1.24", "1.26"]
+numpy = ["1.24", "1.26", "2.0.0rc1"]
 features = ["optional"]
 
 [tool.hatch.envs.test.scripts]
@@ -120,6 +124,7 @@ run-coverage = "pytest --cov-config=pyproject.toml --cov=pkg --cov=tests"
 run = "run-coverage --no-cov"
 run-verbose = "run-coverage --verbose"
 run-mypy = "mypy src"
+list-env = "pip list"
 
 [tool.hatch.envs.docs]
 features = ['docs']
@@ -167,24 +172,19 @@ disallow_untyped_decorators = true
 disallow_any_generics = true
 
 disallow_incomplete_defs = true
+disallow_untyped_calls = true
 
 [[tool.mypy.overrides]]
 module = [
-    "zarr.v2._storage.store",
-    "zarr.v2._storage.v3_storage_transformers",
+    "zarr.v2.*",
     "zarr.group",
-    "zarr.v2.core",
-    "zarr.v2.hierarchy",
-    "zarr.v2.indexing",
-    "zarr.v2.storage",
-    "zarr.v2.sync",
-    "zarr.v2.util",
     "tests.*",
 ]
 check_untyped_defs = false
 
 [[tool.mypy.overrides]]
 module = [
+    "zarr.v2.*",
     "zarr.abc.codec",
     "zarr.codecs.bytes",
     "zarr.codecs.pipeline",
@@ -193,8 +193,6 @@ module = [
     "zarr.array_v2",
     "zarr.array",
     "zarr.sync",
-    "zarr.v2.convenience",
-    "zarr.v2.meta",
 ]
 disallow_any_generics = false
 
@@ -206,6 +204,20 @@ module = [
 ]
 disallow_incomplete_defs = false
 
+[[tool.mypy.overrides]]
+module = [
+    "zarr.v2.*",
+    "zarr.array_v2",
+    "zarr.array",
+    "zarr.common",
+    "zarr.store.local",
+    "zarr.codecs.blosc",
+    "zarr.codecs.gzip",
+    "zarr.codecs.zstd",
+]
+disallow_untyped_calls = false
+
+
 [tool.pytest.ini_options]
 doctest_optionflags = [
     "NORMALIZE_WHITESPACE",
diff --git a/src/zarr/common.py b/src/zarr/common.py
index 6940ec3f..7d8431f9 100644
--- a/src/zarr/common.py
+++ b/src/zarr/common.py
@@ -79,7 +79,7 @@ class ArraySpec:
     dtype: np.dtype[Any]
     fill_value: Any
 
-    def __init__(self, shape, dtype, fill_value):
+    def __init__(self, shape: ChunkCoords, dtype: np.dtype[Any], fill_value: Any) -> None:
         shape_parsed = parse_shapelike(shape)
         dtype_parsed = parse_dtype(dtype)
         fill_value_parsed = parse_fill_value(fill_value)
diff --git a/src/zarr/indexing.py b/src/zarr/indexing.py
index 75bed633..9f324eb5 100644
--- a/src/zarr/indexing.py
+++ b/src/zarr/indexing.py
@@ -19,7 +19,7 @@ def _err_too_many_indices(selection: SliceSelection, shape: ChunkCoords) -> None
     )
 
 
-def _err_negative_step():
+def _err_negative_step() -> None:
     raise IndexError("only slices with step >= 1 are supported")
 
 
@@ -50,7 +50,7 @@ class _ChunkDimProjection(NamedTuple):
     dim_out_sel: Optional[slice]
 
 
-def _ceildiv(a, b):
+def _ceildiv(a: float, b: float) -> int:
     return math.ceil(a / b)
 
 
diff --git a/src/zarr/v2/meta.py b/src/zarr/v2/meta.py
index ee9cc573..2f2f3b94 100644
--- a/src/zarr/v2/meta.py
+++ b/src/zarr/v2/meta.py
@@ -217,9 +217,9 @@ class Metadata2:
             if v == "NaN":
                 return np.nan
             elif v == "Infinity":
-                return np.PINF
+                return np.inf
             elif v == "-Infinity":
-                return np.NINF
+                return -np.inf
             else:
                 return np.array(v, dtype=dtype)[()]
         elif dtype.kind in "c":


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e767541c..fee73805 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -17,7 +17,7 @@ jobs:
     strategy:
       matrix:
         python-version: ['3.10', '3.11']
-        numpy-version: ['1.24', '1.26']
+        numpy-version: ['1.24', '1.26', '2.0.0rc1']
         dependency-set: ["minimal", "optional"]
 
     steps:
@@ -33,7 +33,8 @@ jobs:
         pip install hatch
     - name: Set Up Hatch Env
       run: |
-        hatch env create
+        hatch env create test.py${{ matrix.python-version }}-${{ matrix.numpy-version }}-${{ matrix.dependency-set }}
+        hatch env run -e test.py${{ matrix.python-version }}-${{ matrix.numpy-version }}-${{ matrix.dependency-set }} list-env
     - name: Run Tests
       run: |
         hatch env run --env test.py${{ matrix.python-version }}-${{ matrix.numpy-version }}-${{ matrix.dependency-set }} run
diff --git a/tests/v2/test_meta.py b/tests/v2/test_meta.py
index e6531adf..b7c00ec6 100644
--- a/tests/v2/test_meta.py
+++ b/tests/v2/test_meta.py
@@ -325,8 +325,8 @@ def test_encode_decode_array_structured():
 def test_encode_decode_fill_values_nan():
     fills = (
         (np.nan, "NaN", np.isnan),
-        (np.NINF, "-Infinity", np.isneginf),
-        (np.PINF, "Infinity", np.isposinf),
+        (-np.inf, "-Infinity", np.isneginf),
+        (np.inf, "Infinity", np.isposinf),
     )
 
     for v, s, f in fills: