Skip to content

Commit 8ec7002

Browse files
authored
Backport PR #2351: fix: check .raw length on setting (#2354)
1 parent 7f67bee commit 8ec7002

4 files changed

Lines changed: 34 additions & 13 deletions

File tree

docs/release-notes/2351.fix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Check `Raw` length on creation and fix associated `.adata` in `Raw` slicing {user}`P Angerer`

src/anndata/_core/anndata.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,7 @@ def _init_as_view(
324324
# set raw, easy, as it’s immutable anyways...
325325
if adata_ref._raw is not None:
326326
# slicing along variables axis is ignored
327-
self._raw = adata_ref.raw[oidx]
328-
self._raw._adata = self
327+
self._raw = adata_ref.raw[self, oidx]
329328
else:
330329
self._raw = None
331330

@@ -764,19 +763,20 @@ def raw(self) -> Raw:
764763
return self._raw
765764

766765
@raw.setter
767-
def raw(self, value: AnnData):
766+
def raw(self, value: AnnData) -> None:
768767
if value is None:
769768
del self.raw
770-
elif not isinstance(value, AnnData):
769+
return
770+
if not isinstance(value, AnnData):
771771
msg = "Can only init raw attribute with an AnnData object."
772772
raise ValueError(msg)
773-
else:
774-
if self.is_view:
775-
self._init_as_actual(self.copy())
776-
self._raw = Raw(self, X=value.X, var=value.var, varm=value.varm)
773+
raw = Raw(self, X=value.X, var=value.var, varm=value.varm)
774+
if self.is_view:
775+
self._init_as_actual(self.copy())
776+
self._raw = raw
777777

778778
@raw.deleter
779-
def raw(self):
779+
def raw(self) -> None:
780780
if self.is_view:
781781
self._init_as_actual(self.copy())
782782
self._raw = None

src/anndata/_core/raw.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ def __init__(
3333
X: np.ndarray | CSMatrix | None = None,
3434
var: pd.DataFrame | Mapping[str, Sequence] | None = None,
3535
varm: AxisArrays | Mapping[str, np.ndarray] | None = None,
36-
):
36+
) -> None:
37+
if X is not None and X.shape[0] != adata.n_obs:
38+
msg = f"X has {X.shape[0]} rows, but n_obs is {adata.n_obs}"
39+
raise ValueError(msg)
40+
3741
self._adata = adata
3842
self._n_obs = adata.n_obs
3943
# construct manually
@@ -121,8 +125,19 @@ def var_names(self) -> pd.Index[str]:
121125
def obs_names(self) -> pd.Index[str]:
122126
return self._adata.obs_names
123127

124-
def __getitem__(self, index: Index) -> Raw:
125-
oidx, vidx = self._normalize_indices(index)
128+
def __getitem__(self, index: Index | tuple[AnnData, Index]) -> Raw:
129+
from .anndata import AnnData
130+
131+
if (
132+
isinstance(index, tuple)
133+
and len(index) == 2
134+
and isinstance(index[0], AnnData)
135+
):
136+
adata, index = index
137+
oidx, vidx = self._normalize_indices(index)
138+
else:
139+
oidx, vidx = self._normalize_indices(index)
140+
adata = self._adata[oidx]
126141

127142
# To preserve two dimensional shape
128143
if isinstance(vidx, int | np.integer):
@@ -133,7 +148,7 @@ def __getitem__(self, index: Index) -> Raw:
133148
X = _subset(self.X, (oidx, vidx)) if not self._adata.isbacked else None
134149

135150
var = self._var.iloc[vidx]
136-
new = Raw(self._adata, X=X, var=var)
151+
new = Raw(adata, X=X, var=var)
137152
if self.varm is not None:
138153
# Since there is no view of raws
139154
new.varm = self.varm._view(_RawViewHack(self, vidx), (vidx,)).copy()

tests/test_raw.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ def test_raw_set_as_none(adata_raw: ad.AnnData):
7171
assert_equal(a, b)
7272

7373

74+
def test_raw_set_error(adata_raw: ad.AnnData) -> None:
75+
with pytest.raises(ValueError, match=r"X has 2 rows, but n_obs is 3"):
76+
adata_raw.raw = adata_raw[:2].copy()
77+
78+
7479
def test_raw_of_view(adata_raw: ad.AnnData):
7580
adata_view = adata_raw[adata_raw.obs["oanno1"] == "cat2"]
7681
assert adata_view.raw.X.tolist() == [

0 commit comments

Comments
 (0)