From 24a161f63399abc508e08a6772afa7343cf69d75 Mon Sep 17 00:00:00 2001 From: Luca Marconato Date: Tue, 5 May 2026 13:59:13 +0200 Subject: [PATCH 1/9] reduce warnings and fix pandas CoW issues - Use zip(strict=True) when converting tuple chunks to dict in RasterSchema - Copy AnnData views before modifying .uns in TableModel.parse - Use explicit string obs index in AnnData construction to avoid implicit index warnings - Add .copy() after compute()/slice to avoid pandas SettingWithCopyWarning (CoW) - Remove unused docs/_templates autosummary files - Update tutorials submodule Co-Authored-By: Claude Sonnet 4.6 --- docs/_templates/.gitkeep | 0 docs/_templates/autosummary/base.rst | 5 -- docs/_templates/autosummary/class.rst | 61 ------------------- docs/_templates/autosummary/function.rst | 5 -- docs/tutorials/notebooks | 2 +- src/spatialdata/datasets.py | 3 +- src/spatialdata/models/models.py | 5 ++ tests/conftest.py | 9 ++- .../operations/test_spatialdata_operations.py | 2 +- tests/core/query/test_relational_query.py | 4 +- tests/io/test_readwrite.py | 3 +- 11 files changed, 19 insertions(+), 80 deletions(-) delete mode 100644 docs/_templates/.gitkeep delete mode 100644 docs/_templates/autosummary/base.rst delete mode 100644 docs/_templates/autosummary/class.rst delete mode 100644 docs/_templates/autosummary/function.rst diff --git a/docs/_templates/.gitkeep b/docs/_templates/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/_templates/autosummary/base.rst b/docs/_templates/autosummary/base.rst deleted file mode 100644 index e81ca6450..000000000 --- a/docs/_templates/autosummary/base.rst +++ /dev/null @@ -1,5 +0,0 @@ -:github_url: {{ fullname }} - -{% extends "!autosummary/base.rst" %} - -.. http://www.sphinx-doc.org/en/stable/ext/autosummary.html#customizing-templates diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst deleted file mode 100644 index e4665dfc7..000000000 --- a/docs/_templates/autosummary/class.rst +++ /dev/null @@ -1,61 +0,0 @@ -{{ fullname | escape | underline}} - -.. currentmodule:: {{ module }} - -.. add toctree option to make autodoc generate the pages - -.. autoclass:: {{ objname }} - -{% block attributes %} -{% if attributes %} -Attributes table -~~~~~~~~~~~~~~~~~~ - -.. autosummary:: -{% for item in attributes %} - ~{{ fullname }}.{{ item }} -{%- endfor %} -{% endif %} -{% endblock %} - -{% block methods %} -{% if methods %} -Methods table -~~~~~~~~~~~~~ - -.. autosummary:: -{% for item in methods %} - {%- if item != '__init__' %} - ~{{ fullname }}.{{ item }} - {%- endif -%} -{%- endfor %} -{% endif %} -{% endblock %} - -{% block attributes_documentation %} -{% if attributes %} -Attributes -~~~~~~~~~~~ - -{% for item in attributes %} - -.. autoattribute:: {{ [objname, item] | join(".") }} -{%- endfor %} - -{% endif %} -{% endblock %} - -{% block methods_documentation %} -{% if methods %} -Methods -~~~~~~~ - -{% for item in methods %} -{%- if item != '__init__' %} - -.. automethod:: {{ [objname, item] | join(".") }} -{%- endif -%} -{%- endfor %} - -{% endif %} -{% endblock %} diff --git a/docs/_templates/autosummary/function.rst b/docs/_templates/autosummary/function.rst deleted file mode 100644 index 097114364..000000000 --- a/docs/_templates/autosummary/function.rst +++ /dev/null @@ -1,5 +0,0 @@ -:github_url: {{ fullname }} - -{{ fullname | escape | underline}} - -.. autofunction:: {{ fullname }} diff --git a/docs/tutorials/notebooks b/docs/tutorials/notebooks index 8774b0d92..37e6d233b 160000 --- a/docs/tutorials/notebooks +++ b/docs/tutorials/notebooks @@ -1 +1 @@ -Subproject commit 8774b0d927e1d5ad38aec8f545c7bf0591c77fe7 +Subproject commit 37e6d233b3b39a09be6006a3ef923a11fa8e28b6 diff --git a/src/spatialdata/datasets.py b/src/spatialdata/datasets.py index ea38d739b..49f5d7269 100644 --- a/src/spatialdata/datasets.py +++ b/src/spatialdata/datasets.py @@ -367,7 +367,8 @@ def blobs_annotating_element(name: BlobsTypes) -> SpatialData: index = sdata[name].index instance_id = index.compute().tolist() if isinstance(index, dask.dataframe.Index) else index.tolist() n = len(instance_id) - new_table = AnnData(shape=(n, 0), obs={"region": pd.Categorical([name] * n), "instance_id": instance_id}) + obs_df = pd.DataFrame({"region": pd.Categorical([name] * n), "instance_id": instance_id}, index=[str(i) for i in range(n)]) + new_table = AnnData(shape=(n, 0), obs=obs_df) new_table = TableModel.parse(new_table, region=name, region_key="region", instance_key="instance_id") del sdata.tables["table"] sdata["table"] = new_table diff --git a/src/spatialdata/models/models.py b/src/spatialdata/models/models.py index e834ad78d..88ec89c97 100644 --- a/src/spatialdata/models/models.py +++ b/src/spatialdata/models/models.py @@ -242,6 +242,8 @@ def parse( else: # Chunk single scale images if chunks is not None: + if isinstance(chunks, tuple): + chunks = {dim: chunk for dim, chunk in zip(data.dims, chunks, strict=True)} data = data.chunk(chunks=chunks) cls()._check_chunk_size_not_too_large(data) # recompute coordinates for (multiscale) spatial image @@ -1153,6 +1155,9 @@ def parse( The parsed data. """ validate_table_attr_keys(adata) + # Convert view to actual copy to avoid ImplicitModificationWarning when modifying .uns + if adata.is_view: + adata = adata.copy() # either all live in adata.uns or all be passed in as argument n_args = sum([region is not None, region_key is not None, instance_key is not None]) if n_args == 0: diff --git a/tests/conftest.py b/tests/conftest.py index 7149b4e72..a2481e341 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -540,7 +540,8 @@ def complex_sdata() -> SpatialData: "instance_id": range(1, 51), # Skip background (0) "cell_type": pd.Categorical(RNG.choice(["T cell", "B cell", "Macrophage"], size=50)), "size": RNG.uniform(10, 100, size=50), - } + }, + index=[str(i) for i in range(50)], ) var1 = pd.DataFrame( @@ -575,7 +576,8 @@ def complex_sdata() -> SpatialData: "category": pd.Categorical(RNG.choice(["A", "B", "C"], size=total_items)), "value": RNG.normal(size=total_items), "count": RNG.poisson(10, size=total_items), - } + }, + index=[str(i) for i in range(total_items)], ) var2 = pd.DataFrame( @@ -606,7 +608,8 @@ def complex_sdata() -> SpatialData: "cluster": pd.Categorical(RNG.choice(["cluster_1", "cluster_2", "cluster_3"], size=40)), "sample": pd.Categorical(["sample_A"] * 20 + ["sample_B"] * 20), "qc_pass": RNG.choice([True, False], p=[0.8, 0.2], size=40), - } + }, + index=[str(i) for i in range(40)], ) var3 = pd.DataFrame( diff --git a/tests/core/operations/test_spatialdata_operations.py b/tests/core/operations/test_spatialdata_operations.py index 6ea4661d3..cd34c16a6 100644 --- a/tests/core/operations/test_spatialdata_operations.py +++ b/tests/core/operations/test_spatialdata_operations.py @@ -617,7 +617,7 @@ def test_transform_to_data_extent(full_sdata: SpatialData, maintain_positioning: "poly", ] full_sdata = full_sdata.subset(elements) - points = full_sdata["points_0"].compute() + points = full_sdata["points_0"].compute().copy() # .copy() avoids SettingWithCopyWarning when adding column below (pandas CoW) points["z"] = points["x"] points = PointsModel.parse(points) full_sdata["points_0_3d"] = points diff --git a/tests/core/query/test_relational_query.py b/tests/core/query/test_relational_query.py index 3267b1e55..9b7b07877 100644 --- a/tests/core/query/test_relational_query.py +++ b/tests/core/query/test_relational_query.py @@ -401,8 +401,8 @@ def test_match_rows_inner_join_non_matching_element(sdata_query_aggregation): def test_match_rows_inner_join_non_matching_table(sdata_query_aggregation): sdata = sdata_query_aggregation - table = sdata["table"][3:] - original_instance_id = table.obs["instance_id"] + table = sdata["table"][3:].copy() + original_instance_id = table.obs["instance_id"].copy() reversed_instance_id = [6, 7, 8, 3, 4, 5] + list(reversed(range(12))) table.obs["instance_id"] = reversed_instance_id sdata["table"] = table diff --git a/tests/io/test_readwrite.py b/tests/io/test_readwrite.py index af028d29c..c870be996 100644 --- a/tests/io/test_readwrite.py +++ b/tests/io/test_readwrite.py @@ -1085,7 +1085,8 @@ def test_sdata_with_nan_in_obs() -> None: "instance": [0, 0], "column_only_region1": ["string", np.nan], "column_only_region2": [np.nan, 3], - } + }, + index=["0", "1"], ) ), region_key="region", From 0866ab6d8b210a5e1ff5d5a9a8741a86865f29e4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 12:07:16 +0000 Subject: [PATCH 2/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/spatialdata/datasets.py | 4 +++- tests/core/operations/test_spatialdata_operations.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/spatialdata/datasets.py b/src/spatialdata/datasets.py index b62455f30..37f529c72 100644 --- a/src/spatialdata/datasets.py +++ b/src/spatialdata/datasets.py @@ -397,7 +397,9 @@ def blobs_annotating_element(name: BlobsTypes) -> SpatialData: index = sdata[name].index instance_id = index.compute().tolist() if isinstance(index, dask.dataframe.Index) else index.tolist() n = len(instance_id) - obs_df = pd.DataFrame({"region": pd.Categorical([name] * n), "instance_id": instance_id}, index=[str(i) for i in range(n)]) + obs_df = pd.DataFrame( + {"region": pd.Categorical([name] * n), "instance_id": instance_id}, index=[str(i) for i in range(n)] + ) new_table = AnnData(shape=(n, 0), obs=obs_df) new_table = TableModel.parse(new_table, region=name, region_key="region", instance_key="instance_id") del sdata.tables["table"] diff --git a/tests/core/operations/test_spatialdata_operations.py b/tests/core/operations/test_spatialdata_operations.py index 5bf5c5e9f..65a62d91d 100644 --- a/tests/core/operations/test_spatialdata_operations.py +++ b/tests/core/operations/test_spatialdata_operations.py @@ -619,7 +619,9 @@ def test_transform_to_data_extent(full_sdata: SpatialData, maintain_positioning: "poly", ] full_sdata = full_sdata.subset(elements) - points = full_sdata["points_0"].compute().copy() # .copy() avoids SettingWithCopyWarning when adding column below (pandas CoW) + points = ( + full_sdata["points_0"].compute().copy() + ) # .copy() avoids SettingWithCopyWarning when adding column below (pandas CoW) points["z"] = points["x"] points = PointsModel.parse(points) full_sdata["points_0_3d"] = points From 1fd8c0bae24507e21e1dbd442a2ae49c5c5952c7 Mon Sep 17 00:00:00 2001 From: Luca Marconato Date: Tue, 5 May 2026 14:08:42 +0200 Subject: [PATCH 3/9] fix pre-commit --- src/spatialdata/models/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatialdata/models/models.py b/src/spatialdata/models/models.py index 352343a88..cad35baf7 100644 --- a/src/spatialdata/models/models.py +++ b/src/spatialdata/models/models.py @@ -266,7 +266,7 @@ def parse( # Chunk single scale images if chunks is not None: if isinstance(chunks, tuple): - chunks = {dim: chunk for dim, chunk in zip(data.dims, chunks, strict=True)} + chunks = dict(zip(data.dims, chunks, strict=True)) data = data.chunk(chunks=chunks) # recompute coordinates for (multiscale) spatial image data = compute_coordinates(data) From da5d363ac5e6084e8c8ee54366815866c9f7567b Mon Sep 17 00:00:00 2001 From: Luca Marconato Date: Tue, 5 May 2026 14:55:16 +0200 Subject: [PATCH 4/9] fix: avoid coordinate column collision in test_points_model to suppress INFO noise Build the dataframe with only the coordinate columns actually needed for each branch: source columns (A/B/C) when a coordinates mapping is provided, target columns (x/y/z) otherwise. This prevents the rename-drop and z-ignored-in-2D INFO messages (240 occurrences) from firing during tests. Co-Authored-By: Claude Sonnet 4.6 --- tests/models/test_models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/models/test_models.py b/tests/models/test_models.py index 22e63cc1a..7b914bb76 100644 --- a/tests/models/test_models.py +++ b/tests/models/test_models.py @@ -333,9 +333,9 @@ def test_points_model( return if coordinates is not None: coordinates = coordinates.copy() - coords = ["A", "B", "C", "x", "y", "z"] n = 10 - data = pd.DataFrame(RNG.integers(0, 101, size=(n, 6)), columns=coords) + coord_cols = ["A", "B", "C"] if coordinates is not None else ["x", "y", "z"] + data = pd.DataFrame(RNG.integers(0, 101, size=(n, len(coord_cols))), columns=coord_cols) data["target"] = pd.Series(RNG.integers(0, 2, size=(n,))).astype(str) data["cell_id"] = pd.Series(RNG.integers(0, 5, size=(n,))).astype(np.int_) data["anno"] = pd.Series(RNG.integers(0, 1, size=(n,))).astype(np.int_) From 1fe839be60df7cc924f6b2f0ffc35d1ccbac886a Mon Sep 17 00:00:00 2001 From: Luca Marconato Date: Tue, 5 May 2026 15:23:35 +0200 Subject: [PATCH 5/9] fix: use pd.Categorical for feature_key column in test_points_model to suppress WARNING noise The target column was a plain str Series; PointsModel.parse() converted it to an unknown-categories dask Categorical, triggering a performance warning on every parametrized invocation. Using pd.Categorical with known categories up-front avoids the conversion path entirely. Co-Authored-By: Claude Sonnet 4.6 --- tests/models/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/models/test_models.py b/tests/models/test_models.py index 7b914bb76..e9b81942c 100644 --- a/tests/models/test_models.py +++ b/tests/models/test_models.py @@ -336,7 +336,7 @@ def test_points_model( n = 10 coord_cols = ["A", "B", "C"] if coordinates is not None else ["x", "y", "z"] data = pd.DataFrame(RNG.integers(0, 101, size=(n, len(coord_cols))), columns=coord_cols) - data["target"] = pd.Series(RNG.integers(0, 2, size=(n,))).astype(str) + data["target"] = pd.Categorical(pd.Series(RNG.integers(0, 2, size=(n,))).astype(str)) data["cell_id"] = pd.Series(RNG.integers(0, 5, size=(n,))).astype(np.int_) data["anno"] = pd.Series(RNG.integers(0, 1, size=(n,))).astype(np.int_) # to test for non-contiguous indices From 0d9aa8c8ec7e591fb317c61a6d027960d986bf93 Mon Sep 17 00:00:00 2001 From: Luca Marconato Date: Tue, 5 May 2026 17:36:30 +0200 Subject: [PATCH 6/9] fix: suppress 'not stored in most current format' UserWarning in old-format tests Use `re.match`-compatible pattern (anchored at start) and add `@pytest.mark.filterwarnings` to standalone parametrized tests outside `TestReadWrite` that intentionally exercise the V01 container format. Co-Authored-By: Claude Sonnet 4.6 --- tests/io/test_attrs_io.py | 19 ++++++++++++++++++- tests/io/test_format.py | 9 +++++++++ tests/io/test_partial_read.py | 34 ++++++++++++++++++++++++++-------- tests/io/test_readwrite.py | 20 ++++++++++++++++++++ 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/tests/io/test_attrs_io.py b/tests/io/test_attrs_io.py index 1f9895eaa..dfe0fac46 100644 --- a/tests/io/test_attrs_io.py +++ b/tests/io/test_attrs_io.py @@ -3,6 +3,7 @@ from __future__ import annotations import tempfile +import warnings from pathlib import Path import pytest @@ -11,6 +12,7 @@ from spatialdata._io.format import ( SpatialDataContainerFormats, SpatialDataContainerFormatType, + CurrentSpatialDataContainerFormat, ) FORMAT_V01 = SpatialDataContainerFormats["0.1"] @@ -21,6 +23,17 @@ class TestAttrsIO: """Test SpatialData.attrs read/write for all container formats.""" + @pytest.fixture(autouse=True) + def _suppress_old_format_warning(self, sdata_container_format: SpatialDataContainerFormatType): + if isinstance(sdata_container_format, CurrentSpatialDataContainerFormat): + yield + else: + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", message="SpatialData is not stored in the most current format", category=UserWarning + ) + yield + def test_attrs_write_and_read( self, sdata_container_format: SpatialDataContainerFormatType, @@ -111,7 +124,11 @@ def test_attrs_v1_to_v2() -> None: sdata.write(f_v1, sdata_formats=FORMAT_V01) # Read with V01 - sdata_v1 = read_zarr(f_v1) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", message="SpatialData is not stored in the most current format", category=UserWarning + ) + sdata_v1 = read_zarr(f_v1) assert sdata_v1.attrs == my_attrs # Write with V02 diff --git a/tests/io/test_format.py b/tests/io/test_format.py index 3ef1319c3..9499bf3eb 100644 --- a/tests/io/test_format.py +++ b/tests/io/test_format.py @@ -2,6 +2,7 @@ import json import tempfile +import warnings from pathlib import Path from typing import Any @@ -125,6 +126,14 @@ def test_format_raster_v1_v2_v3(self, images, rformat: type[SpatialDataFormatTyp class TestFormatConversions: """Test format conversions between older formats and newer.""" + @pytest.fixture(autouse=True) + def _suppress_old_format_warning(self): + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", message="SpatialData is not stored in the most current format", category=UserWarning + ) + yield + def test_shapes_v1_to_v2_to_v3(self, shapes): with tempfile.TemporaryDirectory() as tmpdir: f1 = Path(tmpdir) / "data1.zarr" diff --git a/tests/io/test_partial_read.py b/tests/io/test_partial_read.py index 28460046e..444e8a1c8 100644 --- a/tests/io/test_partial_read.py +++ b/tests/io/test_partial_read.py @@ -4,9 +4,10 @@ import os import re import tempfile +import warnings from collections.abc import Generator, Iterable -from contextlib import contextmanager -from dataclasses import dataclass +from contextlib import contextmanager, nullcontext +from dataclasses import dataclass, field from json import JSONDecodeError from pathlib import Path from typing import TYPE_CHECKING @@ -69,6 +70,7 @@ class PartialReadTestCase: expected_elements: list[str] expected_exceptions: type[Exception] | tuple[type[Exception] | IOError, ...] warnings_patterns: list[str] + zarr_version: int = field(default=3) @pytest.fixture(scope="session") @@ -103,6 +105,7 @@ def sdata_with_corrupted_elem_types_zgroup(session_tmp_path: Path) -> PartialRea expected_elements=not_corrupted, expected_exceptions=(JSONDecodeError, ZarrUserWarning), warnings_patterns=["labels: JSONDecodeError", "Object at"], + zarr_version=2, ) @@ -173,6 +176,7 @@ def sdata_with_corrupted_zattrs_elements(session_tmp_path: Path) -> PartialReadT expected_elements=not_corrupted, expected_exceptions=(OSError, JSONDecodeError), warnings_patterns=warnings_patterns, + zarr_version=2, ) @@ -216,6 +220,7 @@ def sdata_with_corrupted_image_chunks_zarrv2(session_tmp_path: Path) -> PartialR expected_elements=not_corrupted, expected_exceptions=(ArrayNotFoundError,), warnings_patterns=[rf"images/{corrupted}: ArrayNotFoundError"], + zarr_version=2, ) @@ -264,6 +269,7 @@ def sdata_with_corrupted_parquet_zarrv2(session_tmp_path: Path) -> PartialReadTe expected_elements=not_corrupted, expected_exceptions=ArrowInvalid, warnings_patterns=[rf"points/{corrupted}: ArrowInvalid"], + zarr_version=2, ) @@ -303,6 +309,7 @@ def sdata_with_missing_zattrs_element(session_tmp_path: Path) -> PartialReadTest expected_elements=not_corrupted, expected_exceptions=OSError, warnings_patterns=["OSError: Image location"], + zarr_version=2, ) @@ -349,6 +356,7 @@ def sdata_with_missing_image_chunks_zarrv2( expected_elements=not_corrupted, expected_exceptions=(ArrayNotFoundError,), warnings_patterns=[rf"images/{corrupted}: (ArrayNotFoundError|TypeError)"], + zarr_version=2, ) @@ -372,6 +380,7 @@ def sdata_with_invalid_zattrs_element_violating_spec(session_tmp_path: Path) -> expected_elements=not_corrupted, expected_exceptions=KeyError, warnings_patterns=[rf"images/{corrupted}: KeyError: coordinateTransformations"], + zarr_version=2, ) @@ -424,6 +433,7 @@ def _create_sdata_with_table_region_not_found(session_tmp_path: Path, zarr_versi warnings_patterns=[ rf"The table is annotating '{re.escape(corrupted)}', which is not present in the SpatialData object" ], + zarr_version=zarr_version, ) @@ -460,11 +470,15 @@ def sdata_with_table_region_not_found_zarrv2(session_tmp_path: Path) -> PartialR indirect=True, ) def test_read_zarr_with_error(test_case: PartialReadTestCase): - if test_case.expected_exceptions: - with pytest.raises(test_case.expected_exceptions): + ctx = warnings.catch_warnings() if test_case.zarr_version < 3 else nullcontext() + with ctx: + if test_case.zarr_version < 3: + warnings.filterwarnings("ignore", message="SpatialData is not stored in the most current format", category=UserWarning) + if test_case.expected_exceptions: + with pytest.raises(test_case.expected_exceptions): + read_zarr(test_case.path, on_bad_files="error") + else: read_zarr(test_case.path, on_bad_files="error") - else: - read_zarr(test_case.path, on_bad_files="error") @pytest.mark.parametrize( @@ -490,8 +504,12 @@ def test_read_zarr_with_error(test_case: PartialReadTestCase): indirect=True, ) def test_read_zarr_with_warnings(test_case: PartialReadTestCase): - with pytest_warns_multiple(UserWarning, matches=test_case.warnings_patterns): - actual: SpatialData = read_zarr(test_case.path, on_bad_files="warn") + ctx = warnings.catch_warnings() if test_case.zarr_version < 3 else nullcontext() + with ctx: + if test_case.zarr_version < 3: + warnings.filterwarnings("ignore", message="SpatialData is not stored in the most current format", category=UserWarning) + with pytest_warns_multiple(UserWarning, matches=test_case.warnings_patterns): + actual: SpatialData = read_zarr(test_case.path, on_bad_files="warn") actual_elements = {name for _, name, _ in actual.gen_elements()} assert set(test_case.expected_elements) == actual_elements diff --git a/tests/io/test_readwrite.py b/tests/io/test_readwrite.py index ceeb14707..78dfc6207 100644 --- a/tests/io/test_readwrite.py +++ b/tests/io/test_readwrite.py @@ -3,6 +3,7 @@ import json import os import tempfile +import warnings from collections.abc import Callable from pathlib import Path from typing import Any, Literal @@ -57,6 +58,17 @@ @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) class TestReadWrite: + @pytest.fixture(autouse=True) + def _suppress_old_format_warning(self, sdata_container_format: SpatialDataContainerFormatType): + if isinstance(sdata_container_format, CurrentSpatialDataContainerFormat): + yield + else: + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", message="SpatialData is not stored in the most current format", category=UserWarning + ) + yield + def test_images( self, tmp_path: str, @@ -743,6 +755,7 @@ def test_single_scale_image_roundtrip_stays_dataarray(tmp_path: Path) -> None: assert list(image_group.keys()) == ["s0"] +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_self_contained(full_sdata: SpatialData, sdata_container_format: SpatialDataContainerFormatType) -> None: # data only in-memory, so the SpatialData object and all its elements are self-contained @@ -804,6 +817,7 @@ def test_self_contained(full_sdata: SpatialData, sdata_container_format: Spatial assert all(description[element_name] for element_name in description if element_name != "combined") +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_symmetric_difference_with_zarr_store( full_sdata: SpatialData, sdata_container_format: SpatialDataContainerFormatType @@ -846,6 +860,7 @@ def test_symmetric_difference_with_zarr_store( } +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_change_path_of_subset(full_sdata: SpatialData, sdata_container_format: SpatialDataContainerFormatType) -> None: """A subset SpatialData object has not Zarr path associated, show that we can reassign the path""" @@ -917,6 +932,7 @@ def test_incremental_io_valid_name(full_sdata: SpatialData) -> None: _check_valid_name(full_sdata.write_transformations) +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_incremental_io_attrs(points: SpatialData, sdata_container_format: SpatialDataContainerFormatType) -> None: with tempfile.TemporaryDirectory() as tmpdir: @@ -945,6 +961,7 @@ def test_incremental_io_attrs(points: SpatialData, sdata_container_format: Spati cached_sdata_blobs = blobs() +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("element_name", ["image2d", "labels2d", "points_0", "circles", "table"]) @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_delete_element_from_disk( @@ -996,6 +1013,7 @@ def test_delete_element_from_disk( assert element_path not in on_disk +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("element_name", ["image2d", "labels2d", "points_0", "circles", "table"]) @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_element_already_on_disk_different_type( @@ -1127,6 +1145,7 @@ def test_reading_invalid_name(tmp_path: Path): ) +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_write_store_unconsolidated_and_read(full_sdata, sdata_container_format: SpatialDataContainerFormatType): with tempfile.TemporaryDirectory() as tmpdir: @@ -1139,6 +1158,7 @@ def test_write_store_unconsolidated_and_read(full_sdata, sdata_container_format: assert_spatial_data_objects_are_identical(full_sdata, second_read) +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) def test_can_read_sdata_with_reconsolidation(full_sdata, sdata_container_format: SpatialDataContainerFormatType): with tempfile.TemporaryDirectory() as tmpdir: From 81c47a0e616b7b8ba9fe092133fbb1779372f5a7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 15:38:45 +0000 Subject: [PATCH 7/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/io/test_attrs_io.py | 2 +- tests/io/test_partial_read.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/io/test_attrs_io.py b/tests/io/test_attrs_io.py index dfe0fac46..2bd379249 100644 --- a/tests/io/test_attrs_io.py +++ b/tests/io/test_attrs_io.py @@ -10,9 +10,9 @@ from spatialdata import SpatialData, read_zarr from spatialdata._io.format import ( + CurrentSpatialDataContainerFormat, SpatialDataContainerFormats, SpatialDataContainerFormatType, - CurrentSpatialDataContainerFormat, ) FORMAT_V01 = SpatialDataContainerFormats["0.1"] diff --git a/tests/io/test_partial_read.py b/tests/io/test_partial_read.py index 444e8a1c8..8141902e9 100644 --- a/tests/io/test_partial_read.py +++ b/tests/io/test_partial_read.py @@ -473,7 +473,9 @@ def test_read_zarr_with_error(test_case: PartialReadTestCase): ctx = warnings.catch_warnings() if test_case.zarr_version < 3 else nullcontext() with ctx: if test_case.zarr_version < 3: - warnings.filterwarnings("ignore", message="SpatialData is not stored in the most current format", category=UserWarning) + warnings.filterwarnings( + "ignore", message="SpatialData is not stored in the most current format", category=UserWarning + ) if test_case.expected_exceptions: with pytest.raises(test_case.expected_exceptions): read_zarr(test_case.path, on_bad_files="error") @@ -507,7 +509,9 @@ def test_read_zarr_with_warnings(test_case: PartialReadTestCase): ctx = warnings.catch_warnings() if test_case.zarr_version < 3 else nullcontext() with ctx: if test_case.zarr_version < 3: - warnings.filterwarnings("ignore", message="SpatialData is not stored in the most current format", category=UserWarning) + warnings.filterwarnings( + "ignore", message="SpatialData is not stored in the most current format", category=UserWarning + ) with pytest_warns_multiple(UserWarning, matches=test_case.warnings_patterns): actual: SpatialData = read_zarr(test_case.path, on_bad_files="warn") From 110a6ce8c8fd469da845b93d0d6dbd371c225376 Mon Sep 17 00:00:00 2001 From: Luca Marconato Date: Tue, 5 May 2026 17:43:55 +0200 Subject: [PATCH 8/9] refactor: simplify old-format warning suppression using pytest.mark.filterwarnings Replace verbose autouse fixtures (catch_warnings + isinstance check) with a single @pytest.mark.filterwarnings decorator on each class/function. The isinstance guard was unnecessary because V02 tests never emit the warning, so the filter is a harmless no-op for them. Also removes now-unused `import warnings` from test_format.py and test_readwrite.py, and CurrentSpatialDataContainerFormat from test_attrs_io.py. Co-Authored-By: Claude Sonnet 4.6 --- tests/io/test_attrs_io.py | 21 +++------------------ tests/io/test_format.py | 10 +--------- tests/io/test_readwrite.py | 13 +------------ 3 files changed, 5 insertions(+), 39 deletions(-) diff --git a/tests/io/test_attrs_io.py b/tests/io/test_attrs_io.py index dfe0fac46..9830ed4eb 100644 --- a/tests/io/test_attrs_io.py +++ b/tests/io/test_attrs_io.py @@ -3,7 +3,6 @@ from __future__ import annotations import tempfile -import warnings from pathlib import Path import pytest @@ -12,28 +11,17 @@ from spatialdata._io.format import ( SpatialDataContainerFormats, SpatialDataContainerFormatType, - CurrentSpatialDataContainerFormat, ) FORMAT_V01 = SpatialDataContainerFormats["0.1"] FORMAT_V02 = SpatialDataContainerFormats["0.2"] +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", [FORMAT_V01, FORMAT_V02]) class TestAttrsIO: """Test SpatialData.attrs read/write for all container formats.""" - @pytest.fixture(autouse=True) - def _suppress_old_format_warning(self, sdata_container_format: SpatialDataContainerFormatType): - if isinstance(sdata_container_format, CurrentSpatialDataContainerFormat): - yield - else: - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", message="SpatialData is not stored in the most current format", category=UserWarning - ) - yield - def test_attrs_write_and_read( self, sdata_container_format: SpatialDataContainerFormatType, @@ -107,6 +95,7 @@ def test_attrs_incremental_write( assert sdata_read3.attrs["initial_key"] == "initial_value" +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") def test_attrs_v1_to_v2() -> None: """Test that attrs are preserved when converting from V01 to V02.""" with tempfile.TemporaryDirectory() as tmpdir: @@ -124,11 +113,7 @@ def test_attrs_v1_to_v2() -> None: sdata.write(f_v1, sdata_formats=FORMAT_V01) # Read with V01 - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", message="SpatialData is not stored in the most current format", category=UserWarning - ) - sdata_v1 = read_zarr(f_v1) + sdata_v1 = read_zarr(f_v1) assert sdata_v1.attrs == my_attrs # Write with V02 diff --git a/tests/io/test_format.py b/tests/io/test_format.py index 9499bf3eb..f3646ef32 100644 --- a/tests/io/test_format.py +++ b/tests/io/test_format.py @@ -2,7 +2,6 @@ import json import tempfile -import warnings from pathlib import Path from typing import Any @@ -123,17 +122,10 @@ def test_format_raster_v1_v2_v3(self, images, rformat: type[SpatialDataFormatTyp # TODO: add tests for TablesFormatV01 and TablesFormatV02 +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") class TestFormatConversions: """Test format conversions between older formats and newer.""" - @pytest.fixture(autouse=True) - def _suppress_old_format_warning(self): - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", message="SpatialData is not stored in the most current format", category=UserWarning - ) - yield - def test_shapes_v1_to_v2_to_v3(self, shapes): with tempfile.TemporaryDirectory() as tmpdir: f1 = Path(tmpdir) / "data1.zarr" diff --git a/tests/io/test_readwrite.py b/tests/io/test_readwrite.py index 78dfc6207..2e8fb0861 100644 --- a/tests/io/test_readwrite.py +++ b/tests/io/test_readwrite.py @@ -3,7 +3,6 @@ import json import os import tempfile -import warnings from collections.abc import Callable from pathlib import Path from typing import Any, Literal @@ -56,19 +55,9 @@ SDATA_FORMATS = list(SpatialDataContainerFormats.values()) +@pytest.mark.filterwarnings("ignore:SpatialData is not stored in the most current format:UserWarning") @pytest.mark.parametrize("sdata_container_format", SDATA_FORMATS) class TestReadWrite: - @pytest.fixture(autouse=True) - def _suppress_old_format_warning(self, sdata_container_format: SpatialDataContainerFormatType): - if isinstance(sdata_container_format, CurrentSpatialDataContainerFormat): - yield - else: - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", message="SpatialData is not stored in the most current format", category=UserWarning - ) - yield - def test_images( self, tmp_path: str, From 7df090f52c20d3d80d4a861696d9ca551d87d918 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 15:45:58 +0000 Subject: [PATCH 9/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/io/test_attrs_io.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/io/test_attrs_io.py b/tests/io/test_attrs_io.py index 5b4e82adb..9830ed4eb 100644 --- a/tests/io/test_attrs_io.py +++ b/tests/io/test_attrs_io.py @@ -9,7 +9,6 @@ from spatialdata import SpatialData, read_zarr from spatialdata._io.format import ( - CurrentSpatialDataContainerFormat, SpatialDataContainerFormats, SpatialDataContainerFormatType, )