Skip to content

Commit efe0308

Browse files
Add support for numpy 1.25+ (#96)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 8e9cf90 commit efe0308

File tree

8 files changed

+68
-34
lines changed

8 files changed

+68
-34
lines changed

docs/conf.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from pathlib import Path
99
from typing import TYPE_CHECKING
1010

11+
from docutils.nodes import Text
12+
1113

1214
if TYPE_CHECKING:
1315
from docutils.nodes import TextElement, reference
@@ -82,6 +84,7 @@
8284
source_directory="docs/",
8385
)
8486

87+
_np_aliases = {"bool_": "bool"}
8588
_np_nocls = {"float64": "attr"}
8689
_optional_types = {
8790
"CupyArray": "cupy.ndarray",
@@ -94,30 +97,31 @@
9497
}
9598

9699

97-
def find_type_alias(name: str) -> tuple[str, str] | tuple[None, None]:
100+
def find_type_alias(name: str) -> tuple[str, str, str | None] | tuple[None, None, None]:
98101
"""Find a type alias."""
99102
import numpy.typing as npt
100103

101104
from fast_array_utils import types, typing
102105

103106
if name in typing.__all__:
104-
return "data", f"fast_array_utils.typing.{name}"
107+
return "data", f"fast_array_utils.typing.{name}", None
105108
if name.startswith("types.") and name[6:] in {*types.__all__, *_optional_types}:
106109
if path := _optional_types.get(name[6:]):
107-
return "class", path
108-
return "data", f"fast_array_utils.{name}"
110+
return "class", path, None
111+
return "data", f"fast_array_utils.{name}", None
109112
if name.startswith("np."):
110-
return _np_nocls.get(name[3:], "class"), f"numpy.{name[3:]}"
113+
name = _np_aliases.get(name[3:], name[3:])
114+
return _np_nocls.get(name, "class"), f"numpy.{name}", f"np.{name}"
111115
if name in npt.__all__:
112-
return "data", f"numpy.typing.{name}"
113-
return None, None
116+
return "data", f"numpy.typing.{name}", None
117+
return None, None, None
114118

115119

116120
def resolve_type_aliases(app: Sphinx, env: BuildEnvironment, node: pending_xref, contnode: TextElement) -> reference | None:
117121
"""Resolve :class: references to our type aliases as :attr: instead."""
118122
if (node["refdomain"], node["reftype"]) != ("py", "class"):
119123
return None
120-
typ, target = find_type_alias(node["reftarget"])
124+
typ, target, name = find_type_alias(node["reftarget"])
121125
if typ is None or target is None:
122126
return None
123127
if target.startswith("fast_array_utils."):
@@ -131,6 +135,8 @@ def resolve_type_aliases(app: Sphinx, env: BuildEnvironment, node: pending_xref,
131135
if ref is None:
132136
msg = f"Could not resolve {typ} {target} (from {node['reftarget']})"
133137
raise AssertionError(msg)
138+
if name:
139+
ref.children[:] = [Text(name)]
134140
return ref
135141

136142

pyproject.toml

+20-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
[build-system]
22
build-backend = "hatchling.build"
3-
requires = [ "hatch-docstring-description>=1.1.1", "hatch-fancy-pypi-readme", "hatch-vcs", "hatchling" ]
3+
requires = [
4+
"hatch-docstring-description>=1.1.1",
5+
"hatch-fancy-pypi-readme",
6+
"hatch-min-requirements",
7+
"hatch-vcs",
8+
"hatchling",
9+
]
410

511
[project]
612
name = "fast-array-utils"
@@ -18,7 +24,7 @@ classifiers = [
1824
"Programming Language :: Python :: 3.13",
1925
]
2026
dynamic = [ "description", "readme", "version" ]
21-
dependencies = [ "numpy" ]
27+
dependencies = [ "numpy>=1.25.2" ]
2228
optional-dependencies.accel = [ "numba" ]
2329
optional-dependencies.doc = [
2430
"furo",
@@ -29,7 +35,7 @@ optional-dependencies.doc = [
2935
"sphinx-autofixture",
3036
]
3137
optional-dependencies.full = [ "dask", "fast-array-utils[accel,sparse]", "h5py", "zarr" ]
32-
optional-dependencies.sparse = [ "scipy>=1.8" ]
38+
optional-dependencies.sparse = [ "scipy>=1.11" ]
3339
optional-dependencies.test = [
3440
"anndata",
3541
"fast-array-utils[accel,test-min]",
@@ -61,6 +67,7 @@ path = "README.rst"
6167
start-after = ".. begin"
6268

6369
[tool.hatch.metadata.hooks.docstring-description]
70+
[tool.hatch.metadata.hooks.min_requirements]
6471

6572
[tool.hatch.build.targets.wheel]
6673
packages = [ "src/testing", "src/fast_array_utils" ]
@@ -87,11 +94,19 @@ overrides.matrix.extras.dependencies = [
8794
{ if = [ "full" ], value = "scipy-stubs" },
8895
{ if = [ "full" ], value = "scikit-learn" },
8996
]
97+
overrides.matrix.resolution.features = [
98+
{ if = [ "lowest" ], value = "min-reqs" }, # feature added by hatch-min-requirements
99+
]
90100

91101
[[tool.hatch.envs.hatch-test.matrix]]
92102
python = [ "3.13", "3.11" ]
93103
extras = [ "full", "min" ]
94104

105+
[[tool.hatch.envs.hatch-test.matrix]]
106+
python = [ "3.11" ]
107+
extras = [ "full" ]
108+
resolution = [ "lowest" ]
109+
95110
[tool.ruff]
96111
line-length = 160
97112
namespace-packages = [ "src/testing" ]
@@ -124,6 +139,7 @@ lint.per-file-ignores."typings/**/*.pyi" = [ "A002", "F403", "F405", "N801" ] #
124139
lint.allowed-confusables = [ "×", "" ]
125140
lint.flake8-bugbear.extend-immutable-calls = [ "testing.fast_array_utils.Flags" ]
126141
lint.flake8-copyright.notice-rgx = "SPDX-License-Identifier: MPL-2\\.0"
142+
lint.flake8-tidy-imports.banned-api."numpy.bool".msg = "Use `np.bool_` instead for numpy>=1.24<2 compatibility"
127143
lint.flake8-type-checking.exempt-modules = [ ]
128144
lint.flake8-type-checking.strict = true
129145
lint.isort.known-first-party = [ "fast_array_utils" ]
@@ -133,6 +149,7 @@ lint.pydocstyle.convention = "numpy"
133149

134150
[tool.pytest.ini_options]
135151
addopts = [
152+
"-ptesting.fast_array_utils._private",
136153
"--import-mode=importlib",
137154
"--strict-markers",
138155
"--doctest-modules",

src/fast_array_utils/stats/__init__.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
@overload
3030
def is_constant(x: NDArray[Any] | types.CSBase | types.CupyArray, /, *, axis: None = None) -> bool: ...
3131
@overload
32-
def is_constant(x: NDArray[Any] | types.CSBase, /, *, axis: Literal[0, 1]) -> NDArray[np.bool]: ...
32+
def is_constant(x: NDArray[Any] | types.CSBase, /, *, axis: Literal[0, 1]) -> NDArray[np.bool_]: ...
3333
@overload
3434
def is_constant(x: types.CupyArray, /, *, axis: Literal[0, 1]) -> types.CupyArray: ...
3535
@overload
@@ -41,7 +41,7 @@ def is_constant(
4141
/,
4242
*,
4343
axis: Literal[0, 1, None] = None,
44-
) -> bool | NDArray[np.bool] | types.CupyArray | types.DaskArray:
44+
) -> bool | NDArray[np.bool_] | types.CupyArray | types.DaskArray:
4545
"""Check whether values in array are constant.
4646
4747
Parameters
@@ -118,7 +118,7 @@ def mean(
118118
... [0, 0, 0],
119119
... ])
120120
>>> mean(x)
121-
np.float64(0.5)
121+
0.5
122122
>>> mean(x, axis=0)
123123
array([0. , 0.5, 1. ])
124124
>>> mean(x, axis=1)
@@ -184,7 +184,7 @@ def mean_var(
184184
... [0, 0, 0],
185185
... ])
186186
>>> mean_var(x) # doctest: +FLOAT_CMP
187-
(np.float64(0.5), np.float64(0.5833333333333334))
187+
(0.5, 0.5833333333333334)
188188
>>> mean_var(x, axis=0)
189189
(array([0. , 0.5, 1. ]), array([0. , 0.25, 1. ]))
190190
>>> mean_var(x, axis=1)
@@ -251,7 +251,7 @@ def sum(
251251
... [0, 0, 0],
252252
... ])
253253
>>> sum(x)
254-
np.int64(3)
254+
3
255255
>>> sum(x, axis=0)
256256
array([0, 1, 2])
257257
>>> sum(x, axis=1)

src/fast_array_utils/stats/_is_constant.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ def is_constant_(
2525
/,
2626
*,
2727
axis: Literal[0, 1, None] = None,
28-
) -> bool | NDArray[np.bool] | types.CupyArray | types.DaskArray: # pragma: no cover
28+
) -> bool | NDArray[np.bool_] | types.CupyArray | types.DaskArray: # pragma: no cover
2929
raise NotImplementedError
3030

3131

3232
@is_constant_.register(np.ndarray | types.CupyArray) # type: ignore[call-overload,misc]
33-
def _is_constant_ndarray(a: NDArray[Any] | types.CupyArray, /, *, axis: Literal[0, 1, None] = None) -> bool | NDArray[np.bool] | types.CupyArray:
33+
def _is_constant_ndarray(a: NDArray[Any] | types.CupyArray, /, *, axis: Literal[0, 1, None] = None) -> bool | NDArray[np.bool_] | types.CupyArray:
3434
# Should eventually support nd, not now.
3535
match axis:
3636
case None:
@@ -41,13 +41,13 @@ def _is_constant_ndarray(a: NDArray[Any] | types.CupyArray, /, *, axis: Literal[
4141
return _is_constant_rows(a)
4242

4343

44-
def _is_constant_rows(a: NDArray[Any] | types.CupyArray) -> NDArray[np.bool] | types.CupyArray:
44+
def _is_constant_rows(a: NDArray[Any] | types.CupyArray) -> NDArray[np.bool_] | types.CupyArray:
4545
b = np.broadcast_to(a[:, 0][:, np.newaxis], a.shape)
46-
return cast("NDArray[np.bool]", (a == b).all(axis=1))
46+
return cast("NDArray[np.bool_]", (a == b).all(axis=1))
4747

4848

4949
@is_constant_.register(types.CSBase) # type: ignore[call-overload,misc]
50-
def _is_constant_cs(a: types.CSBase, /, *, axis: Literal[0, 1, None] = None) -> bool | NDArray[np.bool]:
50+
def _is_constant_cs(a: types.CSBase, /, *, axis: Literal[0, 1, None] = None) -> bool | NDArray[np.bool_]:
5151
from . import is_constant
5252

5353
if len(a.shape) == 1: # pragma: no cover
@@ -68,9 +68,9 @@ def _is_constant_cs(a: types.CSBase, /, *, axis: Literal[0, 1, None] = None) ->
6868

6969

7070
@numba.njit(cache=True)
71-
def _is_constant_cs_major(a: types.CSBase, shape: tuple[int, int]) -> NDArray[np.bool]:
71+
def _is_constant_cs_major(a: types.CSBase, shape: tuple[int, int]) -> NDArray[np.bool_]:
7272
n = len(a.indptr) - 1
73-
result = np.ones(n, dtype=np.bool)
73+
result = np.ones(n, dtype=np.bool_)
7474
for i in numba.prange(n):
7575
start = a.indptr[i]
7676
stop = a.indptr[i + 1]
@@ -89,7 +89,7 @@ def _is_constant_dask(a: types.DaskArray, /, *, axis: Literal[0, 1, None] = None
8989
from . import is_constant
9090

9191
if axis is not None:
92-
return da.map_blocks(partial(is_constant, axis=axis), a, drop_axis=axis, meta=np.array([], dtype=np.bool))
92+
return da.map_blocks(partial(is_constant, axis=axis), a, drop_axis=axis, meta=np.array([], dtype=np.bool_))
9393

9494
rv = (
9595
(a == a[0, 0].compute()).all()
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-License-Identifier: MPL-2.0
2+
from __future__ import annotations
3+
4+
import numpy as np
5+
import pytest
6+
7+
8+
@pytest.fixture(autouse=True)
9+
def _set_numpy_print() -> None: # TODO(flying-sheep): #97 remove once we depend on numpy >=2
10+
if int(np.__version__.split(".", 1)[0]) > 1:
11+
np.set_printoptions(legacy="1.25")

tests/test_numpy_scipy_sparse.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ def test_copy(
6666
assert mat.indptr.ctypes.data != copied.indptr.ctypes.data
6767
# check that the array contents and dtypes are the same
6868
assert mat.shape == copied.shape
69-
np.testing.assert_equal(copied.toarray(), mat.toarray(), strict=True)
70-
np.testing.assert_equal(copied.data, mat.data, strict=True)
71-
np.testing.assert_equal(copied.indices, mat.indices, strict=not downcasts_idx(mat))
72-
np.testing.assert_equal(copied.indptr, mat.indptr, strict=not downcasts_idx(mat))
69+
np.testing.assert_array_equal(copied.toarray(), mat.toarray(), strict=True)
70+
np.testing.assert_array_equal(copied.data, mat.data, strict=True)
71+
np.testing.assert_array_equal(copied.indices, mat.indices, strict=not downcasts_idx(mat))
72+
np.testing.assert_array_equal(copied.indptr, mat.indptr, strict=not downcasts_idx(mat))
7373

7474

7575
def downcasts_idx(mat: types.CSBase) -> bool:

tests/test_stats.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
Array: TypeAlias = CpuArray | GpuArray | DiskArray | types.CSDataset | types.DaskArray
2626

27-
DTypeIn = np.float32 | np.float64 | np.int32 | np.bool
27+
DTypeIn = np.float32 | np.float64 | np.int32 | np.bool_
2828
DTypeOut = np.float32 | np.float64 | np.int64
2929

3030
NdAndAx: TypeAlias = tuple[Literal[1], Literal[None]] | tuple[Literal[2], Literal[0, 1, None]]
@@ -81,7 +81,7 @@ def axis(ndim_and_axis: NdAndAx) -> Literal[0, 1, None]:
8181
return ndim_and_axis[1]
8282

8383

84-
@pytest.fixture(params=[np.float32, np.float64, np.int32, np.bool])
84+
@pytest.fixture(params=[np.float32, np.float64, np.int32, np.bool_])
8585
def dtype_in(request: pytest.FixtureRequest, array_type: ArrayType) -> type[DTypeIn]:
8686
dtype = cast("type[DTypeIn]", request.param)
8787
inner_cls = array_type.inner.cls if array_type.inner else array_type.cls
@@ -152,7 +152,7 @@ def test_sum(
152152

153153
if dtype_arg is not None:
154154
assert sum_.dtype == dtype_arg, (sum_.dtype, dtype_arg)
155-
elif dtype_in in {np.bool, np.int32}:
155+
elif dtype_in in {np.bool_, np.int32}:
156156
assert sum_.dtype == np.int64
157157
else:
158158
assert sum_.dtype == dtype_in
@@ -210,7 +210,7 @@ def test_mean_var(
210210
mean, var = mean.get(), var.get()
211211

212212
mean_expected = np.mean(np_arr, axis=axis) # type: ignore[arg-type]
213-
var_expected = np.var(np_arr, axis=axis, correction=1) # type: ignore[arg-type]
213+
var_expected = np.var(np_arr, axis=axis, ddof=1) # type: ignore[arg-type]
214214
np.testing.assert_array_equal(mean, mean_expected)
215215
np.testing.assert_array_almost_equal(var, var_expected) # type: ignore[arg-type]
216216

@@ -276,7 +276,7 @@ def test_is_constant(
276276
x = array_type(x_data, dtype=np.float64)
277277
result = stats.is_constant(x, axis=axis)
278278
if isinstance(result, types.DaskArray):
279-
result = cast("NDArray[np.bool] | bool", result.compute())
279+
result = cast("NDArray[np.bool_] | bool", result.compute())
280280
if isinstance(result, types.CupyArray | types.CupyCSMatrix):
281281
result = result.get()
282282
if isinstance(expected, list):

typings/cupy/_core/core.pyi

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class ndarray:
3232
@property
3333
def T(self) -> Self: ... # noqa: N802
3434
@overload
35-
def all(self, axis: None = None) -> np.bool: ...
35+
def all(self, axis: None = None) -> np.bool_: ...
3636
@overload
3737
def all(self, axis: int) -> ndarray: ...
3838
def reshape(self, shape: tuple[int, ...] | int) -> ndarray: ...

0 commit comments

Comments
 (0)