Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
select(.key | startswith("hatch-test"))
| {
name: .key,
"test-type": (if (.key | test("pre|min")) then "coverage" else null end),
"test-type": (if (.key | test("pre|low-vers")) then "coverage" else null end),
python: .value.python
}
)')
Expand All @@ -44,6 +44,8 @@ jobs:
strategy:
matrix:
env: ${{ fromJSON(needs.get-environments.outputs.envs) }}
permissions:
id-token: write # for codecov OIDC
steps:
- uses: actions/checkout@v6
with: { filter: 'blob:none', fetch-depth: 0 }
Expand Down Expand Up @@ -77,24 +79,26 @@ jobs:
hatch run ${{ matrix.env.name }}:run-cov
hatch run ${{ matrix.env.name }}:cov-combine
hatch run ${{ matrix.env.name }}:coverage xml
hatch run ${{ matrix.env.name }}:cov-report

- name: Upload coverage data
if: ${{ !cancelled() && matrix.env.test-type == 'coverage' }}
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: ${{ matrix.env.name }}
fail_ci_if_error: true
files: test-data/coverage.xml
use_oidc: true
fail_ci_if_error: true

- name: Upload test results
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
flags: ${{ matrix.env.name }}
files: test-data/test-results.xml
use_oidc: true
fail_ci_if_error: true
file: test-data/test-results.xml

- name: Publish debug artifacts
if: ${{ !cancelled() }}
Expand Down
7 changes: 2 additions & 5 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@
ogp_image = "https://scanpy.readthedocs.io/en/stable/_static/Scanpy_Logo_BrightFG.svg"

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

pygments_style = "default"
pygments_dark_style = "native"
Expand Down Expand Up @@ -157,11 +158,7 @@
pydeseq2=("https://pydeseq2.readthedocs.io/en/stable/", None),
pynndescent=("https://pynndescent.readthedocs.io/en/latest/", None),
pytest=("https://docs.pytest.org/en/latest/", None),
python=(
# TODO: switch to `/3` once docs are built with Python 3.14
"https://docs.python.org/3.13",
None,
),
python=("https://docs.python.org/3", None),
rapids_singlecell=("https://rapids-singlecell.readthedocs.io/en/latest/", None),
scipy=("https://docs.scipy.org/doc/scipy/", None),
seaborn=("https://seaborn.pydata.org/", None),
Expand Down
5 changes: 1 addition & 4 deletions hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ scripts.build = "python3 ci/scripts/towncrier_automation.py {args}"
scripts.clean = "git restore --source=HEAD --staged --worktree -- docs/release-notes"

[envs.hatch-test]
python = "3.14"
default-args = [ ]
dependency-groups = [ "dev", "test-min" ]
extra-dependencies = [ "ipykernel" ]
Expand All @@ -28,10 +29,6 @@ overrides.matrix.deps.pre-install-commands = [
]
overrides.matrix.deps.python = [
{ if = [ "low-vers" ], value = "3.12" },
{ if = [ "stable", "few-extras" ], value = "3.13" },
# numba doesn’t support 3.14 in a stable release yet:
# https://github.com/numba/numba/issues/9957
{ if = [ "pre" ], value = "3.14" },
]
overrides.matrix.deps.extra-dependencies = [
{ if = [ "pre" ], value = "anndata @ git+https://github.com/scverse/anndata.git" },
Expand Down
8 changes: 4 additions & 4 deletions src/scanpy/external/pl.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@

import matplotlib.pyplot as plt
import numpy as np
from anndata import AnnData # noqa: TC002
from matplotlib.axes import Axes # noqa: TC002
from sklearn.utils import deprecated

from .._compat import old_positionals
from .._compat import deprecated, old_positionals
from .._utils import _doc_params
from .._utils._doctests import doctest_needs
from ..plotting import _scrublet, _utils, embedding
Expand All @@ -28,6 +25,9 @@
from collections.abc import Collection
from typing import Any

from anndata import AnnData
from matplotlib.axes import Axes


__all__ = [
"harmony_timeseries",
Expand Down
3 changes: 1 addition & 2 deletions src/scanpy/external/pp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

from __future__ import annotations

from sklearn.utils import deprecated

from ..._compat import deprecated
from ...preprocessing import _scrublet
from ._bbknn import bbknn
from ._dca import dca
Expand Down
3 changes: 2 additions & 1 deletion src/scanpy/external/pp/_harmony_integrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

from collections.abc import Sequence # noqa: TC003
from typing import TYPE_CHECKING

import numpy as np
Expand All @@ -11,6 +10,8 @@
from ..._utils._doctests import doctest_needs

if TYPE_CHECKING:
from collections.abc import Sequence

from anndata import AnnData


Expand Down
89 changes: 52 additions & 37 deletions src/scanpy/plotting/_tools/scatterplots.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,28 @@
from __future__ import annotations

import inspect
from collections.abc import Mapping, Sequence # noqa: TC003
import re
import textwrap
from collections.abc import Sequence
from copy import copy
from functools import partial
from functools import cache, partial
from itertools import combinations, product
from numbers import Integral
from typing import (
TYPE_CHECKING,
Any, # noqa: TC003
Literal, # noqa: TC003
)
from typing import TYPE_CHECKING

import numpy as np
import pandas as pd
from anndata import AnnData # noqa: TC002
from cycler import Cycler # noqa: TC002
from matplotlib import colormaps, colors, patheffects, rcParams
from matplotlib import pyplot as plt
from matplotlib.axes import Axes # noqa: TC002
from matplotlib.colors import (
Colormap, # noqa: TC002
Normalize,
)
from matplotlib.figure import Figure # noqa: TC002
from matplotlib.colors import Normalize
from matplotlib.markers import MarkerStyle
from numpy.typing import NDArray # noqa: TC002

from ... import logging as logg
from ..._compat import deprecated
from ..._settings import settings
from ..._utils import (
Empty, # noqa: TC001
_doc_params,
_empty,
sanitize_anndata,
)
from ..._utils import _doc_params, _empty, sanitize_anndata
from ..._utils._doctests import doctest_internet
from ...get import _check_mask
from ...tools._draw_graph import _Layout # noqa: TC001
from .. import _utils
from .._docs import (
doc_adata_color_etc,
Expand All @@ -47,19 +31,23 @@
doc_scatter_spatial,
doc_show_save_ax,
)
from .._utils import (
ColorLike, # noqa: TC001
VBound, # noqa: TC001
_FontSize, # noqa: TC001
_FontWeight, # noqa: TC001
_LegendLoc, # noqa: TC001
check_colornorm,
check_projection,
circles,
)
from .._utils import check_colornorm, check_projection, circles

if TYPE_CHECKING:
from collections.abc import Collection
from collections.abc import Callable, Collection, Mapping
from types import FunctionType
from typing import Any, Literal

from anndata import AnnData
from cycler import Cycler
from matplotlib.axes import Axes
from matplotlib.colors import Colormap
from matplotlib.figure import Figure
from numpy.typing import NDArray

from ..._utils import Empty
from ...tools._draw_graph import _Layout
from .._utils import ColorLike, VBound, _FontSize, _FontWeight, _LegendLoc


@_doc_params(
Expand Down Expand Up @@ -600,10 +588,29 @@ def _get_vboundnorm(
return tuple(out)


def _wraps_plot_scatter(wrapper):
_TYPE_GUARD_IMPORT_RE = re.compile(r"\nif TYPE_CHECKING:[^\n]*([\s\S]*?)(?=\n\S)")


@cache
def _get_guarded_imports(obj: FunctionType) -> Mapping[str, Any]:
"""Simplified version from `sphinx-autodoc-typehints`."""
module = inspect.getmodule(obj)
assert module
code = inspect.getsource(module)
rv: dict[str, Any] = {}
for m in _TYPE_GUARD_IMPORT_RE.finditer(code):
guarded_code = textwrap.dedent(m.group(1))
rv.update(obj.__globals__)
exec(guarded_code, rv)
for k in obj.__globals__:
del rv[k]
return rv


def _wraps_plot_scatter[**P, R](wrapper: Callable[P, R]) -> Callable[P, R]:
"""Update the wrapper function to use the correct signature."""
params = inspect.signature(embedding, eval_str=True).parameters.copy()
wrapper_sig = inspect.signature(wrapper, eval_str=True)
params = inspect.signature(embedding).parameters.copy()
wrapper_sig = inspect.signature(wrapper)
wrapper_params = wrapper_sig.parameters.copy()

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

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