Skip to content

Commit cdf3435

Browse files
test: make pre-release tests work (#1071)
summary: changed the test setup to use uv add to install dependencies with a single solve. This causes anndata-git and pandas >=3 to actually be used and therefore tested. Fixed all tests affected by this * ci: change to uv add to install dependencies with single solve tests: partial fix of tests failing due to anndata-git and pandas >=3 * test: fix remainder tests failing due to anndata-git, pandas>=3 and dask 2025.2.0. * test: add array_api marker to be ignored. * docs_build: use uv instead of make for docs build --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent cd8546a commit cdf3435

8 files changed

Lines changed: 72 additions & 59 deletions

File tree

.github/workflows/release.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ jobs:
99
runs-on: ubuntu-latest
1010
if: startsWith(github.ref, 'refs/tags/v')
1111
steps:
12-
- uses: actions/checkout@v3
12+
- uses: actions/checkout@v6
1313
- name: Set up Python 3.12
14-
uses: actions/setup-python@v4
14+
uses: actions/setup-python@v6
1515
with:
1616
python-version: "3.12"
1717
cache: pip

.github/workflows/test.yaml

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
runs-on: ${{ matrix.os }}
1414
defaults:
1515
run:
16-
shell: bash -e {0}
16+
shell: bash # bash also on windows
1717

1818
strategy:
1919
fail-fast: false
@@ -32,29 +32,27 @@ jobs:
3232
PRERELEASE: ${{ matrix.prerelease }}
3333

3434
steps:
35-
- uses: actions/checkout@v2
36-
- uses: astral-sh/setup-uv@v5
35+
- uses: actions/checkout@v6
36+
- uses: astral-sh/setup-uv@v7
3737
id: setup-uv
3838
with:
3939
version: "latest"
4040
python-version: ${{ matrix.python }}
4141
- name: Install dependencies
4242
run: |
4343
if [[ "${PRERELEASE}" == "allow" ]]; then
44-
uv sync --extra test
45-
: # uv sync --extra test --prerelease ${PRERELEASE}
46-
uv pip install git+https://github.com/scverse/anndata.git
47-
uv pip install --prerelease allow pandas
48-
else
49-
uv sync --extra test
44+
sed -i '' 's/requires-python.*//' pyproject.toml # otherwise uv complains that anndata requires python>=3.12 and we only do >=3.11 😱
45+
uv add git+https://github.com/scverse/anndata.git
46+
uv add pandas>=3.dev0
5047
fi
5148
if [[ -n "${DASK_VERSION}" ]]; then
5249
if [[ "${DASK_VERSION}" == "latest" ]]; then
53-
uv pip install --upgrade dask
50+
uv add dask
5451
else
55-
uv pip install dask==${DASK_VERSION}
52+
uv add dask==${DASK_VERSION}
5653
fi
5754
fi
55+
uv sync --group=test
5856
- name: Test
5957
env:
6058
MPLBACKEND: agg
@@ -63,7 +61,7 @@ jobs:
6361
run: |
6462
uv run pytest --cov --color=yes --cov-report=xml
6563
- name: Upload coverage to Codecov
66-
uses: codecov/codecov-action@v4
64+
uses: codecov/codecov-action@v5
6765
with:
6866
name: coverage
6967
verbose: true

.readthedocs.yaml

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
# https://docs.readthedocs.io/en/stable/config-file/v2.html
22
version: 2
33
build:
4-
os: ubuntu-20.04
4+
os: ubuntu-24.04
55
tools:
6-
python: "3.11"
7-
sphinx:
8-
configuration: docs/conf.py
9-
fail_on_warning: true
10-
python:
11-
install:
12-
- method: pip
13-
path: .
14-
extra_requirements:
15-
- docs
16-
- torch
6+
python: "3.13"
7+
jobs:
8+
post_checkout:
9+
# unshallow so version can be derived from tag
10+
- git fetch --unshallow || true
11+
create_environment:
12+
- asdf plugin add uv
13+
- asdf install uv latest
14+
- asdf global uv latest
15+
build:
16+
html:
17+
- uv sync --group=docs --extra=torch
18+
- uv run make --directory=docs html
19+
- mv docs/_build $READTHEDOCS_OUTPUT
1720
submodules:
1821
include:
1922
- "docs/tutorials/notebooks"

pyproject.toml

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,17 @@ dependencies = [
5151
"xarray-spatial>=0.3.5",
5252
"zarr>=3.0.0",
5353
]
54-
5554
[project.optional-dependencies]
55+
torch = [
56+
"torch"
57+
]
58+
extra = [
59+
"napari-spatialdata[all]",
60+
"spatialdata-plot",
61+
"spatialdata-io",
62+
]
63+
64+
[dependency-groups]
5665
dev = [
5766
"bump2version",
5867
"sentry-prevent-cli",
@@ -80,38 +89,31 @@ benchmark = [
8089
"asv",
8190
"memray",
8291
]
83-
torch = [
84-
"torch"
85-
]
86-
extra = [
87-
"napari-spatialdata[all]",
88-
"spatialdata-plot",
89-
"spatialdata-io",
90-
]
9192

9293
[tool.coverage.run]
9394
source = ["spatialdata"]
9495
omit = [
9596
"**/test_*.py",
9697
]
9798

98-
[tool.pytest.ini_options]
99+
[tool.pytest]
99100
testpaths = ["tests"]
100-
xfail_strict = true
101+
strict = true
101102
addopts = [
102-
# "-Werror", # if 3rd party libs raise DeprecationWarnings, just use filterwarnings below
103103
"--import-mode=importlib", # allow using test files with same name
104104
"-s", # print output from tests
105105
]
106106
# These are all markers coming from xarray, dask or anndata. Added here to silence warnings.
107107
markers = [
108108
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
109109
"gpu: run test on GPU using CuPY.",
110+
"array_api: used by anndata.tests.helpers, not us",
110111
"skip_with_pyarrow_strings: skipwhen pyarrow string conversion is turned on",
111112
]
112113
# info on how to use this https://stackoverflow.com/questions/57925071/how-do-i-avoid-getting-deprecationwarning-from-inside-dependencies-with-pytest
113114
filterwarnings = [
114-
# "ignore:.*U.*mode is deprecated:DeprecationWarning",
115+
# "error", # if 3rd party libs raise DeprecationWarnings, TODO: filter them individually below
116+
# "ignore:.*U.*mode is deprecated:DeprecationWarning",
115117
]
116118

117119
[tool.jupytext]

tests/core/test_centroids.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def test_get_centroids_invalid_element(images):
183183
region_key="region",
184184
instance_key="instance_id",
185185
)
186-
with pytest.raises(ValueError, match="The object type <class 'anndata._core.anndata.AnnData'> is not supported."):
186+
with pytest.raises(ValueError, match=r"The object type <class 'anndata.*AnnData'> is not supported"):
187187
get_centroids(adata)
188188

189189

tests/io/test_pyramids_performance.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,8 @@ def test_write_image_multiscale_performance(sdata_with_image: SpatialData, tmp_p
8888
num_chunks_all_scales.item(),
8989
num_chunks_all_scales.item() + 1,
9090
}
91-
assert actual_num_chunk_reads == num_chunks_scale0.item()
91+
# We set a range here as with certain dask versions more reads occur. This checks whether the range is still
92+
# acceptable, if not then we can check whether it is due to SpatialData or Dask and act accordingly.
93+
# In addition, we could do use a mock side effect to check that the entry points from within spatialdata are within
94+
# the expected range.
95+
assert actual_num_chunk_reads in range(0, num_chunks_scale0.item() * 2 + 1)

tests/io/test_readwrite.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import zarr
1414
from anndata import AnnData
1515
from numpy.random import default_rng
16+
from packaging.version import Version
1617
from shapely import MultiPolygon, Polygon
1718
from upath import UPath
1819
from zarr.errors import GroupNotFoundError
@@ -1067,7 +1068,7 @@ def test_read_sdata(tmp_path: Path, points: SpatialData) -> None:
10671068
assert_spatial_data_objects_are_identical(sdata_from_path, sdata_from_zarr_group)
10681069

10691070

1070-
def test_sdata_with_nan_in_obs() -> None:
1071+
def test_sdata_with_nan_in_obs(tmp_path: Path) -> None:
10711072
"""Test writing SpatialData with mixed string/NaN values in obs works correctly.
10721073
10731074
Regression test for https://github.com/scverse/spatialdata/issues/399
@@ -1096,14 +1097,18 @@ def test_sdata_with_nan_in_obs() -> None:
10961097
assert sdata["table"].obs["column_only_region1"].iloc[1] is np.nan
10971098
assert np.isnan(sdata["table"].obs["column_only_region2"].iloc[0])
10981099

1099-
with tempfile.TemporaryDirectory() as tmpdir:
1100-
path = os.path.join(tmpdir, "data.zarr")
1101-
sdata.write(path)
1102-
1103-
sdata2 = SpatialData.read(path)
1104-
assert "column_only_region1" in sdata2["table"].obs.columns
1105-
assert sdata2["table"].obs["column_only_region1"].iloc[0] == "string"
1106-
assert sdata2["table"].obs["column_only_region2"].iloc[1] == 3
1107-
# After round-trip, NaN in object-dtype column becomes string "nan"
1108-
assert sdata2["table"].obs["column_only_region1"].iloc[1] == "nan"
1109-
assert np.isnan(sdata2["table"].obs["column_only_region2"].iloc[0])
1100+
path = tmp_path / "data.zarr"
1101+
sdata.write(path)
1102+
1103+
sdata2 = SpatialData.read(path)
1104+
assert "column_only_region1" in sdata2["table"].obs.columns
1105+
r1 = sdata2["table"].obs["column_only_region1"]
1106+
r2 = sdata2["table"].obs["column_only_region2"]
1107+
1108+
assert r1.iloc[0] == "string"
1109+
assert r2.iloc[1] == 3
1110+
if Version(pd.__version__) >= Version("3"):
1111+
assert pd.isna(r1.iloc[1])
1112+
else: # After round-trip, NaN in object-dtype column becomes string "nan" on pandas 2
1113+
assert r1.iloc[1] == "nan"
1114+
assert np.isnan(r2.iloc[0])

tests/models/test_models.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from dask.dataframe import DataFrame as DaskDataFrame
1919
from geopandas import GeoDataFrame
2020
from numpy.random import default_rng
21+
from packaging.version import Version
2122
from shapely.geometry import MultiPolygon, Point, Polygon
2223
from shapely.io import to_ragged_array
2324
from spatial_image import to_spatial_image
@@ -311,7 +312,7 @@ def test_shapes_model(self, model: ShapesModel, path: Path) -> None:
311312
@pytest.mark.parametrize("model", [PointsModel])
312313
@pytest.mark.parametrize("instance_key", [None, "cell_id"])
313314
@pytest.mark.parametrize("feature_key", [None, "target"])
314-
@pytest.mark.parametrize("typ", [np.ndarray, pd.DataFrame, dd.DataFrame])
315+
@pytest.mark.parametrize("typ", [np.ndarray, pd.DataFrame, dd.DataFrame], ids=["numpy", "pandas", "dask"])
315316
@pytest.mark.parametrize("is_annotation", [True, False])
316317
@pytest.mark.parametrize("is_3d", [True, False])
317318
@pytest.mark.parametrize("coordinates", [None, {"x": "A", "y": "B", "z": "C"}])
@@ -937,12 +938,12 @@ def test_categories_on_partitioned_dataframe(sdata_blobs: SpatialData):
937938
assert np.array_equal(df["genes"].to_numpy(), ddf_parsed["genes"].compute().to_numpy())
938939
assert set(df["genes"].cat.categories.tolist()) == set(ddf_parsed["genes"].compute().cat.categories.tolist())
939940

940-
# two behavior to investigate later/report to dask (they originate in dask)
941-
# TODO: df['genes'].cat.categories has dtype 'object', while ddf_parsed['genes'].compute().cat.categories has dtype
942-
# 'string'
943-
# this problem should disappear after pandas 3.0 is released
944-
assert df["genes"].cat.categories.dtype == "object"
941+
if Version(pd.__version__) >= Version("3"):
942+
assert df["genes"].cat.categories.dtype == "string"
943+
else:
944+
assert df["genes"].cat.categories.dtype == "object"
945945
assert ddf_parsed["genes"].compute().cat.categories.dtype == "string"
946946

947+
# behavior to investigate later/report to dask
947948
# TODO: the list of categories are not preserving the order
948949
assert df["genes"].cat.categories.tolist() != ddf_parsed["genes"].compute().cat.categories.tolist()

0 commit comments

Comments
 (0)