Skip to content

Commit 2fa6ac0

Browse files
authored
docs: improve prefix default rendering (#4069)
1 parent 063262b commit 2fa6ac0

4 files changed

Lines changed: 44 additions & 40 deletions

File tree

hatch.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ overrides.matrix.deps.python = [
3838
overrides.matrix.deps.extra-dependencies = [
3939
{ if = [ "stable" ], value = "scipy>=1.17" },
4040
{ if = [ "pre" ], value = "anndata @ git+https://github.com/scverse/anndata.git" },
41-
{ if = [ "pre" ], value = "pandas>=3rc0" },
41+
{ if = [ "pre" ], value = "pandas>=3" },
4242
]
4343
overrides.matrix.deps.dependency-groups = [
4444
{ if = [ "stable", "pre", "low-vers" ], value = "test" },

src/scanpy/_settings/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def preset(cls) -> Preset:
106106
@preset.setter
107107
def preset(cls, preset: Preset | str) -> None:
108108
new_preset = Preset(preset)
109-
new_preset.check_deps()
109+
new_preset.check()
110110
cls._preset = new_preset
111111

112112
@property

src/scanpy/_settings/presets.py

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
from contextlib import contextmanager
77
from dataclasses import dataclass
88
from functools import cached_property, partial, wraps
9-
from importlib.metadata import packages_distributions, requires
10-
from importlib.util import find_spec
9+
from importlib.metadata import distributions, requires
1110
from typing import TYPE_CHECKING, Literal, NamedTuple
1211

1312
from packaging.requirements import Requirement
13+
from packaging.utils import canonicalize_name
1414

1515
from .._utils._doctests import doctest_needs
1616

@@ -51,10 +51,22 @@ def __repr__(self) -> str:
5151
return self.repr or "default"
5252
import scanpy as sc
5353

54+
value = self._get_value(sc.settings.preset)
55+
suffix = (
56+
" – changes in 2.0"
57+
if sc.settings.preset is Preset.ScanpyV1
58+
and value != self._get_value(Preset.ScanpyV2Preview)
59+
else ""
60+
)
61+
return f"{value!r} (sc.settings.preset={str(sc.settings.preset)!r}{suffix})"
62+
63+
def _get_value(self, preset: Preset) -> object:
64+
if not self.preset:
65+
msg = "preset is not set"
66+
raise AssertionError(msg)
5467
func, param = self.preset
55-
params = getattr(sc.settings.preset, func)
56-
value = getattr(params, param)
57-
return f"{value!r} ({sc.settings.preset=} – changes in 2.0)"
68+
params = getattr(preset, func)
69+
return getattr(params, param)
5870

5971

6072
class HVGPreset(NamedTuple):
@@ -225,28 +237,32 @@ def override(self, preset: Preset) -> Generator[Preset, None, None]:
225237
finally:
226238
settings.preset = self
227239

228-
def check_deps(self) -> None:
229-
240+
def check(self) -> None:
241+
"""Check if requirements for preset are met."""
230242
match self:
231243
case self.ScanpyV1:
232244
return
233245
case self.ScanpyV2Preview:
234-
dists = {d: m for m, ds in packages_distributions().items() for d in ds}
235-
missing = [
236-
r.name
237-
for r in map(Requirement, requires("scanpy"))
238-
if r.marker
239-
and r.marker.evaluate({"extra": "scanpy2"})
240-
and find_spec(dists.get(r.name, r.name.replace("-", "_"))) is None
241-
]
242-
if missing:
243-
missing_str = ", ".join(f"‘{m}’" for m in missing)
244-
msg = (
245-
f"Setting preset to {Preset.ScanpyV2Preview!r} requires optional "
246-
f"dependencies that are not installed: {missing_str}. "
247-
"Install them with: pip install `scanpy[scanpy2]`"
248-
)
249-
raise ImportError(msg)
246+
if not (missing := _missing_scanpy2_deps()):
247+
return
248+
missing_str = ", ".join(f"‘{m.name}’" for m in missing)
249+
msg = (
250+
f"Setting preset to {Preset.ScanpyV2Preview!r} requires optional "
251+
f"dependencies that are not installed: {missing_str}. "
252+
"Install them with: pip install `scanpy[scanpy2]`"
253+
)
254+
raise ImportError(msg)
255+
256+
257+
def _missing_scanpy2_deps() -> list[Requirement]:
258+
dist_names = {canonicalize_name(d.name) for d in distributions()}
259+
return [
260+
r
261+
for r in map(Requirement, requires("scanpy") or ())
262+
if r.marker
263+
and r.marker.evaluate({"extra": "scanpy2"}, "requirement")
264+
and canonicalize_name(r.name) not in dist_names
265+
]
250266

251267

252268
for postprocess in preset_postprocessors:

tests/test_settings.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
from __future__ import annotations
22

3-
from importlib.metadata import packages_distributions, requires
4-
from importlib.util import find_spec
5-
63
import pytest
7-
from packaging.requirements import Requirement
84

95
import scanpy as sc
6+
from scanpy._settings.presets import _missing_scanpy2_deps
107

118

129
# TODO: reset everything
@@ -24,20 +21,11 @@ def test_set_figure_params_warns() -> None:
2421

2522

2623
def test_preset_scanpy_v2_preview_checks_deps() -> None:
27-
dists = {d: m for m, ds in packages_distributions().items() for d in ds}
28-
scanpy2_deps_missing = any(
29-
r.name
30-
for r in map(Requirement, requires("scanpy"))
31-
if r.marker
32-
and r.marker.evaluate({"extra": "scanpy2"})
33-
and find_spec(dists.get(r.name, r.name.replace("-", "_"))) is None
34-
)
35-
36-
if scanpy2_deps_missing:
24+
if _missing_scanpy2_deps():
3725
with pytest.raises(ImportError, match=r"scanpy\[scanpy2\]"):
3826
sc.settings.preset = sc.Preset.ScanpyV2Preview
3927
else:
4028
sc.settings.preset = sc.Preset.ScanpyV2Preview
4129
assert sc.settings.preset is sc.Preset.ScanpyV2Preview
4230
sc.settings.preset = sc.Preset.ScanpyV1
43-
assert sc.settings.preset == sc.Preset.ScanpyV1
31+
assert sc.settings.preset is sc.Preset.ScanpyV1

0 commit comments

Comments
 (0)