Skip to content

Commit b85d814

Browse files
authored
chore: switch to dependency groups (#3905)
1 parent 56daa4d commit b85d814

File tree

7 files changed

+70
-29
lines changed

7 files changed

+70
-29
lines changed

.readthedocs.yml

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,14 @@ build:
1010
post_checkout:
1111
# unshallow so version can be derived from tag
1212
- git fetch --unshallow || true
13+
create_environment:
14+
- asdf plugin add uv
15+
- asdf install uv latest
16+
- asdf global uv latest
1317
pre_build:
1418
# run towncrier to preview the next version’s release notes
15-
- ( find docs/release-notes -regex '[^.]+[.][^.]+.md' | grep -q . ) && towncrier build --keep || true
16-
sphinx:
17-
fail_on_warning: true # do not change or you will be fired
18-
configuration: docs/conf.py
19-
python:
20-
install:
21-
- method: pip
22-
path: .
23-
extra_requirements:
24-
- doc
25-
- dev # for towncrier
19+
- ( find docs/release-notes -regex '[^.]+[.][^.]+.md' | grep -q . ) && uvx hatch run towncrier build --keep || true
20+
build:
21+
html:
22+
- uvx hatch run docs:build
23+
- mv docs/_build $READTHEDOCS_OUTPUT

ci/scripts/low-vers.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python3
22
# /// script
33
# requires-python = ">=3.11"
4-
# dependencies = [ "packaging" ]
4+
# dependencies = [ "packaging", "dependency-groups" ]
55
# ///
66
"""Parse a pyproject.toml file and output a list of minimum dependency versions."""
77

@@ -16,6 +16,7 @@
1616
from pathlib import Path
1717
from typing import TYPE_CHECKING
1818

19+
import dependency_groups
1920
from packaging.requirements import Requirement
2021
from packaging.version import Version
2122

@@ -92,6 +93,8 @@ class Args(argparse.Namespace):
9293
output: Path | None
9394
_extras: list[str]
9495
_all_extras: bool
96+
_groups: list[str]
97+
_all_groups: bool
9598

9699
@classmethod
97100
def parse(cls, argv: Sequence[str] | None = None) -> Self:
@@ -128,6 +131,21 @@ def parser(cls) -> argparse.ArgumentParser:
128131
action="store_true",
129132
help="get all extras",
130133
)
134+
parser.add_argument(
135+
"--groups",
136+
dest="_groups",
137+
metavar="GROUP",
138+
type=str,
139+
nargs="*",
140+
default=(),
141+
help="dependency groups to install",
142+
)
143+
parser.add_argument(
144+
"--all-groups",
145+
dest="_all_groups",
146+
action="store_true",
147+
help="get all dependency groups",
148+
)
131149
parser.add_argument(
132150
*("--output", "-o"),
133151
metavar="FILE",
@@ -157,6 +175,17 @@ def extras(self) -> AbstractSet[str]:
157175
return set()
158176
return self.pyproject["project"]["optional-dependencies"].keys()
159177

178+
@cached_property
179+
def groups(self) -> AbstractSet[str]:
180+
"""Return the dependency groups to install."""
181+
if self._groups:
182+
if self._all_groups:
183+
sys.exit("Cannot specify both --groups and --all-groups")
184+
return dict.fromkeys(self._groups).keys()
185+
if not self._all_groups:
186+
return set()
187+
return self.pyproject["dependency-groups"].keys()
188+
160189

161190
def main(argv: Sequence[str] | None = None) -> None:
162191
"""Run main entry point."""
@@ -166,6 +195,12 @@ def main(argv: Sequence[str] | None = None) -> None:
166195
deps = [
167196
*map(Requirement, args.pyproject["project"]["dependencies"]),
168197
*(Requirement(f"{project_name}[{extra}]") for extra in args.extras),
198+
*map(
199+
Requirement,
200+
dependency_groups.resolve(
201+
args.pyproject["dependency-groups"], *args.groups
202+
),
203+
),
169204
]
170205

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

docs/installation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ If you are using `pip>=21.3`, an editable install can be made:
7575
```console
7676
$ python -m venv .venv
7777
$ source .venv/bin/activate
78-
$ pip install -e '.[dev,test]'
78+
$ pip install --group=dev --group=test -e .
7979
```
8080
:::
8181

@@ -87,7 +87,7 @@ $ pipx install beni
8787
$ beni pyproject.toml > environment.yml
8888
$ conda env create -f environment.yml
8989
$ conda activate scanpy
90-
$ pip install -e '.[dev,doc,test]'
90+
$ pip install --group=dev --group=test --group=doc -e .
9191
```
9292

9393
For instructions on how to work with the code, see the {ref}`contribution guide <contribution-guide>`.

docs/release-notes/3905.chore.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Remove development extras (`scanpy[test,dev]`) in favor of dependency groups {smaller}`P Angerer`

hatch.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[envs.default]
22
installer = "uv"
3-
features = [ "dev" ]
3+
dependency-groups = [ "dev" ]
44

55
[envs.docs]
6-
features = [ "doc" ]
7-
scripts.build = "sphinx-build -M html docs docs/_build -W --keep-going {args}"
6+
dependency-groups = [ "doc" ]
7+
scripts.build = "sphinx-build -M html docs docs/_build -W {args}"
88
scripts.open = "python3 -m webbrowser -t docs/_build/html/index.html"
99
scripts.clean = "git clean -fdX -- {args:docs}"
1010

@@ -15,7 +15,7 @@ scripts.clean = "git restore --source=HEAD --staged --worktree -- docs/release-n
1515

1616
[envs.hatch-test]
1717
default-args = [ ]
18-
features = [ "dev", "test-min" ]
18+
dependency-groups = [ "dev", "test-min" ]
1919
extra-dependencies = [ "ipykernel" ]
2020
overrides.matrix.deps.env-vars = [
2121
{ if = [ "pre" ], key = "UV_PRERELEASE", value = "allow" },
@@ -24,7 +24,7 @@ overrides.matrix.deps.env-vars = [
2424
overrides.matrix.deps.pre-install-commands = [
2525
{ if = [
2626
"low-vers",
27-
], value = "uv run ci/scripts/low-vers.py pyproject.toml --all-extras -o ci/scanpy-low-vers.txt" },
27+
], value = "uv run ci/scripts/low-vers.py pyproject.toml --all-extras --all-groups -o ci/scanpy-low-vers.txt" },
2828
]
2929
overrides.matrix.deps.python = [
3030
{ if = [ "low-vers" ], value = "3.12" },
@@ -34,9 +34,9 @@ overrides.matrix.deps.python = [
3434
{ if = [ "pre" ], value = "3.14" },
3535
]
3636
overrides.matrix.deps.extra-dependencies = [
37-
{ if = [ "pre" ], value = "anndata[dev,test] @ git+https://github.com/scverse/anndata.git" },
37+
{ if = [ "pre" ], value = "anndata @ git+https://github.com/scverse/anndata.git" },
3838
]
39-
overrides.matrix.deps.features = [
39+
overrides.matrix.deps.dependency-groups = [
4040
{ if = [ "stable", "pre", "low-vers" ], value = "test" },
4141
]
4242

pyproject.toml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ dependencies = [
5454
"numpy>=2",
5555
"fast-array-utils[accel,sparse]>=1.2.1",
5656
"matplotlib>=3.9",
57-
"pandas >=2.2.2",
57+
"pandas >=2.2.2, <3.0.0rc0",
5858
"scipy>=1.13",
5959
"seaborn>=0.13.2",
6060
"h5py>=3.11",
@@ -87,7 +87,7 @@ Twitter = "https://x.com/scverse_team"
8787
[project.scripts]
8888
scanpy = "scanpy.cli:console_main"
8989

90-
[project.optional-dependencies]
90+
[dependency-groups]
9191
test-min = [
9292
"pytest",
9393
"pytest-mock",
@@ -96,9 +96,10 @@ test-min = [
9696
"pytest-randomly",
9797
"pytest-rerunfailures",
9898
"tuna",
99+
"dependency-groups", # for CI scripts doctests
99100
]
100101
test = [
101-
"scanpy[test-min]",
102+
{ include-group = "test-min" },
102103
# optional storage and processing modes
103104
"scanpy[dask]",
104105
"zarr>=2.18.7",
@@ -131,6 +132,8 @@ dev = [
131132
"towncrier", # release note management
132133
"scipy-stubs", # static typing and IDE support
133134
]
135+
136+
[project.optional-dependencies]
134137
# Algorithms
135138
paga = [ "igraph" ]
136139
louvain = [ "igraph", "louvain>=0.8.2", "setuptools" ] # Louvain community detection
@@ -152,7 +155,8 @@ packages = [ "src/testing", "src/scanpy" ]
152155
source = "vcs"
153156
raw-options.version_scheme = "release-branch-semver"
154157

155-
[tool.pytest.ini_options]
158+
[tool.pytest]
159+
strict = true
156160
addopts = [
157161
"--import-mode=importlib",
158162
"--strict-markers",
@@ -162,7 +166,6 @@ addopts = [
162166
]
163167
testpaths = [ "./tests", "./ci", "scanpy" ]
164168
norecursedirs = [ "tests/_images" ]
165-
xfail_strict = true
166169
junit_family = "xunit1"
167170
markers = [
168171
"internet: tests which rely on internet resources (enable with `--internet-tests`)",

tests/test_normalization.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,16 @@ def test_normalize_pearson_residuals_warnings(pbmc3k_parametrized):
118118
@pytest.mark.parametrize(
119119
("params", "match"),
120120
[
121-
pytest.param(dict(theta=0), r"Pearson residuals require theta > 0", id="theta"),
122121
pytest.param(
123-
dict(theta=-1), r"Pearson residuals require theta > 0", id="theta"
122+
dict(theta=0), r"Pearson residuals require theta > 0", id="theta=0"
124123
),
125124
pytest.param(
126-
dict(clip=-1), r"Pearson residuals require `clip>=0` or `clip=None`."
125+
dict(theta=-1), r"Pearson residuals require theta > 0", id="theta=-1"
126+
),
127+
pytest.param(
128+
dict(clip=-1),
129+
r"Pearson residuals require `clip>=0` or `clip=None`.",
130+
id="clip=-1",
127131
),
128132
],
129133
)

0 commit comments

Comments
 (0)