Skip to content

Commit 38d11a3

Browse files
authored
Merge pull request #73 from YosefLab/anndata-0.12.0
anndata>=0.12.0 compatibility
2 parents 68b47b1 + ab21fb5 commit 38d11a3

8 files changed

Lines changed: 99 additions & 16 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
.DS_Store
33
*~
44
buck-out/
5+
*._*
56

67
# Compiled files
78
.venv/

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,27 @@ and this project adheres to [Semantic Versioning][].
88
[keep a changelog]: https://keepachangelog.com/en/1.0.0/
99
[semantic versioning]: https://semver.org/spec/v2.0.0.html
1010

11-
## [Unrealeased]
11+
## [Unreleased]
1212

1313
### Added
1414

1515
### Changed
1616

1717
### Fixed
1818

19+
## [0.2.4] - 2025-11-05
20+
21+
### Added
22+
23+
### Changed
24+
25+
- Deprecated `TreeData.obst_keys` and `TreeData.vart_keys` for consistency with AnnData (https://github.com/scverse/anndata/pull/2093) (#73)
26+
27+
### Fixed
28+
29+
- Added support for zarr v3 (#73)
30+
- Eliminated deprecations warnings from AnnData>=0.12.0 (#73)
31+
1932
## [0.2.3] - 2025-10-14
2033

2134
### Added

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ requires = [ "hatchling" ]
44

55
[project]
66
name = "treedata"
7-
version = "0.2.3"
7+
version = "0.2.4"
88
description = "anndata with trees"
99
readme = "README.md"
1010
license = { file = "LICENSE" }
@@ -33,7 +33,7 @@ dependencies = [
3333
"pathlib",
3434
"pyarrow",
3535
"session-info2",
36-
"zarr>=2,<3",
36+
"zarr>=2",
3737
]
3838
optional-dependencies.dev = [
3939
"pre-commit",

src/treedata/_core/read.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,22 @@ def read_h5ad(
144144
return read_h5td(filename, backed=backed)
145145

146146

147+
def _open_zarr_group(store, *, mode: str = "r") -> tuple[zarr.Group, bool]:
148+
"""Open a Zarr group and signal whether it should be closed."""
149+
if isinstance(store, zarr.Group):
150+
return store, False
151+
return zarr.open(store, mode=mode), True
152+
153+
154+
def _close_zarr_group(group: zarr.Group) -> None:
155+
"""Close the underlying store for an opened Zarr group if needed."""
156+
store = getattr(group, "store", None)
157+
if store is not None:
158+
close = getattr(store, "close", None)
159+
if callable(close):
160+
close()
161+
162+
147163
def read_zarr(store: str | PathLike | MutableMapping | zarr.Group) -> TreeData:
148164
"""Read from a hierarchical Zarr array store.
149165
@@ -152,6 +168,8 @@ def read_zarr(store: str | PathLike | MutableMapping | zarr.Group) -> TreeData:
152168
store
153169
The filename, a :class:`~typing.MutableMapping`, or a Zarr storage class.
154170
"""
155-
with zarr.open(store, mode="r") as f:
156-
d = _read_tdata(f, store, backed=False)
171+
group, should_close = _open_zarr_group(store)
172+
d = _read_tdata(group, store, backed=False)
173+
if should_close:
174+
_close_zarr_group(group)
157175
return TreeData(**d)

src/treedata/_core/treedata.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,21 @@ def _init_as_view(self, tdata_ref: TreeData, oidx: Index1D | None, vidx: Index1D
232232

233233
def obst_keys(self) -> list[str]:
234234
"""List keys of variable annotation `obst`."""
235+
# add future warning
236+
warnings.warn(
237+
"Use obst (e.g. `k in tdata.obst` or `adata.obst.keys()`) instead of TreeData.obst_keys, TreeData.obst_keys is deprecated and will be removed in the future.",
238+
FutureWarning,
239+
stacklevel=2,
240+
)
235241
return list(self._obst.keys())
236242

237243
def vart_keys(self) -> list[str]:
238244
"""List keys of variable annotation `vart`."""
245+
warnings.warn(
246+
"Use vart (e.g. `k in tdata.vart` or `adata.vart.keys()`) instead of TreeData.vart_keys, TreeData.vart_keys is deprecated and will be removed in the future.",
247+
FutureWarning,
248+
stacklevel=2,
249+
)
239250
return list(self._vart.keys())
240251

241252
@property
@@ -496,7 +507,7 @@ def write_h5td(
496507
def write_zarr(
497508
self,
498509
store: MutableMapping | PathLike,
499-
chunks: bool | int | tuple[int, ...] | None = None,
510+
chunks: tuple[int, ...] | None = None,
500511
**kwargs,
501512
):
502513
"""Write a hierarchical Zarr array store.
@@ -510,6 +521,13 @@ def write_zarr(
510521
"""
511522
from .write import write_zarr
512523

524+
if isinstance(chunks, bool):
525+
msg = (
526+
"Passing `write_zarr(adata, chunks=True)` is no longer supported. "
527+
"Please pass `write_zarr(adata)` instead."
528+
)
529+
raise ValueError(msg)
530+
513531
write_zarr(store, self, chunks=chunks)
514532

515533
def to_memory(self, copy=False) -> TreeData:
@@ -520,7 +538,7 @@ def to_memory(self, copy=False) -> TreeData:
520538
copy:
521539
Whether the arrays that are already in-memory should be copied.
522540
"""
523-
adata = super().to_memory(copy)
541+
adata = super().to_memory(copy=copy)
524542
tdata = TreeData(
525543
adata,
526544
obst=self.obst.copy(),

src/treedata/_core/write.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import warnings
55
from collections.abc import MutableMapping
6+
from importlib.metadata import version as get_version
67
from os import PathLike
78
from pathlib import Path
89
from typing import Any, Literal
@@ -18,8 +19,9 @@
1819
from treedata._core.aligned_mapping import AxisTrees
1920
from treedata._core.treedata import TreeData
2021

21-
ANDATA_VERSION = version.parse(ad.__version__)
22-
USE_EXPERIMENTAL = ANDATA_VERSION < version.parse("0.11.0")
22+
ANNDATA_VERSION = version.parse(get_version("anndata"))
23+
USE_EXPERIMENTAL = ANNDATA_VERSION < version.parse("0.11.0")
24+
ZARR_V2 = version.parse(get_version("zarr")) < version.parse("3.0.0")
2325

2426

2527
def _make_serializable(data: Any) -> Any:
@@ -63,7 +65,7 @@ def _serialize_axis_trees(trees: AxisTrees) -> str:
6365
return json.dumps(_make_serializable(d))
6466

6567

66-
def _write_tdata(f, tdata, filename, **kwargs) -> None:
68+
def _write_tdata(f, tdata, filename, chunks=None, **kwargs) -> None:
6769
"""Write TreeData to file."""
6870
# Add encoding type and version
6971
f = f["/"]
@@ -73,7 +75,7 @@ def _write_tdata(f, tdata, filename, **kwargs) -> None:
7375
tdata.strings_to_categoricals()
7476
# Write X if not backed
7577
if not (tdata.isbacked and Path(tdata.filename) == Path(filename)):
76-
_write_elem(f, "X", tdata.X, dataset_kwargs=kwargs)
78+
_write_elem(f, "X", tdata.X, dataset_kwargs=kwargs.update({"chunks": chunks}) if chunks else kwargs)
7779
# Write array elements
7880
for key in ["obs", "var", "label", "allow_overlap", "alignment"]:
7981
_write_elem(f, key, getattr(tdata, key), dataset_kwargs=kwargs)
@@ -152,7 +154,32 @@ def write_h5ad(
152154
)
153155

154156

155-
def write_zarr(filename: MutableMapping | PathLike, tdata: TreeData, **kwargs) -> None:
157+
def _open_zarr_group(
158+
store: MutableMapping | PathLike | zarr.Group,
159+
*,
160+
mode: str = "w",
161+
) -> tuple[zarr.Group, bool]:
162+
"""Open a Zarr group for writing and indicate ownership."""
163+
if isinstance(store, zarr.Group):
164+
return store, False
165+
group_kwargs: dict[str, int] = {}
166+
if not ZARR_V2:
167+
group_kwargs["zarr_format"] = 3
168+
return zarr.open_group(store, mode=mode, **group_kwargs), True
169+
170+
171+
def _close_zarr_group(group: zarr.Group) -> None:
172+
"""Close the underlying store for an opened Zarr group if needed."""
173+
store = getattr(group, "store", None)
174+
if store is not None:
175+
close = getattr(store, "close", None)
176+
if callable(close):
177+
close()
178+
179+
180+
def write_zarr(
181+
filename: MutableMapping | PathLike | zarr.Group, tdata: TreeData, chunks: tuple[int, ...] | None = None, **kwargs
182+
) -> None:
156183
"""Write `.zarr`-formatted zarr file.
157184
158185
Parameters
@@ -164,5 +191,11 @@ def write_zarr(filename: MutableMapping | PathLike, tdata: TreeData, **kwargs) -
164191
kwargs
165192
Additional keyword arguments passed to :func:`zarr.save`.
166193
"""
167-
with zarr.open(filename, mode="w") as f:
168-
_write_tdata(f, tdata, filename, **kwargs)
194+
# if not ZARR_V2 and "zarr_format" not in kwargs:
195+
# f = zarr.open(store=filename, mode="w", zarr_format=3)
196+
# else:
197+
print(kwargs)
198+
f, should_close = _open_zarr_group(filename)
199+
_write_tdata(f, tdata, filename, chunks, **kwargs)
200+
if should_close:
201+
_close_zarr_group(f)

tests/test_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def test_transpose(adata, tree):
246246
treedata_transpose = treedata.transpose()
247247
assert np.array_equal(treedata.X.T, treedata_transpose.X)
248248
assert treedata.obst["tree"].nodes == treedata_transpose.vart["tree"].nodes
249-
assert treedata_transpose.obst_keys() == []
249+
assert list(treedata_transpose.obst.keys()) == []
250250
assert np.array_equal(treedata.obs_names, treedata.T.obs_names)
251251

252252

tests/test_readwrite.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def test_read_anndata(X, tmp_path):
102102
assert tdata.label == "tree"
103103
assert tdata.allow_overlap is True
104104
assert tdata.alignment == "leaves"
105-
assert tdata.obst_keys() == []
105+
assert list(tdata.obst.keys()) == []
106106

107107

108108
def test_read_no_X(X, tmp_path):

0 commit comments

Comments
 (0)