Skip to content

Commit f95eeb3

Browse files
authored
Support Python 3.12 and drop 3.9. (#656)
1 parent ee81cdc commit f95eeb3

File tree

13 files changed

+60
-58
lines changed

13 files changed

+60
-58
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ jobs:
66
strategy:
77
matrix:
88
os: [ubuntu-latest]
9-
python: ['3.9', '3.10', '3.11']
9+
python: ['3.10', '3.11', '3.12']
1010
numba_boundscheck: [0]
1111
include:
1212
- os: macos-latest
13-
python: '3.9'
13+
python: '3.10'
1414
- os: windows-latest
15-
python: '3.9'
15+
python: '3.10'
1616
- os: ubuntu-latest
17-
python: '3.9'
17+
python: '3.10'
1818
numba_boundscheck: 1
1919
fail-fast: false
2020
runs-on: ${{ matrix.os }}
@@ -113,7 +113,7 @@ jobs:
113113
activate-environment: sparse-dev
114114
allow-softlinks: true
115115
environment-file: ci/environment.yml
116-
python-version: '3.9'
116+
python-version: '3.10'
117117
miniforge-version: latest
118118
- name: Install asv
119119
run: |

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ description = "Sparse n-dimensional arrays for the PyData ecosystem"
99
readme = "README.rst"
1010
dependencies = ["numpy>=1.17", "numba>=0.49"]
1111
maintainers = [{ name = "Hameer Abbasi", email = "[email protected]" }]
12-
requires-python = ">=3.9"
12+
requires-python = ">=3.10"
1313
license = { file = "LICENSE" }
1414
keywords = ["sparse", "numpy", "scipy", "dask"]
1515
classifiers = [
@@ -18,9 +18,9 @@ classifiers = [
1818
"License :: OSI Approved :: BSD License",
1919
"Programming Language :: Python",
2020
"Programming Language :: Python :: 3",
21-
"Programming Language :: Python :: 3.9",
2221
"Programming Language :: Python :: 3.10",
2322
"Programming Language :: Python :: 3.11",
23+
"Programming Language :: Python :: 3.12",
2424
"Programming Language :: Python :: 3 :: Only",
2525
"Intended Audience :: Developers",
2626
"Intended Audience :: Science/Research",

sparse/pydata_backend/_common.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def check_class_nan(test):
7373
from ._compressed import GCXS
7474
from ._coo import COO
7575

76-
if isinstance(test, (GCXS, COO)):
76+
if isinstance(test, GCXS | COO):
7777
return nan_check(test.fill_value, test.data)
7878
if _is_scipy_sparse_obj(test):
7979
return nan_check(test.data)
@@ -248,7 +248,7 @@ def matmul(a, b):
248248
a = a[(None,) * (b.ndim - a.ndim)]
249249
if a.ndim > b.ndim:
250250
b = b[(None,) * (a.ndim - b.ndim)]
251-
for i, j in zip(a.shape[:-2], b.shape[:-2]):
251+
for i, j in zip(a.shape[:-2], b.shape[:-2], strict=True):
252252
if i != 1 and j != 1 and i != j:
253253
raise ValueError("shapes of a and b are not broadcastable")
254254

@@ -655,11 +655,11 @@ def _dot_csr_csr(out_shape, a_data, b_data, a_indices, b_indices, a_indptr, b_in
655655
head = -2
656656
length = 0
657657
next_[:] = -1
658-
for j, av in zip(
658+
for j, av in zip( # noqa: B905
659659
a_indices[a_indptr[i] : a_indptr[i + 1]],
660660
a_data[a_indptr[i] : a_indptr[i + 1]],
661661
):
662-
for k, bv in zip(
662+
for k, bv in zip( # noqa: B905
663663
b_indices[b_indptr[j] : b_indptr[j + 1]],
664664
b_data[b_indptr[j] : b_indptr[j + 1]],
665665
):
@@ -921,11 +921,11 @@ def _dot_coo_coo(out_shape, a_coords, b_coords, a_data, b_data, a_indptr, b_indp
921921
head = -2
922922
length = 0
923923
next_[:] = -1
924-
for j, av in zip(
924+
for j, av in zip( # noqa: B905
925925
a_coords[1, a_indptr[i] : a_indptr[i + 1]],
926926
a_data[a_indptr[i] : a_indptr[i + 1]],
927927
):
928-
for k, bv in zip(
928+
for k, bv in zip( # noqa: B905
929929
b_coords[1, b_indptr[j] : b_indptr[j + 1]],
930930
b_data[b_indptr[j] : b_indptr[j + 1]],
931931
):
@@ -1425,7 +1425,7 @@ def einsum(*operands, **kwargs):
14251425
sizes = {}
14261426
for t, term in enumerate(terms):
14271427
shape = operands[t].shape
1428-
for ix, d in zip(term, shape):
1428+
for ix, d in zip(term, shape, strict=False):
14291429
if d != sizes.setdefault(ix, d):
14301430
raise ValueError(f"Inconsistent shape for index '{ix}'.")
14311431
total.setdefault(ix, set()).add(t)
@@ -1437,7 +1437,7 @@ def einsum(*operands, **kwargs):
14371437
# we could identify and dispatch to tensordot here?
14381438

14391439
parrays = []
1440-
for term, array in zip(terms, operands):
1440+
for term, array in zip(terms, operands, strict=True):
14411441
# calc the target indices for this term
14421442
pterm = "".join(ix for ix in aligned_term if ix in term)
14431443
if pterm != term:
@@ -1924,7 +1924,7 @@ def moveaxis(a, source, destination):
19241924

19251925
order = [n for n in range(a.ndim) if n not in source]
19261926

1927-
for dest, src in sorted(zip(destination, source)):
1927+
for dest, src in sorted(zip(destination, source, strict=True)):
19281928
order.insert(dest, src)
19291929

19301930
return a.transpose(order)
@@ -2042,7 +2042,7 @@ def asarray(obj, /, *, dtype=None, format="coo", device=None, copy=False):
20422042

20432043
format_dict = {"coo": COO, "dok": DOK, "gcxs": GCXS}
20442044

2045-
if isinstance(obj, (COO, DOK, GCXS)):
2045+
if isinstance(obj, COO | DOK | GCXS):
20462046
return obj.asformat(format)
20472047

20482048
if _is_scipy_sparse_obj(obj):
@@ -2051,7 +2051,7 @@ def asarray(obj, /, *, dtype=None, format="coo", device=None, copy=False):
20512051
dtype = sparse_obj.dtype
20522052
return sparse_obj.astype(dtype=dtype, copy=copy)
20532053

2054-
if np.isscalar(obj) or isinstance(obj, (np.ndarray, Iterable)):
2054+
if np.isscalar(obj) or isinstance(obj, np.ndarray | Iterable):
20552055
sparse_obj = format_dict[format].from_numpy(np.asarray(obj))
20562056
if dtype is None:
20572057
dtype = sparse_obj.dtype
@@ -2068,7 +2068,7 @@ def _support_numpy(func):
20682068

20692069
def wrapper_func(*args, **kwargs):
20702070
x = args[0]
2071-
if isinstance(x, (np.ndarray, np.number)):
2071+
if isinstance(x, np.ndarray | np.number):
20722072
warnings.warn(
20732073
f"Sparse {func.__name__} received dense NumPy array instead "
20742074
"of sparse array. Dispatching to NumPy function.",

sparse/pydata_backend/_compressed/indexing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def get_slicing_selection(arr_data, arr_indices, indptr, starts, ends, col): #
185185
"""
186186
indices = []
187187
ind_list = []
188-
for i, (start, end) in enumerate(zip(starts, ends)):
188+
for i, (start, end) in enumerate(zip(starts, ends)): # noqa: B905
189189
inds = []
190190
current_row = arr_indices[start:end]
191191
if current_row.size < col.size: # linear filtering
@@ -246,7 +246,7 @@ def get_array_selection(arr_data, arr_indices, indptr, starts, ends, col): # pr
246246
"""
247247
indices = []
248248
ind_list = []
249-
for i, (start, end) in enumerate(zip(starts, ends)):
249+
for i, (start, end) in enumerate(zip(starts, ends)): # noqa: B905
250250
inds = []
251251
current_row = arr_indices[start:end]
252252
if len(current_row) == 0:

sparse/pydata_backend/_coo/common.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import warnings
33
from collections.abc import Iterable
44
from functools import reduce
5-
from typing import Any, NamedTuple, Optional
5+
from typing import Any, NamedTuple
66

77
import numba
88

@@ -125,7 +125,7 @@ def kron(a, b):
125125
b_expanded_coords = b.coords[:, b_idx]
126126
o_coords = a_expanded_coords * np.asarray(b.shape)[:, None] + b_expanded_coords
127127
o_data = a.data[a_idx] * b.data[b_idx]
128-
o_shape = tuple(i * j for i, j in zip(a.shape, b.shape))
128+
o_shape = tuple(i * j for i, j in zip(a.shape, b.shape, strict=True))
129129

130130
return COO(o_coords, o_data, shape=o_shape, has_duplicates=False)
131131

@@ -783,7 +783,7 @@ def roll(a, shift, axis=None):
783783
# shift elements
784784
coords, data = np.copy(a.coords), np.copy(a.data)
785785
try:
786-
for sh, ax in zip(shift, axis):
786+
for sh, ax in zip(shift, axis, strict=True):
787787
coords[ax] += sh
788788
coords[ax] %= a.shape[ax]
789789
except UFuncTypeError as e:
@@ -1462,7 +1462,7 @@ def _compute_minmax_args(
14621462

14631463
def _arg_minmax_common(
14641464
x: SparseArray,
1465-
axis: Optional[int],
1465+
axis: int | None,
14661466
keepdims: bool,
14671467
mode: str,
14681468
):
@@ -1474,7 +1474,7 @@ def _arg_minmax_common(
14741474

14751475
x = _validate_coo_input(x)
14761476

1477-
if not isinstance(axis, (int, type(None))):
1477+
if not isinstance(axis, int | type(None)):
14781478
raise ValueError(f"`axis` must be `int` or `None`, but it's: {type(axis)}.")
14791479
if isinstance(axis, int) and axis >= x.ndim:
14801480
raise ValueError(f"`axis={axis}` is out of bounds for array of dimension {x.ndim}.")
@@ -1483,7 +1483,7 @@ def _arg_minmax_common(
14831483

14841484
# If `axis` is None then we need to flatten the input array and memorize
14851485
# the original dimensionality for the final reshape operation.
1486-
axis_none_original_ndim: Optional[int] = None
1486+
axis_none_original_ndim: int | None = None
14871487
if axis is None:
14881488
axis_none_original_ndim = x.ndim
14891489
x = x.reshape(-1)[:, None]

sparse/pydata_backend/_coo/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1577,7 +1577,7 @@ def as_coo(x, shape=None, fill_value=None, idx_dtype=None):
15771577
if _is_scipy_sparse_obj(x):
15781578
return COO.from_scipy_sparse(x)
15791579

1580-
if isinstance(x, (Iterable, Iterator)):
1580+
if isinstance(x, Iterable | Iterator):
15811581
return COO.from_iter(x, shape=shape, fill_value=fill_value)
15821582

15831583
raise NotImplementedError(

sparse/pydata_backend/_coo/indexing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def _prune_indices(indices, shape, prune_none=True):
238238
indices = [idx for idx in indices if idx is not None]
239239

240240
i = 0
241-
for idx, sh in zip(indices[::-1], shape[::-1]):
241+
for idx, sh in zip(indices[::-1], shape[::-1], strict=True):
242242
if not isinstance(idx, slice):
243243
break
244244

sparse/pydata_backend/_dok.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def from_coo(cls, x):
181181
"""
182182
ar = cls(x.shape, dtype=x.dtype, fill_value=x.fill_value)
183183

184-
for c, d in zip(x.coords.T, x.data):
184+
for c, d in zip(x.coords.T, x.data, strict=True):
185185
ar.data[tuple(c)] = d
186186

187187
return ar
@@ -235,7 +235,7 @@ def from_numpy(cls, x):
235235
coords = np.nonzero(x)
236236
data = x[coords]
237237

238-
for c in zip(data, *coords):
238+
for c in zip(data, *coords, strict=True):
239239
d, c = c[0], c[1:]
240240
ar.data[c] = d
241241

@@ -338,7 +338,7 @@ def __getitem__(self, key):
338338
def _fancy_getitem(self, key):
339339
"""Subset of fancy indexing, when all dimensions are accessed"""
340340
new_data = {}
341-
for i, k in enumerate(zip(*key)):
341+
for i, k in enumerate(zip(*key, strict=True)):
342342
if k in self.data:
343343
new_data[i] = self.data[k]
344344
return DOK(
@@ -352,7 +352,7 @@ def __setitem__(self, key, value):
352352
value = np.asarray(value, dtype=self.dtype)
353353

354354
# 1D fancy indexing
355-
if self.ndim == 1 and isinstance(key, Iterable) and all(isinstance(i, (int, np.integer)) for i in key):
355+
if self.ndim == 1 and isinstance(key, Iterable) and all(isinstance(i, int | np.integer) for i in key):
356356
key = (key,)
357357

358358
if isinstance(key, tuple) and all(isinstance(k, Iterable) for k in key):
@@ -383,7 +383,7 @@ def _fancy_setitem(self, idxs, values):
383383
raise ValueError(f"Shape mismatch of indices ({idxs[0].shape}) and values ({values.shape})!")
384384
fill_value = self.fill_value
385385
data = self.data
386-
for idx, value in zip(zip(*idxs), values):
386+
for idx, value in zip(zip(*idxs, strict=True), values, strict=True):
387387
if value != fill_value:
388388
data[idx] = value
389389
elif idx in data:

sparse/pydata_backend/_slicing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def normalize_index(idx, shape):
5353
else:
5454
none_shape.append(None)
5555

56-
for i, d in zip(idx, none_shape):
56+
for i, d in zip(idx, none_shape, strict=True):
5757
if d is not None:
5858
check_index(i, d)
5959
idx = tuple(map(sanitize_index, idx))
@@ -206,7 +206,7 @@ def posify_index(shape, ind):
206206
return ind + shape
207207

208208
return ind
209-
if isinstance(ind, (np.ndarray, list)) and not math.isnan(shape):
209+
if isinstance(ind, np.ndarray | list) and not math.isnan(shape):
210210
ind = np.asanyarray(ind)
211211
return np.where(ind < 0, ind + shape, ind)
212212
if isinstance(ind, slice):

sparse/pydata_backend/_sparse_array.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
import operator
33
import warnings
44
from abc import ABCMeta, abstractmethod
5-
from collections.abc import Iterable
5+
from collections.abc import Callable, Iterable
66
from functools import reduce
77
from numbers import Integral
8-
from typing import Callable
98

109
import numpy as np
1110

@@ -695,7 +694,7 @@ def mean(self, axis=None, keepdims=False, dtype=None, out=None):
695694
den = reduce(operator.mul, (self.shape[i] for i in axis), 1)
696695

697696
if dtype is None:
698-
if issubclass(self.dtype.type, (np.integer, np.bool_)):
697+
if issubclass(self.dtype.type, np.integer | np.bool_):
699698
dtype = inter_dtype = np.dtype("f8")
700699
else:
701700
dtype = self.dtype
@@ -786,7 +785,7 @@ def var(self, axis=None, dtype=None, out=None, ddof=0, keepdims=False):
786785
warnings.warn("Degrees of freedom <= 0 for slice", RuntimeWarning, stacklevel=1)
787786

788787
# Cast bool, unsigned int, and int to float64 by default
789-
if dtype is None and issubclass(self.dtype.type, (np.integer, np.bool_)):
788+
if dtype is None and issubclass(self.dtype.type, np.integer | np.bool_):
790789
dtype = np.dtype("f8")
791790

792791
arrmean = self.sum(axis, dtype=dtype, keepdims=True)

0 commit comments

Comments
 (0)