Skip to content

Commit 45766a8

Browse files
authored
ci: use Python 3.14 more (#3936)
1 parent 3cf6391 commit 45766a8

File tree

7 files changed

+72
-59
lines changed

7 files changed

+72
-59
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
select(.key | startswith("hatch-test"))
3333
| {
3434
name: .key,
35-
"test-type": (if (.key | test("pre|min")) then "coverage" else null end),
35+
"test-type": (if (.key | test("pre|low-vers")) then "coverage" else null end),
3636
python: .value.python
3737
}
3838
)')
@@ -44,6 +44,8 @@ jobs:
4444
strategy:
4545
matrix:
4646
env: ${{ fromJSON(needs.get-environments.outputs.envs) }}
47+
permissions:
48+
id-token: write # for codecov OIDC
4749
steps:
4850
- uses: actions/checkout@v6
4951
with: { filter: 'blob:none', fetch-depth: 0 }
@@ -77,24 +79,26 @@ jobs:
7779
hatch run ${{ matrix.env.name }}:run-cov
7880
hatch run ${{ matrix.env.name }}:cov-combine
7981
hatch run ${{ matrix.env.name }}:coverage xml
82+
hatch run ${{ matrix.env.name }}:cov-report
8083
8184
- name: Upload coverage data
8285
if: ${{ !cancelled() && matrix.env.test-type == 'coverage' }}
8386
uses: codecov/codecov-action@v5
8487
with:
85-
token: ${{ secrets.CODECOV_TOKEN }}
8688
flags: ${{ matrix.env.name }}
87-
fail_ci_if_error: true
8889
files: test-data/coverage.xml
90+
use_oidc: true
91+
fail_ci_if_error: true
8992

9093
- name: Upload test results
9194
if: ${{ !cancelled() }}
92-
uses: codecov/test-results-action@v1
95+
uses: codecov/codecov-action@v5
9396
with:
94-
token: ${{ secrets.CODECOV_TOKEN }}
97+
report_type: test_results
9598
flags: ${{ matrix.env.name }}
99+
files: test-data/test-results.xml
100+
use_oidc: true
96101
fail_ci_if_error: true
97-
file: test-data/test-results.xml
98102

99103
- name: Publish debug artifacts
100104
if: ${{ !cancelled() }}

docs/conf.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
ogp_image = "https://scanpy.readthedocs.io/en/stable/_static/Scanpy_Logo_BrightFG.svg"
127127

128128
typehints_defaults = "braces"
129+
always_use_bars_union = True # Don’t use `Union` even when building with Python ≤3.14
129130

130131
pygments_style = "default"
131132
pygments_dark_style = "native"
@@ -157,11 +158,7 @@
157158
pydeseq2=("https://pydeseq2.readthedocs.io/en/stable/", None),
158159
pynndescent=("https://pynndescent.readthedocs.io/en/latest/", None),
159160
pytest=("https://docs.pytest.org/en/latest/", None),
160-
python=(
161-
# TODO: switch to `/3` once docs are built with Python 3.14
162-
"https://docs.python.org/3.13",
163-
None,
164-
),
161+
python=("https://docs.python.org/3", None),
165162
rapids_singlecell=("https://rapids-singlecell.readthedocs.io/en/latest/", None),
166163
scipy=("https://docs.scipy.org/doc/scipy/", None),
167164
seaborn=("https://seaborn.pydata.org/", None),

hatch.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ scripts.build = "python3 ci/scripts/towncrier_automation.py {args}"
1414
scripts.clean = "git restore --source=HEAD --staged --worktree -- docs/release-notes"
1515

1616
[envs.hatch-test]
17+
python = "3.14"
1718
default-args = [ ]
1819
dependency-groups = [ "dev", "test-min" ]
1920
extra-dependencies = [ "ipykernel" ]
@@ -28,10 +29,6 @@ overrides.matrix.deps.pre-install-commands = [
2829
]
2930
overrides.matrix.deps.python = [
3031
{ if = [ "low-vers" ], value = "3.12" },
31-
{ if = [ "stable", "few-extras" ], value = "3.13" },
32-
# numba doesn’t support 3.14 in a stable release yet:
33-
# https://github.com/numba/numba/issues/9957
34-
{ if = [ "pre" ], value = "3.14" },
3532
]
3633
overrides.matrix.deps.extra-dependencies = [
3734
{ if = [ "pre" ], value = "anndata @ git+https://github.com/scverse/anndata.git" },

src/scanpy/external/pl.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,8 @@
77

88
import matplotlib.pyplot as plt
99
import numpy as np
10-
from anndata import AnnData # noqa: TC002
11-
from matplotlib.axes import Axes # noqa: TC002
12-
from sklearn.utils import deprecated
1310

14-
from .._compat import old_positionals
11+
from .._compat import deprecated, old_positionals
1512
from .._utils import _doc_params
1613
from .._utils._doctests import doctest_needs
1714
from ..plotting import _scrublet, _utils, embedding
@@ -28,6 +25,9 @@
2825
from collections.abc import Collection
2926
from typing import Any
3027

28+
from anndata import AnnData
29+
from matplotlib.axes import Axes
30+
3131

3232
__all__ = [
3333
"harmony_timeseries",

src/scanpy/external/pp/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
from __future__ import annotations
44

5-
from sklearn.utils import deprecated
6-
5+
from ..._compat import deprecated
76
from ...preprocessing import _scrublet
87
from ._bbknn import bbknn
98
from ._dca import dca

src/scanpy/external/pp/_harmony_integrate.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
from collections.abc import Sequence # noqa: TC003
65
from typing import TYPE_CHECKING
76

87
import numpy as np
@@ -11,6 +10,8 @@
1110
from ..._utils._doctests import doctest_needs
1211

1312
if TYPE_CHECKING:
13+
from collections.abc import Sequence
14+
1415
from anndata import AnnData
1516

1617

src/scanpy/plotting/_tools/scatterplots.py

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,28 @@
11
from __future__ import annotations
22

33
import inspect
4-
from collections.abc import Mapping, Sequence # noqa: TC003
4+
import re
5+
import textwrap
6+
from collections.abc import Sequence
57
from copy import copy
6-
from functools import partial
8+
from functools import cache, partial
79
from itertools import combinations, product
810
from numbers import Integral
9-
from typing import (
10-
TYPE_CHECKING,
11-
Any, # noqa: TC003
12-
Literal, # noqa: TC003
13-
)
11+
from typing import TYPE_CHECKING
1412

1513
import numpy as np
1614
import pandas as pd
17-
from anndata import AnnData # noqa: TC002
18-
from cycler import Cycler # noqa: TC002
1915
from matplotlib import colormaps, colors, patheffects, rcParams
2016
from matplotlib import pyplot as plt
21-
from matplotlib.axes import Axes # noqa: TC002
22-
from matplotlib.colors import (
23-
Colormap, # noqa: TC002
24-
Normalize,
25-
)
26-
from matplotlib.figure import Figure # noqa: TC002
17+
from matplotlib.colors import Normalize
2718
from matplotlib.markers import MarkerStyle
28-
from numpy.typing import NDArray # noqa: TC002
2919

3020
from ... import logging as logg
3121
from ..._compat import deprecated
3222
from ..._settings import settings
33-
from ..._utils import (
34-
Empty, # noqa: TC001
35-
_doc_params,
36-
_empty,
37-
sanitize_anndata,
38-
)
23+
from ..._utils import _doc_params, _empty, sanitize_anndata
3924
from ..._utils._doctests import doctest_internet
4025
from ...get import _check_mask
41-
from ...tools._draw_graph import _Layout # noqa: TC001
4226
from .. import _utils
4327
from .._docs import (
4428
doc_adata_color_etc,
@@ -47,19 +31,23 @@
4731
doc_scatter_spatial,
4832
doc_show_save_ax,
4933
)
50-
from .._utils import (
51-
ColorLike, # noqa: TC001
52-
VBound, # noqa: TC001
53-
_FontSize, # noqa: TC001
54-
_FontWeight, # noqa: TC001
55-
_LegendLoc, # noqa: TC001
56-
check_colornorm,
57-
check_projection,
58-
circles,
59-
)
34+
from .._utils import check_colornorm, check_projection, circles
6035

6136
if TYPE_CHECKING:
62-
from collections.abc import Collection
37+
from collections.abc import Callable, Collection, Mapping
38+
from types import FunctionType
39+
from typing import Any, Literal
40+
41+
from anndata import AnnData
42+
from cycler import Cycler
43+
from matplotlib.axes import Axes
44+
from matplotlib.colors import Colormap
45+
from matplotlib.figure import Figure
46+
from numpy.typing import NDArray
47+
48+
from ..._utils import Empty
49+
from ...tools._draw_graph import _Layout
50+
from .._utils import ColorLike, VBound, _FontSize, _FontWeight, _LegendLoc
6351

6452

6553
@_doc_params(
@@ -600,10 +588,29 @@ def _get_vboundnorm(
600588
return tuple(out)
601589

602590

603-
def _wraps_plot_scatter(wrapper):
591+
_TYPE_GUARD_IMPORT_RE = re.compile(r"\nif TYPE_CHECKING:[^\n]*([\s\S]*?)(?=\n\S)")
592+
593+
594+
@cache
595+
def _get_guarded_imports(obj: FunctionType) -> Mapping[str, Any]:
596+
"""Simplified version from `sphinx-autodoc-typehints`."""
597+
module = inspect.getmodule(obj)
598+
assert module
599+
code = inspect.getsource(module)
600+
rv: dict[str, Any] = {}
601+
for m in _TYPE_GUARD_IMPORT_RE.finditer(code):
602+
guarded_code = textwrap.dedent(m.group(1))
603+
rv.update(obj.__globals__)
604+
exec(guarded_code, rv)
605+
for k in obj.__globals__:
606+
del rv[k]
607+
return rv
608+
609+
610+
def _wraps_plot_scatter[**P, R](wrapper: Callable[P, R]) -> Callable[P, R]:
604611
"""Update the wrapper function to use the correct signature."""
605-
params = inspect.signature(embedding, eval_str=True).parameters.copy()
606-
wrapper_sig = inspect.signature(wrapper, eval_str=True)
612+
params = inspect.signature(embedding).parameters.copy()
613+
wrapper_sig = inspect.signature(wrapper)
607614
wrapper_params = wrapper_sig.parameters.copy()
608615

609616
params.pop("basis")
@@ -619,6 +626,14 @@ def _wraps_plot_scatter(wrapper):
619626
if wrapper_sig.return_annotation is not inspect.Signature.empty:
620627
annotations["return"] = wrapper_sig.return_annotation
621628

629+
# `sphinx-autodoc-typehints` can execute `if TYPECHECKING` blocks,
630+
# but all all users of `_wraps_plot_scatter` that aren’t in this module
631+
# won’t have any imports. So we execute and inject the imports here.
632+
wrapper.__globals__.update({
633+
k: v
634+
for k, v in {**embedding.__globals__, **_get_guarded_imports(embedding)}.items()
635+
if k not in wrapper.__globals__
636+
})
622637
wrapper.__signature__ = inspect.Signature(
623638
list(params.values()), return_annotation=wrapper_sig.return_annotation
624639
)

0 commit comments

Comments
 (0)