Skip to content

Commit 61fc920

Browse files
committed
parse all
1 parent 05d0900 commit 61fc920

3 files changed

Lines changed: 40 additions & 37 deletions

File tree

src/anndata/acc/__init__.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,29 @@ def resolve(self, spec: str, *, strict: Literal[True] = True) -> P: ...
381381
@overload
382382
def resolve(self, spec: str, *, strict: Literal[False]) -> P | None: ...
383383
def resolve(self, spec: str, *, strict: bool = True) -> P | None:
384-
"""Create accessor from string."""
384+
"""Create accessor from string.
385+
386+
Examples
387+
--------
388+
>>> A.resolve("X[:,:]")
389+
A[:, :]
390+
>>> A.resolve("layers.y[c,:]")
391+
A.layers['y']['c', :]
392+
>>> A.resolve("layers.y[:,g]")
393+
A.layers['y'][:, 'g']
394+
>>> A.resolve("obs.a")
395+
A.obs['a']
396+
>>> A.resolve("var.b")
397+
A.var['b']
398+
>>> A.resolve("obsm.c.0")
399+
A.obsm['c'][:, 0]
400+
>>> A.resolve("varm.d.1")
401+
A.varm['d'][:, 1]
402+
>>> A.resolve("obsp.g[c1,:]")
403+
A.obsp['g']['c1', :]
404+
>>> A.resolve("obsp.g[:,c2]")
405+
A.obsp['g'][:, 'c2']
406+
"""
385407
from ._parse import parse
386408

387409
return parse(self, spec, strict=strict)

src/anndata/acc/_parse.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
from . import GraphAcc, LayerAcc, MetaVecAcc, MultiAcc
77

88
if TYPE_CHECKING:
9+
from collections.abc import Callable
910
from typing import Literal
1011

11-
from . import AdAcc, AdPath, Idx2D
12+
from . import AdAcc, AdPath, GraphVecAcc, Idx2D, LayerVecAcc
1213

1314

1415
@overload
@@ -23,21 +24,19 @@ def parse[P: AdPath](a: AdAcc[P], spec: str, *, strict: bool = True) -> P | None
2324
except ValueError:
2425
return None
2526

27+
if spec.startswith("X["):
28+
return _parse_path_2d(lambda _: a, spec)
2629
if "." not in spec:
2730
msg = f"Cannot parse accessor {spec!r}"
2831
raise ValueError(msg)
29-
acc, rest = spec.split(".", 1)
30-
match getattr(a, acc, None):
31-
# TODO: X
32-
case LayerAcc() as layers:
33-
return _parse_path_layer(layers, rest)
32+
acc_name, rest = spec.split(".", 1)
33+
match getattr(a, acc_name, None):
34+
case LayerAcc() | GraphAcc() as acc:
35+
return _parse_path_2d(acc.__getitem__, rest)
3436
case MetaVecAcc() as meta:
3537
return meta[rest]
3638
case MultiAcc() as multi:
3739
return _parse_path_multi(multi, rest)
38-
case GraphAcc():
39-
msg = "TODO"
40-
raise NotImplementedError(msg)
4140
case None: # pragma: no cover
4241
msg = (
4342
f"Unknown accessor {spec!r}. "
@@ -48,14 +47,17 @@ def parse[P: AdPath](a: AdAcc[P], spec: str, *, strict: bool = True) -> P | None
4847
raise AssertionError(msg) # pragma: no cover
4948

5049

51-
def _parse_path_layer[P: AdPath](layers: LayerAcc[P], spec: str) -> P:
50+
def _parse_path_2d[P: AdPath](
51+
get_vec_acc: Callable[[str], LayerVecAcc[P] | GraphVecAcc[P]], spec: str
52+
) -> P:
5253
if not (
5354
m := re.fullmatch(r"([^\[]+)\[([^,]+),\s?([^\]]+)\]", spec)
5455
): # pragma: no cover
55-
msg = f"Cannot parse layer accessor {spec!r}: should be `name[i,:]`/`name[:,j]`"
56+
msg = f"Cannot parse accessor {spec!r}: should be `name[i,:]`/`name[:,j]`"
5657
raise ValueError(msg)
57-
layer, i, j = m.groups("") # "" just for typing
58-
return layers[layer][_parse_idx_2d(i, j, str)]
58+
name, i, j = m.groups("") # "" just for typing
59+
vec_acc = get_vec_acc(name)
60+
return vec_acc[_parse_idx_2d(i, j, str)]
5961

6062

6163
def _parse_path_multi[P: AdPath](multi: MultiAcc[P], spec: str) -> P:
@@ -68,6 +70,8 @@ def _parse_path_multi[P: AdPath](multi: MultiAcc[P], spec: str) -> P:
6870

6971
def _parse_idx_2d[Idx: int | str](i: str, j: str, cls: type[Idx]) -> Idx2D[Idx]:
7072
match i, j:
73+
case ":", ":":
74+
return slice(None), slice(None)
7175
case _, ":":
7276
return cls(i), slice(None)
7377
case ":", _:

tests/test_accessors.py

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -107,29 +107,6 @@ def test_axes(ad_path: AdPath, axes: Collection[Literal["obs", "var"]]) -> None:
107107
assert ad_path.axes == axes
108108

109109

110-
@pytest.mark.parametrize(
111-
("spec", "expected"),
112-
[
113-
# TODO: pytest.param("???", A[:, :], id="x"),
114-
pytest.param("layers.y[c,:]", A.layers["y"]["c", :], id="layer-obs"),
115-
pytest.param("layers.y[:,g]", A.layers["y"][:, "g"], id="layer-var"),
116-
pytest.param("obs.a", A.obs["a"], id="obs"),
117-
pytest.param("var.b", A.var["b"], id="var"),
118-
pytest.param("obsm.c.0", A.obsm["c"][:, 0], id="obsm"),
119-
pytest.param("varm.d.1", A.varm["d"][:, 1], id="varm"),
120-
pytest.param("obsp.g[c1,:]", A.obsp["d"]["c1", :], id="obsp"),
121-
pytest.param("obsp.g[:,c2]", A.obsp["d"][:, "c2"], id="varp"),
122-
],
123-
)
124-
def test_resolve(spec: str, expected: AdPath) -> None:
125-
try:
126-
assert A.resolve(spec) == expected
127-
assert spec == expected
128-
assert expected != ""
129-
except NotImplementedError:
130-
pytest.xfail("not implemented")
131-
132-
133110
@pytest.mark.parametrize(
134111
"mk_path",
135112
[

0 commit comments

Comments
 (0)