|
6 | 6 | from contextlib import contextmanager |
7 | 7 | from dataclasses import dataclass |
8 | 8 | 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 |
11 | 10 | from typing import TYPE_CHECKING, Literal, NamedTuple |
12 | 11 |
|
13 | 12 | from packaging.requirements import Requirement |
| 13 | +from packaging.utils import canonicalize_name |
14 | 14 |
|
15 | 15 | from .._utils._doctests import doctest_needs |
16 | 16 |
|
@@ -51,10 +51,22 @@ def __repr__(self) -> str: |
51 | 51 | return self.repr or "default" |
52 | 52 | import scanpy as sc |
53 | 53 |
|
| 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) |
54 | 67 | 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) |
58 | 70 |
|
59 | 71 |
|
60 | 72 | class HVGPreset(NamedTuple): |
@@ -225,28 +237,32 @@ def override(self, preset: Preset) -> Generator[Preset, None, None]: |
225 | 237 | finally: |
226 | 238 | settings.preset = self |
227 | 239 |
|
228 | | - def check_deps(self) -> None: |
229 | | - |
| 240 | + def check(self) -> None: |
| 241 | + """Check if requirements for preset are met.""" |
230 | 242 | match self: |
231 | 243 | case self.ScanpyV1: |
232 | 244 | return |
233 | 245 | 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 | + ] |
250 | 266 |
|
251 | 267 |
|
252 | 268 | for postprocess in preset_postprocessors: |
|
0 commit comments