diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0e3c023 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 88 +indent_size = 4 +indent_style = space + +[*.toml] +indent_size = 2 +max_line_length = 120 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b1b0441..d6fd722 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,5 +33,6 @@ repos: - dask - zarr - h5py + - anndata ci: skip: [mypy] # too big diff --git a/.taplo.toml b/.taplo.toml new file mode 100644 index 0000000..420daa4 --- /dev/null +++ b/.taplo.toml @@ -0,0 +1,5 @@ +[formatting] +array_auto_collapse = false +column_width = 120 +compact_arrays = false +indent_string = ' ' diff --git a/.vscode/settings.json b/.vscode/settings.json index 3e7b75f..f6c77c2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,12 +1,20 @@ { - "python.testing.pytestArgs": ["-vv", "--color=yes"], - "python.testing.pytestEnabled": true, + "[toml][json][jsonc][python]": { + "editor.formatOnSave": true, + }, + "[toml]": { + "editor.defaultFormatter": "tamasfe.even-better-toml", + }, + "[json][jsonc]": { + "editor.defaultFormatter": "biomejs.biome", + }, "[python]": { "editor.defaultFormatter": "charliermarsh.ruff", - "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll": "explicit", "source.organizeImports": "explicit", }, }, + "python.testing.pytestArgs": ["-vv", "--color=yes"], + "python.testing.pytestEnabled": true, } diff --git a/biome.jsonc b/biome.jsonc index d85c9a1..e355a01 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -1,9 +1,6 @@ { - "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", - "formatter": { - "indentStyle": "space", - "indentWidth": 4, - }, + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "formatter": { "useEditorconfig": true }, "overrides": [ { "include": ["./.vscode/*.json", "**/*.jsonc"], diff --git a/docs/index.rst b/docs/index.rst index db58c7a..9959889 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,10 +22,3 @@ .. automodule:: fast_array_utils.stats :members: - - -``fast_array_utils.types`` --------------------------- - -.. automodule:: fast_array_utils.types - :members: OutOfCoreDataset diff --git a/pyproject.toml b/pyproject.toml index 8036cf2..37d7098 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,14 @@ optional-dependencies.doc = [ ] optional-dependencies.full = [ "dask", "fast-array-utils[sparse]", "h5py", "zarr" ] optional-dependencies.sparse = [ "scipy>=1.8" ] -optional-dependencies.test = [ "coverage[toml]", "pytest", "pytest-codspeed" ] +optional-dependencies.test = [ + "anndata", + "coverage[toml]", + "packaging", + "pytest", + "pytest-codspeed", + "zarr<3", # anndata needs this +] urls.'Documentation' = "https://icb-fast-array-utils.readthedocs-hosted.com/" urls.'Issue Tracker' = "https://github.com/scverse/fast-array-utils/issues" urls.'Source Code' = "https://github.com/scverse/fast-array-utils" diff --git a/src/fast_array_utils/conv/_to_dense.py b/src/fast_array_utils/conv/_to_dense.py index a32fd49..2dee255 100644 --- a/src/fast_array_utils/conv/_to_dense.py +++ b/src/fast_array_utils/conv/_to_dense.py @@ -2,7 +2,7 @@ from __future__ import annotations from functools import singledispatch -from typing import TYPE_CHECKING, overload +from typing import TYPE_CHECKING, cast, overload import numpy as np @@ -10,38 +10,21 @@ if TYPE_CHECKING: - from typing import Any, Literal + from typing import Any, Literal, TypeAlias from numpy.typing import NDArray - Array = ( - NDArray[Any] - | types.CSBase - | types.CupyArray - | types.CupySparseMatrix - | types.DaskArray - | types.OutOfCoreDataset[Any] - | types.H5Dataset - | types.ZarrArray + MemDiskArray: TypeAlias = ( + NDArray[Any] | types.CSBase | types.H5Dataset | types.ZarrArray | types.CSDataset ) + Array: TypeAlias = MemDiskArray | types.CupyArray | types.CupySparseMatrix | types.DaskArray __all__ = ["to_dense"] @overload -def to_dense( - x: ( - NDArray[Any] - | types.CSBase - | types.OutOfCoreDataset[Any] - | types.H5Dataset - | types.ZarrArray - ), - /, - *, - to_memory: bool = False, -) -> NDArray[Any]: ... +def to_dense(x: MemDiskArray, /, *, to_memory: bool = False) -> NDArray[Any]: ... @overload @@ -103,19 +86,17 @@ def _to_dense_dask( ) -> NDArray[Any] | types.DaskArray: import dask.array as da - x = da.map_blocks(to_dense, x) # type: ignore[arg-type] + x = da.map_blocks(to_dense, x) return x.compute() if to_memory else x # type: ignore[return-value] -@_to_dense.register(types.OutOfCoreDataset) -def _to_dense_ooc( - x: types.OutOfCoreDataset[types.CSBase | NDArray[Any]], /, *, to_memory: bool = False -) -> NDArray[Any]: +@_to_dense.register(types.CSDataset) +def _to_dense_ooc(x: types.CSDataset, /, *, to_memory: bool = False) -> NDArray[Any]: if not to_memory: - msg = "to_memory must be True if x is an OutOfCoreDataset" + msg = "to_memory must be True if x is an CS{R,C}Dataset" raise ValueError(msg) # TODO(flying-sheep): why is to_memory of type Any? # noqa: TD003 - return to_dense(x.to_memory()) + return to_dense(cast("types.CSBase", x.to_memory())) @_to_dense.register(types.CupyArray | types.CupySparseMatrix) # type: ignore[call-overload,misc] diff --git a/src/fast_array_utils/stats/_mean.py b/src/fast_array_utils/stats/_mean.py index a00d616..a31eb6b 100644 --- a/src/fast_array_utils/stats/_mean.py +++ b/src/fast_array_utils/stats/_mean.py @@ -16,7 +16,7 @@ from .. import types - # all supported types except Dask and OutOfCoreDataset (TODO) + # all supported types except Dask and CSDataset (TODO) NonDaskArray = ( NDArray[Any] | types.CSBase diff --git a/src/fast_array_utils/stats/_power.py b/src/fast_array_utils/stats/_power.py index 54625a4..946074d 100644 --- a/src/fast_array_utils/stats/_power.py +++ b/src/fast_array_utils/stats/_power.py @@ -12,7 +12,7 @@ from numpy.typing import NDArray - # All supported array types except for disk ones and OutOfCoreDataset + # All supported array types except for disk ones and CSDataset Array = NDArray[Any] | types.CSBase | types.CupyArray | types.CupySparseMatrix | types.DaskArray _Arr = TypeVar("_Arr", bound=Array) diff --git a/src/fast_array_utils/stats/_sum.py b/src/fast_array_utils/stats/_sum.py index a7df5dc..76cea5a 100644 --- a/src/fast_array_utils/stats/_sum.py +++ b/src/fast_array_utils/stats/_sum.py @@ -16,27 +16,24 @@ from numpy._typing._array_like import _ArrayLikeFloat_co as ArrayLike from numpy.typing import DTypeLike, NDArray - # all supported types except CSBase, Dask and OutOfCoreDataset (TODO) + # all supported types except Dask and CSDataset (TODO) Array = ( - NDArray[Any] | types.H5Dataset | types.ZarrArray | types.CupyArray | types.CupySparseMatrix + NDArray[Any] + | types.CSBase + | types.H5Dataset + | types.ZarrArray + | types.CupyArray + | types.CupySparseMatrix ) @overload def sum( - x: ArrayLike | Array | types.CSBase, - /, - *, - axis: None = None, - dtype: DTypeLike | None = None, + x: ArrayLike | Array, /, *, axis: None = None, dtype: DTypeLike | None = None ) -> np.number[Any]: ... @overload def sum( - x: ArrayLike | Array | types.CSBase, - /, - *, - axis: Literal[0, 1], - dtype: DTypeLike | None = None, + x: ArrayLike | Array, /, *, axis: Literal[0, 1], dtype: DTypeLike | None = None ) -> NDArray[Any]: ... @overload def sum( @@ -45,7 +42,7 @@ def sum( def sum( - x: ArrayLike | Array | types.CSBase | types.DaskArray, + x: ArrayLike | Array | types.DaskArray, /, *, axis: Literal[0, 1, None] = None, @@ -69,7 +66,7 @@ def sum( @singledispatch def _sum( - x: ArrayLike | Array | types.CSBase | types.DaskArray, + x: ArrayLike | Array | types.DaskArray, /, *, axis: Literal[0, 1, None] = None, diff --git a/src/fast_array_utils/types.py b/src/fast_array_utils/types.py index d9b058e..23e3385 100644 --- a/src/fast_array_utils/types.py +++ b/src/fast_array_utils/types.py @@ -4,7 +4,7 @@ from __future__ import annotations from importlib.util import find_spec -from typing import TYPE_CHECKING, Generic, Protocol, TypeVar, runtime_checkable +from typing import TYPE_CHECKING, TypeVar __all__ = [ @@ -13,7 +13,6 @@ "CupySparseMatrix", "DaskArray", "H5Dataset", - "OutOfCoreDataset", "ZarrArray", ] @@ -22,14 +21,10 @@ # scipy sparse if TYPE_CHECKING: - from typing import Any - - import numpy as np from scipy.sparse import csc_array, csc_matrix, csr_array, csr_matrix CSArray = csr_array | csc_array CSMatrix = csr_matrix | csc_matrix - CSBase = CSMatrix | CSArray else: try: # cs?_array isn’t available in older scipy versions from scipy.sparse import csc_array, csr_array @@ -44,8 +39,7 @@ CSMatrix = csr_matrix | csc_matrix except ImportError: # pragma: no cover CSMatrix = type("CSMatrix", (), {}) - - CSBase = CSMatrix | CSArray +CSBase = CSMatrix | CSArray if TYPE_CHECKING or find_spec("cupy"): @@ -70,23 +64,23 @@ if TYPE_CHECKING or find_spec("h5py"): from h5py import Dataset as H5Dataset + from h5py import Group as H5Group else: # pragma: no cover H5Dataset = type("Dataset", (), {}) + H5Group = type("Group", (), {}) if TYPE_CHECKING or find_spec("zarr"): from zarr import Array as ZarrArray + from zarr import Group as ZarrGroup else: # pragma: no cover ZarrArray = type("Array", (), {}) + ZarrGroup = type("Group", (), {}) -@runtime_checkable -class OutOfCoreDataset(Protocol, Generic[T_co]): - """An out-of-core dataset.""" - - shape: tuple[int, int] - dtype: np.dtype[Any] - - def to_memory(self) -> T_co: - """Load data into memory.""" - ... +if TYPE_CHECKING or find_spec("anndata"): + from anndata.abc import CSCDataset, CSRDataset +else: # pragma: no cover + CSRDataset = type("CSRDataset", (), {}) + CSCDataset = type("CSCDataset", (), {}) +CSDataset = CSRDataset | CSCDataset diff --git a/src/testing/fast_array_utils/__init__.py b/src/testing/fast_array_utils/__init__.py index f018895..eb0c037 100644 --- a/src/testing/fast_array_utils/__init__.py +++ b/src/testing/fast_array_utils/__init__.py @@ -9,7 +9,12 @@ if TYPE_CHECKING: - from ._array_type import Array, MemArray, ToArray # noqa: TC004 + from ._array_type import ( + Array, # noqa: TC004 + InnerArrayDask, + InnerArrayDisk, + ToArray, # noqa: TC004 + ) __all__ = [ @@ -34,12 +39,17 @@ ), ) _TP_DASK = tuple( - ArrayType("dask.array", "Array", Flags.Dask | t.flags, inner=t) - for t in cast("tuple[ArrayType[MemArray, None], ...]", _TP_MEM) + ArrayType("dask.array", "Array", Flags.Dask | t.flags, inner=t) # type: ignore[type-var] + for t in cast("tuple[ArrayType[InnerArrayDask, None], ...]", _TP_MEM) ) -_TP_DISK = tuple( +_TP_DISK_DENSE = tuple( ArrayType(m, n, Flags.Any | Flags.Disk) for m, n in [("h5py", "Dataset"), ("zarr", "Array")] ) +_TP_DISK_SPARSE = tuple( + ArrayType("anndata.abc", n, Flags.Any | Flags.Disk | Flags.Sparse, inner=t) # type: ignore[type-var] + for t in cast("tuple[ArrayType[InnerArrayDisk, None], ...]", _TP_DISK_DENSE) + for n in ["CSRDataset", "CSCDataset"] +) -SUPPORTED_TYPES: tuple[ArrayType, ...] = (*_TP_MEM, *_TP_DASK, *_TP_DISK) +SUPPORTED_TYPES: tuple[ArrayType, ...] = (*_TP_MEM, *_TP_DASK, *_TP_DISK_DENSE, *_TP_DISK_SPARSE) """All supported array types.""" diff --git a/src/testing/fast_array_utils/_array_type.py b/src/testing/fast_array_utils/_array_type.py index 481a449..8f6611a 100644 --- a/src/testing/fast_array_utils/_array_type.py +++ b/src/testing/fast_array_utils/_array_type.py @@ -6,28 +6,30 @@ import enum from dataclasses import KW_ONLY, dataclass, field from functools import cached_property +from importlib.metadata import version from typing import TYPE_CHECKING, Generic, TypeVar, cast import numpy as np +from packaging.version import Version if TYPE_CHECKING: - from typing import Any, Literal, Protocol + from typing import Any, Literal, Protocol, TypeAlias import h5py from numpy.typing import ArrayLike, DTypeLike, NDArray from fast_array_utils import types - MemArray = NDArray[Any] | types.CSBase | types.CupyArray | types.CupySparseMatrix - Array = ( - MemArray | types.DaskArray | types.OutOfCoreDataset[Any] | types.H5Dataset | types.ZarrArray - ) + InnerArrayDask = NDArray[Any] | types.CSBase | types.CupyArray | types.CupySparseMatrix + InnerArrayDisk = types.H5Dataset | types.ZarrArray + InnerArray = InnerArrayDask | InnerArrayDisk + Array: TypeAlias = InnerArray | types.DaskArray | types.CSDataset Arr = TypeVar("Arr", bound=Array, default=Array) Arr_co = TypeVar("Arr_co", bound=Array, covariant=True) - Inner = TypeVar("Inner", bound="ArrayType[MemArray, None] | None", default=Any) + Inner = TypeVar("Inner", bound="ArrayType[InnerArray, None] | None", default=Any) class ToArray(Protocol, Generic[Arr_co]): """Convert to a supported array.""" @@ -135,6 +137,10 @@ def cls(self) -> type[Arr]: # noqa: PLR0911 import zarr return cast("type[Arr]", zarr.Array) + case "anndata.abc", ("CSCDataset" | "CSRDataset") as cls_name, _: + import anndata.abc + + return cast("type[Arr]", getattr(anndata.abc, cls_name)) case _: msg = f"Unknown array class: {self}" raise ValueError(msg) @@ -185,6 +191,8 @@ def random( raise NotImplementedError case "zarr", "Array", _: raise NotImplementedError + case "anndata.abc", ("CSCDataset" | "CSRDataset"), _: + raise NotImplementedError case _: msg = f"Unknown array class: {self}" raise ValueError(msg) @@ -205,6 +213,8 @@ def __call__(self, x: ArrayLike, /, *, dtype: DTypeLike | None = None) -> Arr: fn = cast("ToArray[Arr]", self._to_h5py_dataset) elif self.cls is types.ZarrArray: fn = cast("ToArray[Arr]", self._to_zarr_array) + elif self.cls in {types.CSCDataset, types.CSRDataset}: + fn = cast("ToArray[Arr]", self._to_cs_dataset) elif self.cls is types.CupyArray: import cupy as cu @@ -219,6 +229,8 @@ def _to_dask_array(self, x: ArrayLike, /, *, dtype: DTypeLike | None = None) -> import dask.array as da assert self.inner is not None + if TYPE_CHECKING: + assert isinstance(self.inner, ArrayType[InnerArrayDask, None]) # type: ignore[misc] arr = self.inner(x, dtype=dtype) return da.from_array(arr, _half_chunk_size(arr.shape)) @@ -239,10 +251,43 @@ def _to_zarr_array(x: ArrayLike, /, *, dtype: DTypeLike | None = None) -> types. import zarr arr = np.asarray(x, dtype=dtype) - za = zarr.create_array({}, shape=arr.shape, dtype=arr.dtype) + if Version(version("zarr")) >= Version("3"): + za = zarr.create_array({}, shape=arr.shape, dtype=arr.dtype) + else: + za = zarr.create(shape=arr.shape, dtype=arr.dtype) za[...] = arr return za + def _to_cs_dataset(self, x: ArrayLike, /, *, dtype: DTypeLike | None = None) -> types.CSDataset: + """Convert to a scipy sparse dataset.""" + import anndata.io + from scipy.sparse import csc_array, csr_array + + from fast_array_utils import types + + assert self.inner is not None + + grp: types.H5Group | types.ZarrGroup + if self.inner.cls is types.ZarrArray: + import zarr + + grp = zarr.group() + elif self.inner.cls is types.H5Dataset: + if (ctx := self.conversion_context) is None: + msg = "`conversion_context` must be set for h5py" + raise RuntimeError(msg) + grp = ctx.hdf5_file + else: + raise NotImplementedError + + x_sparse = ( + csr_array(np.asarray(x, dtype=dtype)) + if self.cls is types.CSRDataset + else csc_array(np.asarray(x, dtype=dtype)) + ) + anndata.io.write_elem(grp, "/", x_sparse) + return anndata.io.sparse_dataset(grp) + def random_mat( shape: tuple[int, int], diff --git a/src/testing/fast_array_utils/pytest.py b/src/testing/fast_array_utils/pytest.py index d8e1645..9b6da0e 100644 --- a/src/testing/fast_array_utils/pytest.py +++ b/src/testing/fast_array_utils/pytest.py @@ -16,7 +16,7 @@ if TYPE_CHECKING: - from collections.abc import Generator + from collections.abc import Generator, Iterable __all__ = ["array_type", "conversion_context"] @@ -28,8 +28,28 @@ def pytest_configure(config: pytest.Config) -> None: ) -def _resolve_sel(select: Flags = ~Flags(0), skip: Flags = Flags(0)) -> tuple[Flags, Flags]: - return select, skip +def _selected( + array_type: ArrayType, + /, + select: Flags | ArrayType | Iterable[Flags | ArrayType] = ~Flags(0), + skip: Flags | ArrayType | Iterable[Flags | ArrayType] = Flags(0), +) -> bool: + """Check if ``array_type`` matches. + + Returns ``True`` if ``array_type`` matches (one of the conditions in) ``select`` + and does not match (one of the conditions in) ``skip``. + """ + if isinstance(select, Flags | ArrayType): + select = [select] + if isinstance(skip, Flags | ArrayType): + skip = [skip] + + def matches(selector: Flags | ArrayType) -> bool: + if isinstance(selector, ArrayType): + return array_type == selector + return bool(array_type.flags & selector) + + return any(map(matches, select)) and not any(map(matches, skip)) def pytest_collection_modifyitems( @@ -51,8 +71,7 @@ def pytest_collection_modifyitems( if not isinstance(at, ArrayType): msg = f"{msg} of type {ArrayType.__name__}, got {type(at).__name__}" raise TypeError(msg) - select, skip = _resolve_sel(*mark.args, **mark.kwargs) - if not (at.flags & select) or (at.flags & skip): + if not _selected(at, *mark.args, **mark.kwargs): del items[i] @@ -92,7 +111,7 @@ def test_something(array_type: ArrayType) -> None: from fast_array_utils.types import H5Dataset at = cast("ArrayType", request.param) - if at.cls is H5Dataset: + if at.cls is H5Dataset or (at.inner and at.inner.cls is H5Dataset): ctx = request.getfixturevalue("conversion_context") at = dataclasses.replace(at, conversion_context=ctx) return at diff --git a/tests/test_sparse.py b/tests/test_sparse.py index a8ac88d..2ac3089 100644 --- a/tests/test_sparse.py +++ b/tests/test_sparse.py @@ -39,7 +39,7 @@ def dtype(request: pytest.FixtureRequest) -> type[np.float32 | np.float64]: return cast("type[np.float32 | np.float64]", request.param) -@pytest.mark.array_type(select=Flags.Sparse, skip=Flags.Dask) +@pytest.mark.array_type(select=Flags.Sparse, skip=Flags.Dask | Flags.Disk) @pytest.mark.parametrize("order", ["C", "F"]) def test_to_dense( array_type: ArrayType[CSBase, None], @@ -54,7 +54,7 @@ def test_to_dense( @pytest.mark.benchmark -@pytest.mark.array_type(select=Flags.Sparse, skip=Flags.Dask) +@pytest.mark.array_type(select=Flags.Sparse, skip=Flags.Dask | Flags.Disk) @pytest.mark.parametrize("order", ["C", "F"]) def test_to_dense_benchmark( benchmark: BenchmarkFixture, diff --git a/tests/test_stats.py b/tests/test_stats.py index c2dfb5e..589aeb3 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -7,7 +7,7 @@ import pytest from fast_array_utils import stats, types -from testing.fast_array_utils import Flags +from testing.fast_array_utils import SUPPORTED_TYPES, Flags if TYPE_CHECKING: @@ -35,6 +35,10 @@ def __call__( # noqa: D102 ) -> NDArray[Any] | np.number[Any] | types.DaskArray: ... +# can’t select these using a category filter +ATS_SPARSE_DS = {at for at in SUPPORTED_TYPES if at.mod == "anndata.abc"} + + @pytest.fixture(scope="session", params=[0, 1, None]) def axis(request: pytest.FixtureRequest) -> Literal[0, 1, None]: return cast("Literal[0, 1, None]", request.param) @@ -50,6 +54,7 @@ def dtype_arg(request: pytest.FixtureRequest) -> DTypeOut | None: return cast("DTypeOut | None", request.param) +@pytest.mark.array_type(skip=ATS_SPARSE_DS) def test_sum( array_type: ArrayType[Array], dtype_in: DTypeIn, @@ -86,6 +91,7 @@ def test_sum( np.testing.assert_array_equal(sum_, expected) # type: ignore[arg-type] +@pytest.mark.array_type(skip=ATS_SPARSE_DS) @pytest.mark.parametrize(("axis", "expected"), [(None, 3.5), (0, [2.5, 3.5, 4.5]), (1, [2.0, 5.0])]) def test_mean( array_type: ArrayType[Array], axis: Literal[0, 1, None], expected: float | list[float] diff --git a/tests/test_test_utils.py b/tests/test_test_utils.py index 47c4882..f1532e1 100644 --- a/tests/test_test_utils.py +++ b/tests/test_test_utils.py @@ -32,7 +32,11 @@ def test_array_types(array_type: ArrayType) -> None: assert array_type.flags & Flags.Any assert array_type.flags & ~Flags(0) assert not (array_type.flags & Flags(0)) - assert ("sparse" in str(array_type)) == bool(array_type.flags & Flags.Sparse) + assert ("sparse" in str(array_type) or array_type.name in {"CSCDataset", "CSRDataset"}) == bool( + array_type.flags & Flags.Sparse + ) assert ("cupy" in str(array_type)) == bool(array_type.flags & Flags.Gpu) assert ("dask" in str(array_type)) == bool(array_type.flags & Flags.Dask) - assert (array_type.mod in {"zarr", "h5py"}) == bool(array_type.flags & Flags.Disk) + assert any( + getattr(t, "mod", None) in {"zarr", "h5py"} for t in (array_type, array_type.inner) + ) == bool(array_type.flags & Flags.Disk) diff --git a/tests/test_to_dense.py b/tests/test_to_dense.py index 467d9a4..54f3ec3 100644 --- a/tests/test_to_dense.py +++ b/tests/test_to_dense.py @@ -18,7 +18,12 @@ @pytest.mark.parametrize("to_memory", [True, False], ids=["to_memory", "not_to_memory"]) def test_to_dense(array_type: ArrayType[Array], *, to_memory: bool) -> None: x = array_type([[1, 2, 3], [4, 5, 6]]) - arr = to_dense(x, to_memory=to_memory) # type: ignore[arg-type] # https://github.com/python/mypy/issues/14764 + if not to_memory and array_type.cls in {types.CSCDataset, types.CSRDataset}: + with pytest.raises(ValueError, match="to_memory must be True if x is an CS{R,C}Dataset"): + to_dense(x, to_memory=to_memory) + return + + arr = to_dense(x, to_memory=to_memory) match (to_memory, x): case False, types.DaskArray(): assert isinstance(arr, types.DaskArray) diff --git a/typings/h5py.pyi b/typings/h5py.pyi index 2fcb5d1..67bfd50 100644 --- a/typings/h5py.pyi +++ b/typings/h5py.pyi @@ -24,6 +24,7 @@ class File(Group, closing[File]): # not actually a subclass of closing **kw: object, ) -> None: ... def close(self) -> None: ... + def create_group(self, name: str, track_order: bool | None = None) -> Group: ... def create_dataset( self, name: str,