From edfa63ca05f494e5f5145068a636d5b429bea74a Mon Sep 17 00:00:00 2001 From: Ilia Kats Date: Mon, 30 Mar 2026 10:26:55 +0200 Subject: [PATCH 1/3] bump min Python to 3.12, min AnnData to 0.11 --- pyproject.toml | 8 +++----- src/mudata/_core/compat.py | 29 ----------------------------- src/mudata/_core/mudata.py | 3 +-- tests/test_pull_push.py | 6 +++--- tests/test_update.py | 4 ++-- 5 files changed, 9 insertions(+), 41 deletions(-) delete mode 100644 src/mudata/_core/compat.py diff --git a/pyproject.toml b/pyproject.toml index 673c277..c0f6d55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,14 +14,12 @@ maintainers = [ authors = [ { name = "Danila Bredikhin" }, ] -requires-python = ">=3.10" +requires-python = ">=3.12" classifiers = [ "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", @@ -29,7 +27,7 @@ classifiers = [ ] dynamic = [ "version" ] dependencies = [ - "anndata>=0.10.8", + "anndata>=0.11", "h5py", "numpy", "pandas>=1.4", @@ -78,7 +76,7 @@ envs.docs.scripts.open = "python -m webbrowser -t docs/_build/html/index.html" envs.docs.scripts.clean = "git clean -fdX -- {args:docs}" envs.hatch-test.dependency-groups = [ "dev", "test" ] envs.hatch-test.matrix = [ - { deps = [ "stable" ], python = [ "3.11", "3.14" ] }, + { deps = [ "stable" ], python = [ "3.12", "3.14" ] }, { deps = [ "pre" ], python = [ "3.14" ] } ] # If the matrix variable `deps` is set to "pre", diff --git a/src/mudata/_core/compat.py b/src/mudata/_core/compat.py deleted file mode 100644 index cc7f7a0..0000000 --- a/src/mudata/_core/compat.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Mapping - - from anndata import AnnData - from anndata._core.raw import Raw - -try: - from anndata._core.aligned_mapping import AlignedView, AxisArrays, PairwiseArrays -except ImportError: - # anndata < 0.10.9 - from anndata._core.aligned_mapping import AlignedViewMixin as AlignedView - from anndata._core.aligned_mapping import AxisArrays as AxisArraysLegacy - from anndata._core.aligned_mapping import AxisArraysBase - from anndata._core.aligned_mapping import PairwiseArrays as PairwiseArraysLegacy - - class AxisArrays(AxisArraysLegacy): - def __init__(self, parent: AnnData | Raw, axis: int, store: Mapping | AxisArraysBase | None = None): - super().__init__(parent, axis=axis, vals=store) - - class PairwiseArrays(PairwiseArraysLegacy): - def __init__(self, parent: AnnData, axis: int, store: Mapping | None = None): - super().__init__(parent, axis=axis, vals=store) - - -__all__ = ["AlignedView", "AxisArrays", "PairwiseArrays"] diff --git a/src/mudata/_core/mudata.py b/src/mudata/_core/mudata.py index a1edf1d..601831c 100644 --- a/src/mudata/_core/mudata.py +++ b/src/mudata/_core/mudata.py @@ -17,11 +17,10 @@ import numpy as np import pandas as pd from anndata import AnnData -from anndata._core.aligned_mapping import AxisArraysBase +from anndata._core.aligned_mapping import AlignedView, AxisArrays, AxisArraysBase, PairwiseArrays from anndata._core.views import DataFrameView from anndata.utils import convert_to_dict -from .compat import AlignedView, AxisArrays, PairwiseArrays from .config import OPTIONS from .file_backing import MuDataFileManager from .repr import MUDATA_CSS, block_matrix, details_block_table diff --git a/tests/test_pull_push.py b/tests/test_pull_push.py index 64d25ee..f33e363 100644 --- a/tests/test_pull_push.py +++ b/tests/test_pull_push.py @@ -1,5 +1,5 @@ from collections.abc import Callable -from typing import Literal, TypeAlias +from typing import Literal import numpy as np import pandas as pd @@ -8,8 +8,8 @@ from mudata import MuData, set_options -Axis: TypeAlias = Literal[0, 1] -AxisAttr: TypeAlias = Literal["obs", "var"] +type Axis = Literal[0, 1] +type AxisAttr = Literal["obs", "var"] @pytest.fixture(params=(0, 1)) diff --git a/tests/test_update.py b/tests/test_update.py index 938529f..b898121 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -1,6 +1,6 @@ from collections.abc import Mapping, Sequence from functools import reduce -from typing import Literal, TypeAlias +from typing import Literal import numpy as np import pandas as pd @@ -10,7 +10,7 @@ from mudata import MuData, set_options -Axis: TypeAlias = Literal[0, 1] +type Axis = Literal[0, 1] @pytest.fixture From f55e4835be2b3c720882df42deec1a19d750e5b2 Mon Sep 17 00:00:00 2001 From: Ilia Kats Date: Mon, 30 Mar 2026 11:08:18 +0200 Subject: [PATCH 2/3] use public read_elem/write_elem --- src/mudata/_core/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mudata/_core/io.py b/src/mudata/_core/io.py index 40f7146..241fa5f 100644 --- a/src/mudata/_core/io.py +++ b/src/mudata/_core/io.py @@ -21,11 +21,11 @@ from anndata import AnnData from anndata._io.h5ad import _read_raw from anndata._io.h5ad import read_dataframe as read_h5ad_dataframe -from anndata._io.specs.registry import read_elem, write_elem from anndata._io.zarr import _read_legacy_raw from anndata._io.zarr import read_dataframe as read_zarr_dataframe from anndata._io.zarr import write_zarr as anndata_write_zarr from anndata.compat import _read_attr +from anndata.io import read_elem, write_elem from scipy import sparse from .config import OPTIONS From da9e2d34621291aed6b17e12f1ffa2ce0e819ed6 Mon Sep 17 00:00:00 2001 From: Ilia Kats Date: Mon, 30 Mar 2026 11:24:39 +0200 Subject: [PATCH 3/3] use anndata's read_zarr instead of our custom implementation MuData/AnnData read from Zarr cannot be backed, so there is no need for custom handling --- src/mudata/_core/io.py | 38 +++++--------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/src/mudata/_core/io.py b/src/mudata/_core/io.py index 241fa5f..65bb9de 100644 --- a/src/mudata/_core/io.py +++ b/src/mudata/_core/io.py @@ -21,11 +21,11 @@ from anndata import AnnData from anndata._io.h5ad import _read_raw from anndata._io.h5ad import read_dataframe as read_h5ad_dataframe -from anndata._io.zarr import _read_legacy_raw from anndata._io.zarr import read_dataframe as read_zarr_dataframe -from anndata._io.zarr import write_zarr as anndata_write_zarr from anndata.compat import _read_attr from anndata.io import read_elem, write_elem +from anndata.io import read_zarr as anndata_read_zarr +from anndata.io import write_zarr as anndata_write_zarr from scipy import sparse from .config import OPTIONS @@ -197,7 +197,7 @@ def write_zarr( if adata.raw is not None: adata.strings_to_categoricals(adata.raw.var) - if write_data or not adata.isbacked: + if write_data: if chunks is not None and not isinstance(adata.X, sparse.spmatrix): write_elem(group, "X", adata.X, dataset_kwargs=dict(chunks=chunks, **kwargs)) else: @@ -479,15 +479,11 @@ def read_zarr(store: str | PathLike | MutableMapping | zarr.Group | zarr.abc.sto """ import zarr - if isinstance(store, Path): - store = str(store) - f = zarr.open(store, mode="r") d = {} if "mod" not in f.keys(): return ad.read_zarr(store) - manager = MuDataFileManager() for k in f.keys(): if k in {"obs", "var"}: d[k] = read_zarr_dataframe(f[k]) @@ -495,7 +491,7 @@ def read_zarr(store: str | PathLike | MutableMapping | zarr.Group | zarr.abc.sto mods = {} gmods = f[k] for m in gmods.keys(): - mods[m] = _read_zarr_mod(gmods[m], manager) + mods[m] = anndata_read_zarr(gmods[m]) mod_order = None if "mod-order" in gmods.attrs: @@ -508,35 +504,11 @@ def read_zarr(store: str | PathLike | MutableMapping | zarr.Group | zarr.abc.sto d[k] = read_elem(f[k]) mu = MuData._init_from_dict_(**d) - mu.file = manager + mu.file = MuDataFileManager() return mu -def _read_zarr_mod(g: zarr.Group, manager: MuDataFileManager = None, backed: bool = False) -> dict: - d = {} - - for k in g.keys(): - if k in ("obs", "var"): - d[k] = read_zarr_dataframe(g[k]) - elif k == "X": - X = g["X"] - if not backed: - d["X"] = read_elem(X) - elif k != "raw": - d[k] = read_elem(g[k]) - ad = AnnData(**d) - if manager is not None: - ad.file = AnnDataFileManager(ad, Path(g.name).name, manager) - - raw = _read_legacy_raw( - g, d.get("raw"), read_zarr_dataframe, read_elem, attrs=("var", "varm") if backed else ("var", "varm", "X") - ) - if raw: - ad._raw = ad.Raw(ad, **raw) - return ad - - def _read_h5mu_mod(g: h5py.Group, manager: MuDataFileManager = None, backed: bool = False) -> dict: d = {}