Skip to content

Commit abbee76

Browse files
authored
run doctests (#2605)
1 parent 673b109 commit abbee76

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+428
-190
lines changed

docs/conf.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def setup(app: Sphinx):
179179
# -- Suppress link warnings ----------------------------------------------------
180180

181181
qualname_overrides = {
182-
"sklearn.neighbors._dist_metrics.DistanceMetric": "sklearn.neighbors.DistanceMetric",
182+
"sklearn.neighbors._dist_metrics.DistanceMetric": "sklearn.metrics.DistanceMetric",
183183
# If the docs are built with an old version of numpy, this will make it work:
184184
"numpy.random.RandomState": "numpy.random.mtrand.RandomState",
185185
"scanpy.plotting._matrixplot.MatrixPlot": "scanpy.pl.MatrixPlot",
@@ -192,8 +192,10 @@ def setup(app: Sphinx):
192192
# Will probably be documented
193193
("py:class", "scanpy._settings.Verbosity"),
194194
("py:class", "scanpy.neighbors.OnFlySymMatrix"),
195-
# Currently undocumented: https://github.com/mwaskom/seaborn/issues/1810
195+
# Currently undocumented
196+
# https://github.com/mwaskom/seaborn/issues/1810
196197
("py:class", "seaborn.ClusterGrid"),
198+
("py:class", "samalg.SAM"),
197199
# Won’t be documented
198200
("py:class", "scanpy.plotting._utils._AxesSubplot"),
199201
("py:class", "scanpy._utils.Empty"),

docs/extensions/typed_returns.py

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ def process_return(lines):
1616

1717
def scanpy_parse_returns_section(self, section):
1818
lines_raw = list(process_return(self._dedent(self._consume_to_next_section())))
19+
if lines_raw[0] == ":":
20+
# Remove the “:” inserted by sphinx-autodoc-typehints
21+
# https://github.com/tox-dev/sphinx-autodoc-typehints/blob/a5c091f725da8374347802d54c16c3d38833d41c/src/sphinx_autodoc_typehints/patches.py#L66
22+
lines_raw.pop(0)
1923
lines = self._format_block(":returns: ", lines_raw)
2024
if lines and lines[-1]:
2125
lines.append("")

docs/release-notes/1.10.0.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313

1414
```{rubric} Docs
1515
```
16+
* Fixed a lot of broken usage examples {pr}`2605` {smaller}`P Angerer`
1617

1718
```{rubric} Bug fixes
1819
```
1920

2021
* Updated {func}`~scanpy.read_visium` such that it can read spaceranger 2.0 files {smaller}`L Lehner`
2122
* Fix {func}`~scanpy.pp.normalize_total` {pr}`2466` {smaller}`P Angerer`
2223
* Fix testing package build {pr}`2468` {smaller}`P Angerer`
23-
24+
* Fix setting `sc.settings.verbosity` in some cases {pr}`2605` {smaller}`P Angerer`
2425

2526
```{rubric} Ecosystem
2627
```

pyproject.toml

+5-2
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ scanpy = "scanpy.cli:console_main"
8282

8383
[project.optional-dependencies]
8484
test-min = [
85-
"pytest>=4.4",
85+
"pytest>=7.4.2",
8686
"pytest-nunit",
8787
"pytest-mock",
8888
"profimp",
@@ -106,7 +106,7 @@ test-full = [
106106
"scanpy[dask-ml]",
107107
]
108108
doc = [
109-
"sphinx>=4.4,<5", # remove upper bound when theme supports dark mode
109+
"sphinx>=5",
110110
"sphinx-book-theme",
111111
"scanpydoc>=0.9.5",
112112
"sphinx-autodoc-typehints",
@@ -118,7 +118,9 @@ doc = [
118118
"nbsphinx",
119119
"ipython>=7.20", # for nbsphinx code highlighting
120120
"matplotlib!=3.6.1",
121+
# TODO: remove necessity for being able to import doc-linked classes
121122
"scanpy[paga]",
123+
"sam-algorithm",
122124
]
123125
dev = [
124126
# getting the dev version
@@ -152,6 +154,7 @@ version-file = "scanpy/_version.py"
152154
addopts = [
153155
"--import-mode=importlib",
154156
"--strict-markers",
157+
"--doctest-modules",
155158
"-pscanpy.testing._pytest",
156159
]
157160
testpaths = ["scanpy"]

scanpy/_compat.py

+23
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass, field
4+
from pathlib import Path
15
from packaging import version
26

37
try:
@@ -26,6 +30,25 @@ def fullname(typ: type) -> str:
2630
return f"{module}.{name}"
2731

2832

33+
try:
34+
from contextlib import chdir
35+
except ImportError: # Python < 3.11
36+
import os
37+
from contextlib import AbstractContextManager
38+
39+
@dataclass
40+
class chdir(AbstractContextManager):
41+
path: Path
42+
_old_cwd: list[Path] = field(default_factory=list)
43+
44+
def __enter__(self) -> None:
45+
self._old_cwd.append(Path.cwd())
46+
os.chdir(self.path)
47+
48+
def __exit__(self, *_excinfo) -> None:
49+
os.chdir(self._old_cwd.pop())
50+
51+
2952
def pkg_metadata(package):
3053
from importlib.metadata import metadata as m
3154

scanpy/_settings.py

+29-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import inspect
24
import sys
35
from contextlib import contextmanager
@@ -18,25 +20,42 @@
1820
"hint": "HINT",
1921
"debug": "DEBUG",
2022
}
21-
# Python 3.7 ensures iteration order
23+
# Python 3.7+ ensures iteration order
2224
for v, level in enumerate(list(_VERBOSITY_TO_LOGLEVEL.values())):
2325
_VERBOSITY_TO_LOGLEVEL[v] = level
2426

2527

28+
# Collected from the print_* functions in matplotlib.backends
29+
_Format = Union[
30+
Literal["png", "jpg", "tif", "tiff"],
31+
Literal["pdf", "ps", "eps", "svg", "svgz", "pgf"],
32+
Literal["raw", "rgba"],
33+
]
34+
35+
2636
class Verbosity(IntEnum):
2737
error = 0
28-
warn = 1
38+
warning = 1
2939
info = 2
3040
hint = 3
3141
debug = 4
3242

43+
def __eq__(self, other: Verbosity | int | str) -> bool:
44+
if isinstance(other, Verbosity):
45+
return self is other
46+
if isinstance(other, int):
47+
return self.value == other
48+
if isinstance(other, str):
49+
return self.name == other
50+
return NotImplemented
51+
3352
@property
3453
def level(self) -> int:
3554
# getLevelName(str) returns the int level…
36-
return getLevelName(_VERBOSITY_TO_LOGLEVEL[self])
55+
return getLevelName(_VERBOSITY_TO_LOGLEVEL[self.name])
3756

3857
@contextmanager
39-
def override(self, verbosity: "Verbosity") -> ContextManager["Verbosity"]:
58+
def override(self, verbosity: Verbosity | str | int) -> ContextManager[Verbosity]:
4059
"""\
4160
Temporarily override verbosity
4261
"""
@@ -45,6 +64,10 @@ def override(self, verbosity: "Verbosity") -> ContextManager["Verbosity"]:
4564
settings.verbosity = self
4665

4766

67+
# backwards compat
68+
Verbosity.warn = Verbosity.warning
69+
70+
4871
def _type_check(var: Any, varname: str, types: Union[type, Tuple[type, ...]]):
4972
if isinstance(var, types):
5073
return
@@ -69,7 +92,7 @@ class ScanpyConfig:
6992
def __init__(
7093
self,
7194
*,
72-
verbosity: str = "warning",
95+
verbosity: Verbosity | int | str = Verbosity.warning,
7396
plot_suffix: str = "",
7497
file_format_data: str = "h5ad",
7598
file_format_figs: str = "pdf",
@@ -160,7 +183,7 @@ def verbosity(self, verbosity: Union[Verbosity, int, str]):
160183
self._verbosity = Verbosity(verbosity_str_options.index(verbosity))
161184
else:
162185
_type_check(verbosity, "verbosity", (str, int))
163-
_set_log_level(self, _VERBOSITY_TO_LOGLEVEL[self._verbosity])
186+
_set_log_level(self, _VERBOSITY_TO_LOGLEVEL[self._verbosity.name])
164187

165188
@property
166189
def plot_suffix(self) -> str:
@@ -386,15 +409,6 @@ def categories_to_ignore(self, categories_to_ignore: Iterable[str]):
386409
# Functions
387410
# --------------------------------------------------------------------------------
388411

389-
# Collected from the print_* functions in matplotlib.backends
390-
# fmt: off
391-
_Format = Literal[
392-
'png', 'jpg', 'tif', 'tiff',
393-
'pdf', 'ps', 'eps', 'svg', 'svgz', 'pgf',
394-
'raw', 'rgba',
395-
]
396-
# fmt: on
397-
398412
def set_figure_params(
399413
self,
400414
scanpy: bool = True,

scanpy/cli.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def parse_known_args(
116116

117117

118118
def _cmd_settings() -> None:
119-
from . import settings
119+
from ._settings import settings
120120

121121
print(settings)
122122

scanpy/datasets/_datasets.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,8 @@ def krumsiek11() -> ad.AnnData:
9595
Annotated data matrix.
9696
"""
9797
filename = HERE / "krumsiek11.txt"
98-
verbosity_save = settings.verbosity
99-
settings.verbosity = "error" # suppress output...
100-
adata = read(filename, first_column_names=True)
101-
settings.verbosity = verbosity_save
98+
with settings.verbosity.override("error"): # suppress output...
99+
adata = read(filename, first_column_names=True)
102100
adata.uns["iroot"] = 0
103101
fate_labels = {0: "Stem", 159: "Mo", 319: "Ery", 459: "Mk", 619: "Neu"}
104102
adata.uns["highlights"] = fate_labels

scanpy/external/pl.py

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from anndata import AnnData
77
from matplotlib.axes import Axes
88

9+
from ..testing._doctests import doctest_needs
910
from .._utils import _doc_params
1011
from ..plotting import embedding
1112
from ..plotting._docs import (
@@ -19,6 +20,7 @@
1920
from .tl._wishbone import _anndata_to_wishbone
2021

2122

23+
@doctest_needs("phate")
2224
@_wraps_plot_scatter
2325
@_doc_params(
2426
adata_color_etc=doc_adata_color_etc,

scanpy/external/pp/_bbknn.py

+13-12
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
from typing import Union, Optional, Callable
1+
from __future__ import annotations
22

3-
from anndata import AnnData
4-
import sklearn
3+
from typing import TYPE_CHECKING, Union, Optional, Callable
54

6-
from ..._utils import lazy_import
5+
from anndata import AnnData
76

7+
if TYPE_CHECKING:
8+
from sklearn.metrics import DistanceMetric
89

9-
# Import this lazily so we don’t slowly import sklearn.stats just for annotation
10-
lazy_import("sklearn.neighbors")
11-
del lazy_import
10+
from ...testing._doctests import doctest_needs
1211

1312

13+
@doctest_needs("bbknn")
1414
def bbknn(
1515
adata: AnnData,
1616
batch_key: str = "batch",
1717
use_rep: str = "X_pca",
1818
approx: bool = True,
1919
use_annoy: bool = True,
20-
metric: Union[str, Callable, "sklearn.neighbors.DistanceMetric"] = "euclidean",
20+
metric: Union[str, Callable, DistanceMetric] = "euclidean",
2121
copy: bool = False,
2222
*,
2323
neighbors_within_batch: int = 3,
@@ -55,7 +55,7 @@ def bbknn(
5555
use_rep
5656
The dimensionality reduction in `.obsm` to use for neighbour detection. Defaults to PCA.
5757
approx
58-
If `True`, use approximate neighbour finding - annoy or pyNNDescent. This results
58+
If `True`, use approximate neighbour finding - annoy or PyNNDescent. This results
5959
in a quicker run time for large datasets while also potentially increasing the degree of
6060
batch correction.
6161
use_annoy
@@ -78,12 +78,13 @@ def bbknn(
7878
'kantorovich', 'wasserstein', 'tsss', 'true_angular', 'hamming', 'jaccard', 'dice', 'matching', 'kulsinski',
7979
'rogerstanimoto', 'russellrao', 'sokalsneath', 'sokalmichener', 'yule'])
8080
81-
KDTree supports members of the `sklearn.neighbors.KDTree.valid_metrics` list, or parameterised
82-
`sklearn.neighbors.DistanceMetric` `objects
83-
<https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.DistanceMetric.html>`_:
81+
KDTree supports members of :class:`sklearn.neighbors.KDTree`’s ``valid_metrics`` list, or parameterised
82+
:class:`~sklearn.metrics.DistanceMetric` objects:
8483
8584
>>> sklearn.neighbors.KDTree.valid_metrics
8685
['p', 'chebyshev', 'cityblock', 'minkowski', 'infinity', 'l2', 'euclidean', 'manhattan', 'l1']
86+
87+
.. note:: check the relevant documentation for up-to-date lists.
8788
copy
8889
If `True`, return a copy instead of writing to the supplied adata.
8990
neighbors_within_batch

scanpy/external/pp/_harmony_integrate.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
Use harmony to integrate cells from different experiments.
33
"""
44

5-
from typing import Optional
6-
75
from anndata import AnnData
86
import numpy as np
97

8+
from ...testing._doctests import doctest_needs
9+
1010

11+
@doctest_needs("harmonypy")
1112
def harmony_integrate(
1213
adata: AnnData,
1314
key: str,

scanpy/external/pp/_hashsolo.py

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
#!/usr/bin/env python
2-
from scipy.stats import norm
3-
from scanpy._utils import check_nonnegative_integers
4-
from itertools import product
5-
import anndata
6-
import numpy as np
7-
import pandas as pd
8-
91
"""
102
HashSolo script provides a probabilistic cell hashing demultiplexing method
113
which generates a noise distribution and signal distribution for
@@ -23,6 +15,16 @@
2315
hypotheses in a bayesian fashion, and select the most probable hypothesis.
2416
"""
2517

18+
from itertools import product
19+
20+
import anndata
21+
import numpy as np
22+
import pandas as pd
23+
from scipy.stats import norm
24+
25+
from ..._utils import check_nonnegative_integers
26+
from ...testing._doctests import doctest_skip
27+
2628

2729
def _calculate_log_likelihoods(data, number_of_noise_barcodes):
2830
"""Calculate log likelihoods for each hypothesis, negative, singlet, doublet
@@ -255,6 +257,7 @@ def _calculate_bayes_rule(data, priors, number_of_noise_barcodes):
255257
}
256258

257259

260+
@doctest_skip("Illustrative but not runnable doctest code")
258261
def hashsolo(
259262
adata: anndata.AnnData,
260263
cell_hashing_columns: list,

scanpy/external/pp/_magic.py

+2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
from ... import logging as logg
1010
from ..._settings import settings
1111
from ..._utils import AnyRandom
12+
from ...testing._doctests import doctest_needs
1213

1314

1415
MIN_VERSION = "2.0"
1516

1617

18+
@doctest_needs("magic")
1719
def magic(
1820
adata: AnnData,
1921
name_list: Union[Literal["all_genes", "pca_only"], Sequence[str], None] = None,

0 commit comments

Comments
 (0)