Skip to content

Commit

Permalink
Merge branch 'main' into no-positional-bools
Browse files Browse the repository at this point in the history
  • Loading branch information
flying-sheep committed Jan 7, 2025
2 parents 1021b1f + 4bcd989 commit 298fd0c
Show file tree
Hide file tree
Showing 15 changed files with 151 additions and 51 deletions.
8 changes: 2 additions & 6 deletions .azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,7 @@ jobs:
path: $(uv_cache_dir)
displayName: Cache pip packages

# This prevents broken and slow back-tracking in dependency resolution
- script: printf "llvmlite>=0.43\nscanpy>=1.10.0rc1" | tee /tmp/constraints.txt
displayName: "Create constraints file for `pre-release` and `latest` jobs"

- script: uv pip install --system --compile "anndata[dev,test] @ ." -c /tmp/constraints.txt
- script: uv pip install --system --compile "anndata[dev,test] @ ." -c ci/constraints.txt
displayName: "Install dependencies"
condition: eq(variables['DEPENDENCIES_VERSION'], 'latest')

Expand All @@ -65,7 +61,7 @@ jobs:
displayName: "Install minimum dependencies"
condition: eq(variables['DEPENDENCIES_VERSION'], 'minimum')
- script: uv pip install -v --system --compile --pre "anndata[dev,test] @ ." -c /tmp/constraints.txt
- script: uv pip install -v --system --compile --pre "anndata[dev,test] @ ." -c ci/constraints.txt
displayName: "Install dependencies release candidates"
condition: eq(variables['DEPENDENCIES_VERSION'], 'pre-release')

Expand Down
12 changes: 10 additions & 2 deletions .github/ISSUE_TEMPLATE/bug-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,18 @@ body:
description: |
Which version of anndata and other related software you used.
Please install `session-info`, run the following command, then paste the results into the text box below.
Please install `session-info2`, run the following command in a notebook,
click the “Copy as Markdown” button,
then paste the results into the text box below.
```python
>>> import anndata, session_info; session_info.show(html=False, dependencies=True)
In[1]: import anndata, session_info2; session_info2.session_info(dependencies=True)
```
Alternatively, run this in a console:
```python
>>> import session_info2; print(session_info2.session_info(dependencies=True)._repr_mimebundle_()["text/markdown"])
```
render: python
validations:
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/test-gpu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,7 @@ jobs:
cache-dependency-path: pyproject.toml

- name: Install AnnData
run: |
printf "llvmlite>=0.43\nscanpy>=1.10.0rc1" | tee /tmp/constraints.txt
uv pip install --system -e ".[dev,test,cu12]" -c /tmp/constraints.txt
run: uv pip install --system -e ".[dev,test,cu12]" -c ci/constraints.txt

- name: Env list
run: pip list
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ __pycache__/

# Distribution / packaging
/dist/
/ci/min-deps.txt
/src/anndata/_version.py
/requirements*.lock
/.python-version
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.2
rev: v0.8.6
hooks:
- id: ruff
types_or: [python, pyi, jupyter]
Expand Down
1 change: 1 addition & 0 deletions ci/constraints.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
numba>=0.56
108 changes: 85 additions & 23 deletions ci/scripts/min-deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import argparse
import sys
from collections import deque
from contextlib import ExitStack
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING

Expand All @@ -23,7 +25,9 @@
from packaging.version import Version

if TYPE_CHECKING:
from collections.abc import Generator, Iterable
from collections.abc import Generator, Iterable, Sequence
from collections.abc import Set as AbstractSet
from typing import Any, Self


def min_dep(req: Requirement) -> Requirement:
Expand Down Expand Up @@ -75,34 +79,92 @@ def extract_min_deps(
yield min_dep(req)


def main():
parser = argparse.ArgumentParser(
prog="min-deps",
description="""Parse a pyproject.toml file and output a list of minimum dependencies.
Output is directly passable to `pip install`.""",
usage="pip install `python min-deps.py pyproject.toml`",
)
parser.add_argument(
"path", type=Path, help="pyproject.toml to parse minimum dependencies from"
)
parser.add_argument(
"--extras", type=str, nargs="*", default=(), help="extras to install"
)

args = parser.parse_args()

pyproject = tomllib.loads(args.path.read_text())
class Args(argparse.Namespace):
"""\
Parse a pyproject.toml file and output a list of minimum dependencies.
Output is optimized for `[uv] pip install` (see `-o`/`--output` for details).
"""

project_name = pyproject["project"]["name"]
_path: Path
output: Path | None
_extras: list[str]
_all_extras: bool

@classmethod
def parse(cls, argv: Sequence[str] | None = None) -> Self:
return cls.parser().parse_args(argv, cls())

@classmethod
def parser(cls) -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="min-deps",
description=cls.__doc__,
usage="pip install `python min-deps.py pyproject.toml`",
)
parser.add_argument(
"_path",
metavar="pyproject.toml",
type=Path,
help="Path to pyproject.toml to parse minimum dependencies from",
)
parser.add_argument(
"--extras",
dest="_extras",
metavar="EXTRA",
type=str,
nargs="*",
default=(),
help="extras to install",
)
parser.add_argument(
"--all-extras",
dest="_all_extras",
action="store_true",
help="get all extras",
)
parser.add_argument(
*("--output", "-o"),
metavar="FILE",
type=Path,
default=None,
help=(
"output file (default: stdout). "
"Without this option, output is space-separated for direct passing to `pip install`. "
"With this option, output written to a file newline-separated file usable as `requirements.txt` or `constraints.txt`."
),
)
return parser

@cached_property
def pyproject(self) -> dict[str, Any]:
return tomllib.loads(self._path.read_text())

@cached_property
def extras(self) -> AbstractSet[str]:
if self._extras:
if self._all_extras:
sys.exit("Cannot specify both --extras and --all-extras")
return dict.fromkeys(self._extras).keys()
if not self._all_extras:
return set()
return self.pyproject["project"]["optional-dependencies"].keys()


def main(argv: Sequence[str] | None = None) -> None:
args = Args.parse(argv)

project_name = args.pyproject["project"]["name"]
deps = [
*map(Requirement, pyproject["project"]["dependencies"]),
*map(Requirement, args.pyproject["project"]["dependencies"]),
*(Requirement(f"{project_name}[{extra}]") for extra in args.extras),
]

min_deps = extract_min_deps(deps, pyproject=pyproject)
min_deps = extract_min_deps(deps, pyproject=args.pyproject)

print(" ".join(map(str, min_deps)))
sep = "\n" if args.output else " "
with ExitStack() as stack:
f = stack.enter_context(args.output.open("w")) if args.output else sys.stdout
print(sep.join(map(str, min_deps)), file=f)


if __name__ == "__main__":
Expand Down
3 changes: 3 additions & 0 deletions ci/scripts/towncrier_automation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#!/usr/bin/env python3
# /// script
# dependencies = [ "towncrier", "packaging" ]
# ///
from __future__ import annotations

import argparse
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/1806.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add {mod}`scipy` 1.5 compatibility {user}`flying-sheep`
13 changes: 9 additions & 4 deletions hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@ features = ["dev"]

[envs.docs]
features = ["doc"]
extra-dependencies = ["setuptools"] # https://bitbucket.org/pybtex-devs/pybtex/issues/169
scripts.build = "sphinx-build -M html docs docs/_build -W --keep-going {args}"
scripts.open = "python3 -m webbrowser -t docs/_build/html/index.html"
scripts.clean = "git clean -fdX -- {args:docs}"

[envs.towncrier]
scripts.create = "towncrier create {args}"
scripts.build = "python3 ci/scripts/towncrier_automation.py {args}"
scripts.clean = "git restore --source=HEAD --staged --worktree -- docs/release-notes"

[envs.hatch-test]
default-args = []
extra-dependencies = ["ipykernel"]
features = ["dev", "test"]
extra-dependencies = ["ipykernel"]
env-vars.UV_CONSTRAINT = "ci/constraints.txt"
overrides.matrix.deps.env-vars = [
{ key = "UV_PRERELEASE", value = "allow", if = ["pre"] },
{ key = "UV_RESOLUTION", value = "lowest-direct", if = ["min"] },
{ if = ["pre"], key = "UV_PRERELEASE", value = "allow" },
{ if = ["min"], key = "UV_CONSTRAINT", value = "ci/constraints.txt ci/min-deps.txt" },
]
overrides.matrix.deps.pre-install-commands = [
{ if = ["min"], value = "uv run ci/scripts/min-deps.py pyproject.toml --all-extras -o ci/min-deps.txt" },
]
overrides.matrix.deps.python = [
{ if = ["min"], value = "3.10" },
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ dependencies = [
"numpy>=1.23",
# https://github.com/scverse/anndata/issues/1434
"scipy >1.8",
"h5py>=3.6",
"h5py>=3.7",
"exceptiongroup; python_version<'3.11'",
"natsort",
"packaging>=20.0",
Expand Down Expand Up @@ -75,7 +75,7 @@ doc = [
"nbsphinx",
"scanpydoc[theme,typehints] >=0.14.1",
"zarr",
"awkward>=2.0.7",
"awkward>=2.3",
"IPython", # For syntax highlighting in notebooks
"myst_parser",
"sphinx_design>=0.5.0",
Expand All @@ -94,7 +94,7 @@ test = [
"openpyxl",
"joblib",
"boltons",
"scanpy",
"scanpy>=1.9.8",
"httpx", # For data downloading
"dask[distributed]",
"awkward>=2.3",
Expand Down
22 changes: 18 additions & 4 deletions src/anndata/_core/sparse_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@

import h5py
import numpy as np
import scipy
import scipy.sparse as ss
from packaging.version import Version
from scipy.sparse import _sparsetools

from .. import abc
Expand All @@ -39,11 +41,14 @@

from .._types import GroupStorageType
from ..compat import H5Array
from .index import Index
from .index import Index, Index1D
else:
from scipy.sparse import spmatrix as _cs_matrix


SCIPY_1_15 = Version(scipy.__version__) >= Version("1.15rc0")


class BackedFormat(NamedTuple):
format: Literal["csr", "csc"]
backed_type: type[BackedSparseMatrix]
Expand Down Expand Up @@ -353,7 +358,9 @@ def _get_group_format(group: GroupStorageType) -> str:


# Check for the overridden few methods above in our BackedSparseMatrix subclasses
def is_sparse_indexing_overridden(format: Literal["csr", "csc"], row, col):
def is_sparse_indexing_overridden(
format: Literal["csr", "csc"], row: Index1D, col: Index1D
):
major_indexer, minor_indexer = (row, col) if format == "csr" else (col, row)
return isinstance(minor_indexer, slice) and (
(isinstance(major_indexer, int | np.integer))
Expand All @@ -362,6 +369,13 @@ def is_sparse_indexing_overridden(format: Literal["csr", "csc"], row, col):
)


def validate_indices(
mtx: BackedSparseMatrix, indices: tuple[Index1D, Index1D]
) -> tuple[Index1D, Index1D]:
res = mtx._validate_indices(indices)
return res[0] if SCIPY_1_15 else res


class BaseCompressedSparseDataset(abc._AbstractCSDataset, ABC):
_group: GroupStorageType

Expand Down Expand Up @@ -424,8 +438,8 @@ def __getitem__(
indices = self._normalize_index(index)
row, col = indices
mtx = self._to_backed()
row_sp_matrix_validated, col_sp_matrix_validated = mtx._validate_indices(
(row, col)
row_sp_matrix_validated, col_sp_matrix_validated = validate_indices(
mtx, indices
)

# Handle masked indexing along major axis
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 @@ -47,7 +47,7 @@ class Empty:
pass


Index1D = slice | int | str | np.int64 | np.ndarray
Index1D = slice | int | str | np.int64 | np.ndarray | pd.Series
IndexRest = Index1D | EllipsisType
Index = (
IndexRest
Expand Down
17 changes: 14 additions & 3 deletions tests/test_backed_sparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,17 +615,28 @@ def test_backed_sizeof(
assert csr_mem.__sizeof__() > csc_disk.__sizeof__()


sparray_scipy_bug_marks = (
[pytest.mark.skip(reason="scipy bug causes view to be allocated")]
if CAN_USE_SPARSE_ARRAY
else []
)


@pytest.mark.parametrize(
"group_fn",
[
pytest.param(lambda _: zarr.group(), id="zarr"),
pytest.param(lambda p: h5py.File(p / "test.h5", mode="a"), id="h5py"),
],
)
@pytest.mark.parametrize("sparse_class", [sparse.csr_matrix, sparse.csr_array])
@pytest.mark.parametrize(
"sparse_class",
[
sparse.csr_matrix,
pytest.param(sparse.csr_array, marks=[*sparray_scipy_bug_marks]),
],
)
def test_append_overflow_check(group_fn, sparse_class, tmp_path):
if CAN_USE_SPARSE_ARRAY and issubclass(sparse_class, SpArray):
pytest.skip("scipy bug causes view to be allocated")
group = group_fn(tmp_path)
typemax_int32 = np.iinfo(np.int32).max
orig_mtx = sparse_class(np.ones((1, 1), dtype=bool))
Expand Down
2 changes: 1 addition & 1 deletion tests/test_concatenate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,7 @@ def test_concat_outer_aligned_mapping(elem):
del b.obsm[elem]

concated = concat({"a": a, "b": b}, join="outer", label="group")
result = concated.obsm[elem][concated.obs["group"] == "b"]
result = concated[concated.obs["group"] == "b"].obsm[elem]

check_filled_like(result, elem_name=f"obsm/{elem}")

Expand Down

0 comments on commit 298fd0c

Please sign in to comment.