Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix positional boolean APIs #1285

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ select = [
"TCH", # manage type checking blocks
"ICN", # Follow import conventions
"PTH", # Pathlib instead of os.path
"FBT", # No positional parameters that are booleans
]
ignore = [
# line too long -> we accept long comment lines; formatter gets rid of long code lines
Expand All @@ -174,6 +175,8 @@ ignore = [
[tool.ruff.lint.per-file-ignores]
# E721 comparing types, but we specifically are checking that we aren't getting subtypes (views)
"tests/test_readwrite.py" = ["E721"]
# test_* functions use dependency injection and aren’t called
"tests/**/*.py" = ["FBT"]
[tool.ruff.lint.isort]
known-first-party = ["anndata"]
required-imports = ["from __future__ import annotations"]
Expand Down
31 changes: 19 additions & 12 deletions src/anndata/_core/anndata.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ def __init__(
obs: pd.DataFrame | Mapping[str, Iterable[Any]] | None = None,
var: pd.DataFrame | Mapping[str, Iterable[Any]] | None = None,
uns: Mapping[str, Any] | None = None,
*,
obsm: np.ndarray | Mapping[str, Sequence[Any]] | None = None,
varm: np.ndarray | Mapping[str, Sequence[Any]] | None = None,
layers: Mapping[str, np.ndarray | sparse.spmatrix] | None = None,
Expand All @@ -260,11 +261,10 @@ def __init__(
filename: PathLike | None = None,
filemode: Literal["r", "r+"] | None = None,
asview: bool = False,
*,
obsp: np.ndarray | Mapping[str, Sequence[Any]] | None = None,
varp: np.ndarray | Mapping[str, Sequence[Any]] | None = None,
oidx: Index1D = None,
vidx: Index1D = None,
oidx: Index1D | None = None,
vidx: Index1D | None = None,
):
if asview:
if not isinstance(X, AnnData):
Expand Down Expand Up @@ -504,7 +504,9 @@ def _init_as_actual(
# layers
self._layers = Layers(self, layers)

def __sizeof__(self, show_stratified=None, with_disk: bool = False) -> int:
def __sizeof__(
self, *, show_stratified: bool = False, with_disk: bool = False
) -> int:
def get_size(X) -> int:
def cs_to_bytes(X) -> int:
return int(X.data.nbytes + X.indptr.nbytes + X.indices.nbytes)
Expand Down Expand Up @@ -769,7 +771,7 @@ def n_vars(self) -> int:
"""Number of variables/features."""
return len(self.var_names)

def _set_dim_df(self, value: pd.DataFrame, attr: str):
def _set_dim_df(self, value: pd.DataFrame, attr: str) -> None:
if not isinstance(value, pd.DataFrame):
raise ValueError(f"Can only assign pd.DataFrame to {attr}.")
value_idx = self._prep_dim_index(value.index, attr)
Expand Down Expand Up @@ -1320,7 +1322,7 @@ def to_df(self, layer: str | None = None) -> pd.DataFrame:
X = X.toarray()
return pd.DataFrame(X, index=self.obs_names, columns=self.var_names)

def _get_X(self, use_raw=False, layer=None):
def _get_X(self, *, use_raw: bool = False, layer: str | None = None):
"""\
Convenience method for getting expression values
with common arguments and error handling.
Expand Down Expand Up @@ -1372,7 +1374,7 @@ def obs_vector(self, k: str, *, layer: str | None = None) -> np.ndarray:
layer = None
return get_vector(self, k, "obs", "var", layer=layer)

def var_vector(self, k, *, layer: str | None = None) -> np.ndarray:
def var_vector(self, k: str, *, layer: str | None = None) -> np.ndarray:
"""\
Convenience function for returning a 1 dimensional ndarray of values
from :attr:`X`, :attr:`layers`\\ `[k]`, or :attr:`obs`.
Expand Down Expand Up @@ -1405,7 +1407,9 @@ def var_vector(self, k, *, layer: str | None = None) -> np.ndarray:
return get_vector(self, k, "var", "obs", layer=layer)

@utils.deprecated("obs_vector")
def _get_obs_array(self, k, use_raw=False, layer=None):
def _get_obs_array(
self, k, *, use_raw: bool = False, layer: str | None = None
) -> np.ndarray:
"""\
Get an array from the layer (default layer='X') along the :attr:`obs`
dimension by first looking up `obs.keys` and then :attr:`obs_names`.
Expand All @@ -1416,7 +1420,9 @@ def _get_obs_array(self, k, use_raw=False, layer=None):
return self.raw.obs_vector(k)

@utils.deprecated("var_vector")
def _get_var_array(self, k, use_raw=False, layer=None):
def _get_var_array(
self, k: str, *, use_raw: bool = False, layer: str | None = None
) -> np.ndarray:
"""\
Get an array from the layer (default layer='X') along the :attr:`var`
dimension by first looking up `var.keys` and then :attr:`var_names`.
Expand Down Expand Up @@ -1455,7 +1461,7 @@ def _mutated_copy(self, **kwargs):
new["raw"] = self.raw.copy()
return AnnData(**new)

def to_memory(self, copy=False) -> AnnData:
def to_memory(self, *, copy: bool = False) -> AnnData:
"""Return a new AnnData object with all backed arrays loaded into memory.

Params
Expand Down Expand Up @@ -1950,7 +1956,7 @@ def write_h5ad(

write = write_h5ad # a shortcut and backwards compat

def write_csvs(self, dirname: PathLike, skip_data: bool = True, sep: str = ","):
def write_csvs(self, dirname: PathLike, *, skip_data: bool = True, sep: str = ","):
"""\
Write annotation to `.csv` files.

Expand All @@ -1970,7 +1976,7 @@ def write_csvs(self, dirname: PathLike, skip_data: bool = True, sep: str = ","):

write_csvs(dirname, self, skip_data=skip_data, sep=sep)

def write_loom(self, filename: PathLike, write_obsm_varm: bool = False):
def write_loom(self, filename: PathLike, *, write_obsm_varm: bool = False):
"""\
Write `.loom`-formatted hdf5 file.

Expand Down Expand Up @@ -2026,6 +2032,7 @@ def chunked_X(self, chunk_size: int | None = None):
def chunk_X(
self,
select: int | Sequence[int] | np.ndarray = 1000,
*,
replace: bool = True,
):
"""\
Expand Down
12 changes: 6 additions & 6 deletions src/anndata/_core/file_backing.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def is_open(self) -> bool:


@singledispatch
def to_memory(x, copy=False):
def to_memory(x, *, copy: bool = False):
"""Permissivley convert objects to in-memory representation.

If they already are in-memory, (or are just unrecognized) pass a copy through.
Expand All @@ -115,27 +115,27 @@ def to_memory(x, copy=False):

@to_memory.register(ZarrArray)
@to_memory.register(h5py.Dataset)
def _(x, copy=False):
def _(x, *, copy: bool = False):
return x[...]


@to_memory.register(BaseCompressedSparseDataset)
def _(x: BaseCompressedSparseDataset, copy=True):
def _(x: BaseCompressedSparseDataset, *, copy: bool = True):
return x.to_memory()


@to_memory.register(DaskArray)
def _(x, copy=False):
def _(x, *, copy: bool = False):
return x.compute()


@to_memory.register(Mapping)
def _(x: Mapping, copy=False):
def _(x: Mapping, *, copy: bool = False):
return {k: to_memory(v, copy=copy) for k, v in x.items()}


@to_memory.register(AwkArray)
def _(x, copy=False):
def _(x, *, copy: bool = False):
from copy import copy as _copy

if copy:
Expand Down
12 changes: 11 additions & 1 deletion src/anndata/_core/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from collections.abc import Sequence
from functools import singledispatch
from itertools import repeat
from typing import TYPE_CHECKING, Literal

import h5py
import numpy as np
Expand All @@ -12,6 +13,9 @@

from ..compat import AwkArray, DaskArray, Index, Index1D, SpArray

if TYPE_CHECKING:
from .. import AnnData


def _normalize_indices(
index: Index | None, names0: pd.Index, names1: pd.Index
Expand Down Expand Up @@ -204,7 +208,13 @@ def make_slice(idx, dimidx, n=2):
return tuple(mut)


def get_vector(adata, k, coldim, idxdim, layer=None):
def get_vector(
adata: AnnData,
k: str,
coldim: Literal["obs", "var"],
idxdim: Literal["obs", "var"],
layer: str | None = None,
) -> np.ndarray:
# adata could be self if Raw and AnnData shared a parent
dims = ("obs", "var")
col = getattr(adata, coldim).columns
Expand Down
2 changes: 1 addition & 1 deletion src/anndata/_io/specs/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ def read_series(dataset: h5py.Dataset) -> np.ndarray | pd.Categorical:
parent = dataset.parent
categories_dset = parent[_read_attr(dataset.attrs, "categories")]
categories = read_elem(categories_dset)
ordered = bool(_read_attr(categories_dset.attrs, "ordered", False))
ordered = bool(_read_attr(categories_dset.attrs, "ordered", default=False))
return pd.Categorical.from_codes(
read_elem(dataset), categories, ordered=ordered
)
Expand Down
4 changes: 2 additions & 2 deletions src/anndata/_io/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@


def write_csvs(
dirname: PathLike, adata: AnnData, skip_data: bool = True, sep: str = ","
dirname: PathLike, adata: AnnData, *, skip_data: bool = True, sep: str = ","
):
"""See :meth:`~anndata.AnnData.write_csvs`."""
dirname = Path(dirname)
Expand Down Expand Up @@ -73,7 +73,7 @@ def write_csvs(
)


def write_loom(filename: PathLike, adata: AnnData, write_obsm_varm: bool = False):
def write_loom(filename: PathLike, adata: AnnData, *, write_obsm_varm: bool = False):
filename = Path(filename)
row_attrs = {k: np.array(v) for k, v in adata.var.to_dict("list").items()}
row_names = adata.var_names
Expand Down
2 changes: 1 addition & 1 deletion src/anndata/compat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def _from_fixed_length_strings(value):


def _decode_structured_array(
arr: np.ndarray, dtype: np.dtype | None = None, copy: bool = False
arr: np.ndarray, *, dtype: np.dtype | None = None, copy: bool = False
) -> np.ndarray:
"""
h5py 3.0 now reads all strings as bytes. There is a helper method which can convert these to strings,
Expand Down
38 changes: 24 additions & 14 deletions src/anndata/experimental/multi_files/_anncollection.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

import warnings
from collections.abc import Mapping, Sequence
from collections.abc import Iterable, Mapping, Sequence
from functools import reduce
from typing import Callable, Literal, Union
from typing import Any, Callable, Literal, Union

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -117,9 +117,12 @@


class _IterateViewMixin:
shape: tuple[int, int]

def iterate_axis(
self,
batch_size: int,
*,
axis: Literal[0, 1] = 0,
shuffle: bool = False,
drop_last: bool = False,
Expand Down Expand Up @@ -183,7 +186,7 @@
self.dtypes = dtypes
self.obs_names = obs_names

def __getitem__(self, key, use_convert=True):
def __getitem__(self, key: str, *, use_convert: bool = True):
if self._keys is not None and key not in self._keys:
raise KeyError(f"No {key} in {self.attr} view")

Expand Down Expand Up @@ -225,17 +228,19 @@

return _arr

def keys(self):
def keys(self) -> list[str]:
if self._keys is not None:
return self._keys
else:
return list(getattr(self.adatas[0], self.attr).keys())

def to_dict(self, keys=None, use_convert=True):
def to_dict(
self, *, keys: Iterable[str] | None = None, use_convert: bool = True
) -> dict[str, Any]:
dct = {}
keys = self.keys() if keys is None else keys
for key in keys:
dct[key] = self.__getitem__(key, use_convert)
dct[key] = self.__getitem__(key, use_convert=use_convert)

Check warning on line 243 in src/anndata/experimental/multi_files/_anncollection.py

View check run for this annotation

Codecov / codecov/patch

src/anndata/experimental/multi_files/_anncollection.py#L243

Added line #L243 was not covered by tests
return dct

@property
Expand Down Expand Up @@ -293,7 +298,9 @@
self._convert_X = None
self.convert = convert

def _lazy_init_attr(self, attr, set_vidx=False):
def _lazy_init_attr(
self, attr: Literal["obs", "obsm", "layers"], *, set_vidx: bool = False
) -> None:
if getattr(self, f"_{attr}_view") is not None:
return
keys = None
Expand Down Expand Up @@ -504,30 +511,30 @@
return self._convert

@convert.setter
def convert(self, value):
def convert(self, value) -> None:
self._convert = value
self._convert_X = _select_convert("X", self._convert)
for attr in ATTRS:
setattr(self, f"_{attr}_view", None)

def __len__(self):
def __len__(self) -> int:
return len(self.obs_names)

def __getitem__(self, index: Index):
def __getitem__(self, index: Index) -> AnnCollectionView:
oidx, vidx = _normalize_indices(index, self.obs_names, self.var_names)
resolved_idx = self._resolve_idx(oidx, vidx)

return AnnCollectionView(self.reference, self.convert, resolved_idx)

@property
def has_backed(self):
def has_backed(self) -> bool:
"""`True` if the current subset of `adatas` has backed objects, `False` otherwise."""
for i, adata in enumerate(self.adatas):
if adata.isbacked and self.adatas_oidx[i] is not None:
return True
return False

def __repr__(self):
def __repr__(self) -> str:
n_obs, n_vars = self.shape
descr = f"AnnCollectionView object with n_obs × n_vars = {n_obs} × {n_vars}"
all_attrs_keys = self._view_attrs_keys.copy()
Expand All @@ -538,7 +545,9 @@
descr += f"\n {attr}: {str(keys)[1:-1]}"
return descr

def to_adata(self, ignore_X: bool = False, ignore_layers: bool = False):
def to_adata(
self, *, ignore_X: bool = False, ignore_layers: bool = False
) -> AnnData:
"""Convert this AnnCollectionView object to an AnnData object.

Parameters
Expand Down Expand Up @@ -672,6 +681,7 @@
def __init__(
self,
adatas: Sequence[AnnData] | dict[str, AnnData],
*,
join_obs: Literal["inner", "outer"] | None = "inner",
join_obsm: Literal["inner"] | None = None,
join_vars: Literal["inner"] | None = None,
Expand All @@ -681,7 +691,7 @@
convert: ConvertType | None = None,
harmonize_dtypes: bool = True,
indices_strict: bool = True,
):
) -> None:
if isinstance(adatas, Mapping):
if keys is not None:
raise TypeError(
Expand Down
Loading
Loading