From 2f0959ae12b328c4b6c24e7fa5b071ad13c885ab Mon Sep 17 00:00:00 2001 From: colganwi Date: Wed, 8 Jul 2026 13:08:50 -0400 Subject: [PATCH 1/2] Compatibility with anndata 0.13 Delegate anndata.acc accessor indexers (AdRef, MapAcc, RefAcc) from TreeData.__getitem__ to AnnData.__getitem__ instead of routing them through _normalize_indices, fixing an "IndexError: Unknown indexer" from obs_vector/var_vector under anndata >= 0.13. Bump the floors to anndata>=0.13 and zarr>=3 (anndata 0.13 requires Python >=3.12 and no longer supports zarr <3), and remove the now-dead anndata<0.11 (USE_EXPERIMENTAL) and zarr<3 (ZARR_V2) code branches. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 6 +++++- pyproject.toml | 7 +++---- src/treedata/_core/read.py | 10 +--------- src/treedata/_core/treedata.py | 5 +++++ src/treedata/_core/write.py | 18 ++---------------- 5 files changed, 16 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 827023b..2916b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,12 +16,15 @@ and this project adheres to [Semantic Versioning][]. ### Fixed -## [0.3.0] - 2026-07-07 + +## [0.3.0] - 2026-07-08 ### Added ### Changed +- Require `anndata>=0.13` (and consequently Python `>=3.12`, which anndata 0.13 requires) (#88) +- Require `zarr>=3` (anndata 0.13 no longer supports zarr <3). Removed the dead `zarr<3` (`ZARR_V2`) code branches and the dead `anndata<0.11` (`USE_EXPERIMENTAL`) code branches now that the supported floors exceed them (#88) - Zarr writes now store `obst`/`vart` in a columnar layout (one array per node/edge attribute key) instead of a single JSON scalar. Dense numeric/boolean columns use native dtypes; ragged or complex columns fall back to per-element JSON. This fixes a `TypeError: string too large to store inside array` crash when writing large trees to zarr v3 (#86) - `dtype` parameter in `TreeData.__init__` is now deprecated; passing it raises a `FutureWarning` and has no effect, matching anndata's removal of this argument - `obsm`, `varm`, `layers`, `raw`, `shape`, `filename`, `filemode`, `asview`, and related parameters in `TreeData.__init__` are now keyword-only, matching the anndata 0.13.x API @@ -30,6 +33,7 @@ and this project adheres to [Semantic Versioning][]. ### Fixed +- `TreeData.__getitem__` now delegates `anndata.acc` accessor indexers (`AdRef`, `MapAcc`, `RefAcc`) to `AnnData.__getitem__` instead of routing them through `_normalize_indices`. This fixes an `IndexError: Unknown indexer` from `obs_vector`/`var_vector` (and any `var_names`/layer column lookup) under anndata ≥ 0.13 (#88) - Fixed `UserWarning: Duplicate name: 'zarr.json'` spam when writing to a zarr `ZipStore` by staging the write in a `MemoryStore` and copying to the `ZipStore` once (#86) - `read_zarr` now auto-detects `.zip` paths and opens them as a `ZipStore` (#86) - Node/edge attributes explicitly set to `None` are now preserved through a zarr round-trip and kept distinct from absent attributes (#86) diff --git a/pyproject.toml b/pyproject.toml index 7ec15d6..86e05b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,16 +14,15 @@ maintainers = [ authors = [ { name = "William Colgan" }, ] -requires-python = ">=3.11" +requires-python = ">=3.12" classifiers = [ "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] dependencies = [ - "anndata>=0.11", + "anndata>=0.13", "h5py", "networkx", "numpy", @@ -31,7 +30,7 @@ dependencies = [ "pandas", "pyarrow", "session-info2", - "zarr>=2", + "zarr>=3", ] urls.Documentation = "https://treedata.readthedocs.io/" urls.Homepage = "https://github.com/colganwi/treedata" diff --git a/src/treedata/_core/read.py b/src/treedata/_core/read.py index a90ef2d..75e20b7 100755 --- a/src/treedata/_core/read.py +++ b/src/treedata/_core/read.py @@ -3,7 +3,6 @@ import json import warnings from collections.abc import MutableMapping -from importlib.metadata import version as get_version from os import PathLike from typing import Literal @@ -11,23 +10,16 @@ import h5py import networkx as nx import zarr -from packaging import version from treedata._core.treedata import TreeData -ANNDATA_VERSION = version.parse(get_version("anndata")) -USE_EXPERIMENTAL = ANNDATA_VERSION < version.parse("0.11.0") - # Sentinel distinguishing an absent attribute from an attribute explicitly set to ``None``. _MISSING = object() def _read_elem(elem): """Read an element from a store.""" - if USE_EXPERIMENTAL: - return ad.experimental.read_elem(elem) - else: - return ad.io.read_elem(elem) + return ad.io.read_elem(elem) def _dict_to_digraph(graph_dict: dict) -> nx.DiGraph: diff --git a/src/treedata/_core/treedata.py b/src/treedata/_core/treedata.py index 55916a0..ee17f2e 100755 --- a/src/treedata/_core/treedata.py +++ b/src/treedata/_core/treedata.py @@ -397,6 +397,11 @@ def __repr__(self) -> str: def __getitem__(self, index: Any) -> TreeData: """Returns a sliced view of the object.""" + from anndata.acc import AdRef, MapAcc, RefAcc + + if isinstance(index, AdRef | MapAcc | RefAcc): + # Accessor indexers retrieve an array/vector, not a TreeData view. + return super().__getitem__(index) oidx, vidx = self._normalize_indices(index) return TreeData(self, oidx=oidx, vidx=vidx, asview=True) diff --git a/src/treedata/_core/write.py b/src/treedata/_core/write.py index e224397..77a2f8c 100755 --- a/src/treedata/_core/write.py +++ b/src/treedata/_core/write.py @@ -3,7 +3,6 @@ import json import warnings from collections.abc import MutableMapping -from importlib.metadata import version as get_version from os import PathLike from pathlib import Path from typing import Any, Literal @@ -14,15 +13,10 @@ import numpy as np import pandas as pd import zarr -from packaging import version from treedata._core.aligned_mapping import AxisTrees from treedata._core.treedata import TreeData -ANNDATA_VERSION = version.parse(get_version("anndata")) -USE_EXPERIMENTAL = ANNDATA_VERSION < version.parse("0.11.0") -ZARR_V2 = version.parse(get_version("zarr")) < version.parse("3.0.0") - # Sentinel distinguishing an absent attribute from an attribute explicitly set to ``None``. _MISSING = object() @@ -45,10 +39,7 @@ def _make_serializable(data: Any) -> Any: def _write_elem(f, k, elem, *, dataset_kwargs) -> None: """Write an element to a storage group using anndata encoding.""" - if USE_EXPERIMENTAL: - ad.experimental.write_elem(f, k, elem, dataset_kwargs=dataset_kwargs) - else: - ad.io.write_elem(f, k, elem, dataset_kwargs=dataset_kwargs) + ad.io.write_elem(f, k, elem, dataset_kwargs=dataset_kwargs) def _digraph_to_dict(G: nx.DiGraph) -> dict: @@ -229,10 +220,7 @@ def _open_zarr_group( """Open a Zarr group for writing and indicate ownership.""" if isinstance(store, zarr.Group): return store, False - group_kwargs: dict[str, int] = {} - if not ZARR_V2: - group_kwargs["zarr_format"] = 3 - return zarr.open_group(store, mode=mode, **group_kwargs), True + return zarr.open_group(store, mode=mode, zarr_format=3), True def _close_zarr_group(group: zarr.Group) -> None: @@ -246,8 +234,6 @@ def _close_zarr_group(group: zarr.Group) -> None: def _is_zip_store(store: Any) -> bool: """Return True if store is or resolves to a zarr ZipStore.""" - if ZARR_V2: - return False return isinstance(store, zarr.storage.ZipStore) or ( isinstance(store, (str, PathLike)) and str(store).endswith(".zip") ) From 78eb2be5791da5ff32da6b4b0c791af933f23abe Mon Sep 17 00:00:00 2001 From: colganwi Date: Wed, 8 Jul 2026 13:16:24 -0400 Subject: [PATCH 2/2] updated test to python 3.12 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 86e05b0..095614b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ envs.docs.scripts.open = "python -m webbrowser -t docs/_build/html/index.html" envs.docs.dependency-groups = [ "doc" ] envs.hatch-test.matrix = [ # Test the lowest and highest supported Python versions with normal deps - { deps = [ "stable" ], python = [ "3.11", "3.14" ] }, + { deps = [ "stable" ], python = [ "3.12", "3.14" ] }, # Test the newest supported Python version also with pre-release deps { deps = [ "pre" ], python = [ "3.14" ] }, ]