Skip to content

Commit

Permalink
Deprecate positional boolean arguments (#1694)
Browse files Browse the repository at this point in the history
  • Loading branch information
flying-sheep authored Jan 7, 2025
1 parent 3e2fbd2 commit 21d191d
Show file tree
Hide file tree
Showing 23 changed files with 251 additions and 104 deletions.
10 changes: 8 additions & 2 deletions benchmarks/benchmarks/anndata.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ def track_peakmem_garbage_collection(self, *_):
def display_top(snapshot, key_type="lineno"):
snapshot = snapshot.filter_traces(
(
tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
tracemalloc.Filter(False, "<unknown>"),
tracemalloc.Filter(
inclusive=False,
filename_pattern="<frozen importlib._bootstrap>",
),
tracemalloc.Filter(
inclusive=False,
filename_pattern="<unknown>",
),
)
)
top_stats = snapshot.statistics(key_type)
Expand Down
3 changes: 2 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@
# Generate the API documentation when building
autosummary_generate = True
autodoc_member_order = "bysource"
autodoc_mock_imports = ["torch"]
# autodoc_default_flags = ['members']
issues_github_path = "scverse/anndata"
rtd_links_prefix = PurePosixPath("src")
# autodoc_default_flags = ['members']
napoleon_google_docstring = False
napoleon_numpy_docstring = True
napoleon_include_init_with_doc = False
Expand Down
2 changes: 1 addition & 1 deletion docs/extensions/no_skip_abc_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def autodoc_skip_member(
what: Literal["module", "class", "exception", "function", "method", "attribute"],
name: str,
obj: object,
skip: bool,
skip: bool, # noqa: FBT001
options: Options,
):
if what == "method" and getattr(obj, "__isabstractmethod__", False):
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ dependencies = [
"packaging>=20.0",
# array-api-compat 1.5 has https://github.com/scverse/anndata/issues/1410
"array_api_compat>1.4,!=1.5",
"legacy-api-wrap",
]
dynamic = ["version"]

Expand Down Expand Up @@ -172,6 +173,7 @@ docstring-code-format = true
select = [
"E", # Error detected by Pycodestyle
"F", # Errors detected by Pyflakes
"FBT", # Boolean positional arguments
"W", # Warning detected by Pycodestyle
"PLW", # Pylint
"UP", # pyupgrade
Expand Down Expand Up @@ -205,6 +207,7 @@ required-imports = ["from __future__ import annotations"]
"subprocess.call".msg = "Use `subprocess.run([…])` instead"
"subprocess.check_call".msg = "Use `subprocess.run([…], check=True)` instead"
"subprocess.check_output".msg = "Use `subprocess.run([…], check=True, capture_output=True)` instead"
"legacy_api_wrap.legacy_api".msg = "Use anndata.compat.old_positionals instead"
[tool.ruff.lint.flake8-type-checking]
exempt-modules = []
strict = true
Expand Down
51 changes: 35 additions & 16 deletions src/anndata/_core/anndata.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from .. import utils
from .._settings import settings
from ..compat import DaskArray, SpArray, ZarrArray, _move_adj_mtx
from ..compat import DaskArray, SpArray, ZarrArray, _move_adj_mtx, old_positionals
from ..logging import anndata_logger as logger
from ..utils import (
axis_len,
Expand Down Expand Up @@ -219,12 +219,24 @@ class AnnData(metaclass=utils.DeprecationMixinMeta):
var={"var_names", "col_names", "index"},
)

@old_positionals(
"obsm",
"varm",
"layers",
"raw",
"dtype",
"shape",
"filename",
"filemode",
"asview",
)
def __init__(
self,
X: np.ndarray | sparse.spmatrix | pd.DataFrame | None = None,
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 @@ -234,7 +246,6 @@ 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 = None,
Expand Down Expand Up @@ -470,7 +481,10 @@ def _init_as_actual(
# layers
self.layers = layers

def __sizeof__(self, show_stratified=None, with_disk: bool = False) -> int:
@old_positionals("show_stratified", "with_disk")
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 @@ -1247,7 +1261,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 @@ -1331,8 +1345,8 @@ def var_vector(self, k, *, layer: str | None = None) -> np.ndarray:
layer = None
return get_vector(self, k, "var", "obs", layer=layer)

@utils.deprecated("obs_vector")
def _get_obs_array(self, k, use_raw=False, layer=None):
@deprecated("obs_vector")
def _get_obs_array(self, k, use_raw=False, layer=None): # noqa: FBT002
"""\
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 @@ -1342,8 +1356,8 @@ def _get_obs_array(self, k, use_raw=False, layer=None):
else:
return self.raw.obs_vector(k)

@utils.deprecated("var_vector")
def _get_var_array(self, k, use_raw=False, layer=None):
@deprecated("var_vector")
def _get_var_array(self, k, use_raw=False, layer=None): # noqa: FBT002
"""\
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 @@ -1382,7 +1396,8 @@ def _mutated_copy(self, **kwargs):
new["raw"] = self.raw.copy()
return AnnData(**new)

def to_memory(self, copy=False) -> AnnData:
@old_positionals("copy")
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 @@ -1413,13 +1428,13 @@ def to_memory(self, copy=False) -> AnnData:
]:
attr = getattr(self, attr_name, None)
if attr is not None:
new[attr_name] = to_memory(attr, copy)
new[attr_name] = to_memory(attr, copy=copy)

if self.raw is not None:
new["raw"] = {
"X": to_memory(self.raw.X, copy),
"var": to_memory(self.raw.var, copy),
"varm": to_memory(self.raw.varm, copy),
"X": to_memory(self.raw.X, copy=copy),
"var": to_memory(self.raw.var, copy=copy),
"varm": to_memory(self.raw.varm, copy=copy),
}

if self.isbacked:
Expand Down Expand Up @@ -1875,7 +1890,8 @@ def write_h5ad(

write = write_h5ad # a shortcut and backwards compat

def write_csvs(self, dirname: PathLike, skip_data: bool = True, sep: str = ","):
@old_positionals("skip_data", "sep")
def write_csvs(self, dirname: PathLike, *, skip_data: bool = True, sep: str = ","):
"""\
Write annotation to `.csv` files.
Expand All @@ -1895,7 +1911,8 @@ 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):
@old_positionals("write_obsm_varm")
def write_loom(self, filename: PathLike, *, write_obsm_varm: bool = False):
"""\
Write `.loom`-formatted hdf5 file.
Expand Down Expand Up @@ -1948,9 +1965,11 @@ def chunked_X(self, chunk_size: int | None = None):
if start < n:
yield (self.X[start:n], start, n)

@old_positionals("replace")
def chunk_X(
self,
select: int | Sequence[int] | np.ndarray = 1000,
*,
replace: bool = True,
):
"""\
Expand Down Expand Up @@ -2008,7 +2027,7 @@ def _has_X(self) -> bool:
# --------------------------------------------------------------------------

@property
@utils.deprecated("is_view")
@deprecated("is_view")
def isview(self):
return self.is_view

Expand Down
13 changes: 7 additions & 6 deletions src/anndata/_core/file_backing.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from os import PathLike
from typing import Literal

from .._types import ArrayStorageType
from . import anndata


Expand Down Expand Up @@ -118,7 +119,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 @@ -131,27 +132,27 @@ def to_memory(x, copy=False):

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


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


@to_memory.register(DaskArray)
def _(x, copy=False):
def _(x: DaskArray, *, 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: AwkArray, *, copy: bool = False):
from copy import copy as _copy

if copy:
Expand Down
2 changes: 1 addition & 1 deletion src/anndata/_core/merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def equal_awkward(a, b) -> bool:
return ak.almost_equal(a, b)


def as_sparse(x, use_sparse_array=False):
def as_sparse(x, *, use_sparse_array: bool = False):
if not isinstance(x, sparse.spmatrix | SpArray):
if CAN_USE_SPARSE_ARRAY and use_sparse_array:
return sparse.csr_array(x)
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 @@ -943,7 +943,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
7 changes: 5 additions & 2 deletions src/anndata/_io/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from scipy.sparse import issparse

from .._warnings import WriteWarning
from ..compat import old_positionals
from ..logging import get_logger

if TYPE_CHECKING:
Expand All @@ -21,8 +22,9 @@
logger = get_logger(__name__)


@old_positionals("skip_data", "sep")
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 @@ -75,7 +77,8 @@ def write_csvs(
)


def write_loom(filename: PathLike, adata: AnnData, write_obsm_varm: bool = False):
@old_positionals("write_obsm_varm")
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
14 changes: 12 additions & 2 deletions src/anndata/compat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from collections.abc import Mapping
from contextlib import AbstractContextManager
from dataclasses import dataclass, field
from functools import singledispatch, wraps
from functools import partial, singledispatch, wraps
from importlib.util import find_spec
from inspect import Parameter, signature
from pathlib import Path
Expand Down Expand Up @@ -185,6 +185,16 @@ def __repr__():
return "mock cupy.ndarray"


if find_spec("legacy_api_wrap") or TYPE_CHECKING:
from legacy_api_wrap import legacy_api # noqa: TID251

old_positionals = partial(legacy_api, category=FutureWarning)
else:

def old_positionals(*old_positionals):
return lambda func: func


#############################
# IO helpers
#############################
Expand Down Expand Up @@ -251,7 +261,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
Loading

0 comments on commit 21d191d

Please sign in to comment.