Skip to content
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
2 changes: 0 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ jobs:
os: [ubuntu-latest, macos-latest]
env: ${{ fromJSON(needs.get-environments.outputs.envs) }}
exclude:
- os: macos-latest
env: { name: hatch-test.py3.11-stable }
- os: macos-latest
env: { name: hatch-test.py3.12-stable }
- os: macos-latest
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Head over to the [documentation](https://squidpy.readthedocs.io/en/stable/) for

## Installation

We recommend running Squidpy on a recent Linux or macOS system with Python ≥3.11, but it also works on Windows via WSL.
We recommend running Squidpy on a recent Linux or macOS system with Python ≥3.12, but it also works on Windows via WSL.

Install from [PyPI](https://pypi.org/project/squidpy) with:

Expand Down
8 changes: 8 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ import squidpy as sq
tl.var_by_distance
```

## Settings

```{eval-rst}
.. data:: squidpy.settings

The global backend settings instance provided by :mod:`scverse_backends`.
```

## Datasets
```{eval-rst}
.. module:: squidpy.datasets
Expand Down
2 changes: 1 addition & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Installation

Squidpy requires Python version >= 3.11 to run.
Squidpy requires Python version >= 3.12 to run.

## PyPI

Expand Down
2 changes: 1 addition & 1 deletion hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ download = "python ./.scripts/ci/download_data.py {args}"

[[envs.hatch-test.matrix]]
deps = ["stable"]
python = ["3.11", "3.12", "3.13"]
python = ["3.12", "3.13"]

[[envs.hatch-test.matrix]]
deps = ["pre"]
Expand Down
18 changes: 12 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ maintainers = [
authors = [
{ name = "scverse" },
]
requires-python = ">=3.11"
requires-python = ">=3.12"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
Expand All @@ -31,7 +31,6 @@ classifiers = [
"Operating System :: MacOS :: MacOS X",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
Expand Down Expand Up @@ -66,6 +65,7 @@ dependencies = [
"scikit-image>=0.25",
# due to https://github.com/scikit-image/scikit-image/issues/6850 breaks rescale ufunc
"scikit-learn>=0.24",
"scverse-backends>=0.0.2,<0.1",
"spatialdata>=0.7.1",
"spatialdata-plot",
"statsmodels>=0.12",
Expand All @@ -76,6 +76,12 @@ dependencies = [
"xarray>=2024.10",
"zarr>=3",
]
optional-dependencies.cu12 = [
"rapids-singlecell-cu12[rapids]",
]
optional-dependencies.cu13 = [
"rapids-singlecell-cu13[rapids]",
]
optional-dependencies.dev = [
"hatch>=1.9",
"ipykernel",
Expand Down Expand Up @@ -136,7 +142,7 @@ version.source = "vcs"
[tool.pixi]
workspace.channels = [ "conda-forge" ]
workspace.platforms = [ "linux-64", "osx-arm64" ]
dependencies.python = ">=3.11"
dependencies.python = ">=3.12"
pypi-dependencies.squidpy = { path = ".", editable = true }
tasks.format = "ruff format ."
tasks.kernel-install = 'python -m ipykernel install --user --name pixi-dev --display-name "squidpy (dev)"'
Expand All @@ -145,12 +151,12 @@ tasks.lint = "ruff check ."
tasks.pre-commit = "pre-commit run"
tasks.pre-commit-install = "pre-commit install"
tasks.test = "pytest -v --color=yes --tb=short --durations=10"
feature.py311.dependencies.python = "3.11.*"
feature.py312.dependencies.python = "3.12.*"
feature.py313.dependencies.python = "3.13.*"
environments.default = { features = [ "py313" ], solve-group = "py313" }
environments.dev-py311 = { features = [ "dev", "test", "py311" ], solve-group = "py311" }
environments.dev-py312 = { features = [ "dev", "test", "py312" ], solve-group = "py312" }
environments.dev-py313 = { features = [ "dev", "test", "py313" ], solve-group = "py313" }
environments.docs-py311 = { features = [ "docs", "py311" ], solve-group = "py311" }
environments.docs-py312 = { features = [ "docs", "py312" ], solve-group = "py312" }
environments.docs-py313 = { features = [ "docs", "py313" ], solve-group = "py313" }
environments.test-py313 = { features = [ "test", "py313" ], solve-group = "py313" }

Expand Down
3 changes: 2 additions & 1 deletion src/squidpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from importlib.metadata import PackageMetadata

from squidpy import datasets, experimental, gr, im, pl, read, tl
from squidpy._backends import settings

try:
md: PackageMetadata = metadata.metadata(__name__)
Expand All @@ -15,4 +16,4 @@

del metadata, md

__all__ = ["datasets", "experimental", "gr", "im", "pl", "read", "tl"]
__all__ = ["datasets", "experimental", "gr", "im", "pl", "read", "settings", "tl"]
40 changes: 40 additions & 0 deletions src/squidpy/_backends/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Pluggable backend dispatch system for squidpy."""

from __future__ import annotations

from scverse_backends import BackendDispatcher

_dispatcher = BackendDispatcher(
entrypoint_group="squidpy.backends",
host_name="squidpy",
trusted_backends={
"rapids_singlecell": {
"aliases": ["rapids-singlecell", "rsc", "cuda"],
"package": "rapids-singlecell",
"distributions": [
"rapids-singlecell",
"rapids-singlecell-cu12",
"rapids-singlecell-cu13",
],
"entrypoints": ["rapids_singlecell"],
"module_prefixes": ["rapids_singlecell"],
},
},
reserved_backends={
"gpu": "Use a concrete backend alias such as 'cuda' or 'rsc'.",
},
)

backend_dispatch = _dispatcher.backend_dispatch
settings = _dispatcher.settings
get_backend = _dispatcher.get_backend
available_backend_names = _dispatcher.available_backend_names
discover = _dispatcher.discover

__all__ = [
"available_backend_names",
"backend_dispatch",
"discover",
"get_backend",
"settings",
]
7 changes: 2 additions & 5 deletions src/squidpy/_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,12 @@ def decorator2(obj: Any) -> Any:
_parallelize = """\
n_jobs
Number of parallel jobs to use.
For ``backend="loky"``, the number of cores used by numba for
each job spawned by the backend will be set to 1 in order to
overcome the oversubscription issue in case you run
The number of cores used by numba for each job will be set to 1
in order to overcome the oversubscription issue in case you run
numba in your function to parallelize.
To set the absolute maximum number of threads in numba
for your python program, set the environment variable:
``NUMBA_NUM_THREADS`` before running the program.
backend
Parallelization backend to use. See :class:`joblib.Parallel` for available options.
show_progress_bar
Whether to show the progress bar or not."""
_channels = """\
Expand Down
39 changes: 39 additions & 0 deletions src/squidpy/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,45 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
return wrapper


_JOBLIB_BACKENDS = frozenset({"dask", "loky", "multiprocessing", "ray", "sequential", "threading"})


def _is_joblib_backend(backend: Any) -> bool:
return isinstance(backend, str) and (backend in _JOBLIB_BACKENDS or backend in jl.parallel.BACKENDS)


def _deprecate_backend_as_parallel_backend(func: Callable[..., Any]) -> Callable[..., Any]:
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
if _is_joblib_backend(kwargs.get("backend")):
if "parallel_backend" in kwargs:
raise TypeError("Pass only one of `backend` or `parallel_backend` for joblib parallelism.")
warnings.warn(
"Using `backend` for joblib parallelism is deprecated. Use `parallel_backend` instead.",
FutureWarning,
stacklevel=2,
)
kwargs["parallel_backend"] = kwargs.pop("backend")
return func(*args, **kwargs)

return wrapper


def _deprecate_legacy_joblib_backend(func: Callable[..., Any]) -> Callable[..., Any]:
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
if _is_joblib_backend(kwargs.get("backend")):
warnings.warn(
"Using `backend` for joblib parallelism is deprecated and has no effect.",
FutureWarning,
stacklevel=2,
)
kwargs.pop("backend")
return func(*args, **kwargs)

return wrapper


def _get_n_cores(n_cores: int | None) -> int:
"""
Make number of cores a positive integer.
Expand Down
2 changes: 2 additions & 0 deletions src/squidpy/gr/_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
get_model,
)

from squidpy._backends import backend_dispatch
from squidpy._constants._constants import CoordType, Transform
from squidpy._constants._pkg_constants import Key
from squidpy._docs import d, inject_docs
Expand All @@ -61,6 +62,7 @@ class SpatialNeighborsResult(NamedTuple):

@d.dedent
@inject_docs(t=Transform, c=CoordType)
@backend_dispatch
def spatial_neighbors(
adata: AnnData | SpatialData,
spatial_key: str = Key.obsm.spatial,
Expand Down
30 changes: 23 additions & 7 deletions src/squidpy/gr/_ligrec.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from functools import partial
from itertools import product
from types import MappingProxyType
from typing import TYPE_CHECKING, Any, Literal, TypeAlias
from typing import TYPE_CHECKING, Any, Literal

import numpy as np
import pandas as pd
Expand All @@ -17,10 +17,11 @@
from scipy.sparse import csc_matrix
from spatialdata import SpatialData

from squidpy._backends import backend_dispatch
from squidpy._constants._constants import ComplexPolicy, CorrAxis
from squidpy._constants._pkg_constants import Key
from squidpy._docs import d, inject_docs
from squidpy._utils import NDArrayA, Signal, SigQueue, _get_n_cores, parallelize
from squidpy._utils import NDArrayA, Signal, SigQueue, _deprecate_backend_as_parallel_backend, _get_n_cores, parallelize
from squidpy._validators import assert_positive, check_tuple_needles
from squidpy.gr._utils import (
_assert_categorical_obs,
Expand All @@ -30,11 +31,11 @@

__all__ = ["ligrec", "PermutationTest"]

StrSeq: TypeAlias = Sequence[str]
SeqTuple: TypeAlias = Sequence[tuple[str, str]]
Interaction_t: TypeAlias = pd.DataFrame | Mapping[str, StrSeq] | StrSeq | tuple[StrSeq, StrSeq] | SeqTuple
type StrSeq = Sequence[str]
type SeqTuple = Sequence[tuple[str, str]]
type Interaction_t = pd.DataFrame | Mapping[str, StrSeq] | StrSeq | tuple[StrSeq, StrSeq] | SeqTuple

Cluster_t: TypeAlias = StrSeq | tuple[StrSeq, StrSeq] | SeqTuple
type Cluster_t = StrSeq | tuple[StrSeq, StrSeq] | SeqTuple

SOURCE = "source"
TARGET = "target"
Expand Down Expand Up @@ -313,6 +314,7 @@ def prepare(
@d.get_sections(base="PT_test", sections=["Parameters"])
@d.dedent
@inject_docs(src=SOURCE, tgt=TARGET, fa=CorrAxis)
@_deprecate_backend_as_parallel_backend
def test(
self,
cluster_key: str,
Expand All @@ -326,6 +328,7 @@ def test(
copy: bool = False,
key_added: str | None = None,
numba_parallel: bool | None = None,
parallel_backend: str = "loky",
**kwargs: Any,
) -> Mapping[str, pd.DataFrame] | None:
"""
Expand Down Expand Up @@ -356,6 +359,8 @@ def test(
If `None`, ``'{{cluster_key}}_ligrec'`` will be used.
%(numba_parallel)s
%(parallelize)s
parallel_backend
Which joblib backend to use for permutation parallelism.

Returns
-------
Expand Down Expand Up @@ -422,6 +427,7 @@ def test(
seed=seed,
n_jobs=n_jobs,
numba_parallel=numba_parallel,
parallel_backend=parallel_backend,
**kwargs,
)
index = pd.MultiIndex.from_frame(interactions, names=[SOURCE, TARGET])
Expand Down Expand Up @@ -629,6 +635,8 @@ def prepare(


@d.dedent
@_deprecate_backend_as_parallel_backend
@backend_dispatch
def ligrec(
adata: AnnData | SpatialData,
cluster_key: str,
Expand All @@ -641,6 +649,7 @@ def ligrec(
copy: bool = False,
key_added: str | None = None,
gene_symbols: str | None = None,
parallel_backend: str = "loky",
**kwargs: Any,
) -> Mapping[str, pd.DataFrame] | None:
"""
Expand All @@ -653,6 +662,8 @@ def ligrec(
%(PT_test.parameters)s
gene_symbols
Key in :attr:`anndata.AnnData.var` to use instead of :attr:`anndata.AnnData.var_names`.
parallel_backend
Which joblib backend to use for permutation parallelism.

Returns
-------
Expand All @@ -671,6 +682,7 @@ def ligrec(
corr_axis=corr_axis,
copy=copy,
key_added=key_added,
parallel_backend=parallel_backend,
**kwargs,
)
)
Expand All @@ -686,6 +698,7 @@ def _analysis(
seed: int | None = None,
n_jobs: int = 1,
numba_parallel: bool | None = None,
parallel_backend: str = "loky",
**kwargs: Any,
) -> TempResult:
"""
Expand All @@ -709,8 +722,10 @@ def _analysis(
Number of parallel jobs to launch.
numba_parallel
Whether to use :func:`numba.prange` or not. If `None`, it's determined automatically.
parallel_backend
Which joblib backend to use for permutation parallelism.
kwargs
Keyword arguments for :func:`squidpy._utils.parallelize`, such as ``n_jobs`` or ``backend``.
Additional keyword arguments for :func:`squidpy._utils.parallelize`.

Returns
-------
Expand Down Expand Up @@ -754,6 +769,7 @@ def extractor(res: Sequence[TempResult]) -> TempResult:
_analysis_helper,
np.arange(n_perms, dtype=np.int32).tolist(),
n_jobs=n_jobs,
backend=parallel_backend,
unit="permutation",
extractor=extractor,
**kwargs,
Expand Down
Loading
Loading