Skip to content

Migrate supported Python to 3.12, 3.13, 3.14#1151

Open
timtreis wants to merge 8 commits into
mainfrom
chore/python-3.12-3.14
Open

Migrate supported Python to 3.12, 3.13, 3.14#1151
timtreis wants to merge 8 commits into
mainfrom
chore/python-3.12-3.14

Conversation

@timtreis

@timtreis timtreis commented Jun 20, 2026

Copy link
Copy Markdown
Member

Summary

Drops Python 3.11 and adds 3.14 as a supported version. anndata>=0.12 already requires >=3.12, so 3.11 was effectively broken.

Notes

  • Left as-is (already inside the new range): readthedocs pins 3.13, release workflow pins 3.12, Dockerfile uses system python3.
  • ruff pyupgrade now targets py312; a pre-commit run --all-files pass may surface new auto-fixes.
  • Compat to ome_zarr>=0.18 needed two fixes:
    • omero channel metadata: 0.18's writers no longer emit the omero block we passed, so channel names were lost on write/read. spatialdata now writes it itself.
    • integer labels: 0.18 requires integer label-values. Labels{2,3}DModel.parse now rejects float labels (meaningless for masks). Breaking: cast float labels to int, e.g. .astype(np.uint16).

timtreis and others added 4 commits June 20, 2026 11:58
Drop Python 3.11 (anndata>=0.12 already requires >=3.12, so 3.11 was
effectively broken) and add 3.14.

- pyproject.toml: requires-python ">=3.12", ruff target-version py312
- .mypy.ini: python_version 3.12
- test.yaml: matrix 3.12/3.13/3.14; repoint bleeding-edge deps job to
  3.14 and drop the obsolete requires-python sed hack

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The target-version bump to py312 enables ruff UP040. Rewrite the four
explicit TypeAlias declarations to the `type` keyword and drop the now
unused TypeAlias imports. Annotation-only aliases (the repo uses
`from __future__ import annotations`), so no runtime behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The module __getattr__ fell through to `getattr(spatialdata.dataloader,
attr_name)` for any unknown name, re-entering itself indefinitely
(RecursionError) instead of raising AttributeError per PEP 562.

This was latent until the docs build hit it: the PEP 695 `type` aliases
live in private modules, so sphinx-autodoc-typehints probes every
`spatialdata.*` submodule with getattr() looking for a public re-export,
tripping the recursion and failing the RTD build.

Raise AttributeError for unknown names; drop the now-unused
`import spatialdata` and tighten the return type to type[ImageTilesDataset].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ome_zarr 0.18 switched to the NGFF 0.5 layout: channel metadata moved
out of the `omero` block, so overwrite_channel_names() in
_io/_utils.py gets None and crashes (~48 IO test failures). This breaks
main independently of the Python bump. Pin as a stopgap until NGFF 0.5
is supported.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.83333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.43%. Comparing base (af51100) to head (f84be26).

Files with missing lines Patch % Lines
src/spatialdata/models/models.py 91.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1151      +/-   ##
==========================================
- Coverage   92.44%   92.43%   -0.01%     
==========================================
  Files          51       51              
  Lines        7811     7814       +3     
==========================================
+ Hits         7221     7223       +2     
- Misses        590      591       +1     
Files with missing lines Coverage Δ
src/spatialdata/_io/_utils.py 86.66% <100.00%> (ø)
src/spatialdata/_io/io_raster.py 89.04% <100.00%> (-0.21%) ⬇️
src/spatialdata/_types.py 100.00% <100.00%> (ø)
src/spatialdata/dataloader/__init__.py 100.00% <100.00%> (ø)
src/spatialdata/models/_utils.py 89.20% <100.00%> (ø)
src/spatialdata/models/chunks_utils.py 100.00% <100.00%> (ø)
src/spatialdata/models/models.py 87.97% <91.66%> (-0.01%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

timtreis and others added 4 commits June 20, 2026 12:58
ome_zarr 0.18 refactored the functional write_image/write_multiscale
entrypoints (ome/ome-zarr-py#515) to read omero from the top-level
metadata dict; spatialdata passes it nested under metadata["metadata"],
so 0.18 silently dropped it. Effects: write_channel_names() crashed
(omero block absent) and plain write->read lost channel names entirely
(["r","g","b"] came back as [0,1,2]).

Instead of depending on ome-zarr-py to emit omero, write it ourselves:
- _write_raster() now calls overwrite_channel_names() after every image
  write, so the omero block is always present (idempotent on 0.17).
- overwrite_channel_names() defaults to an empty omero block when none
  exists yet.

Pin ome_zarr>=0.18 so CI resolves the same version a fresh install gets
(uv otherwise lands on 0.17, hiding 0.18 regressions). Verified: full
tests/io suite (227) passes on 0.18.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
overwrite_channel_names() now writes the omero channel block on every
image write, so building the same metadata to pass into the ome-zarr-py
writer (which 0.18 ignores anyway) was dead duplication. Remove it along
with the now-unused get_channel_names import, and tighten comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ome_zarr 0.18's label writer auto-parses unique label values and
validates each `label-value` as an integer (via ome-zarr-models), which
rejected spatialdata's float-dtype labels. Float labels are meaningless
for segmentation masks and inconsistent with the rest of the codebase
(fixtures, rasterize, relabel_sequential all assume integers), so the
correct fix is to enforce it: Labels{2,3}DModel.parse now rejects
non-integer/bool data with a clear error.

Tests that fed float data to label models (reusing image-style
generators) now use integers; test_rasterize_bins_invalid casts a parsed
integer label to float to still exercise rasterize_bins' own guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Our omero self-write and integer-label fixes work across 0.16/0.17/0.18
(verified), so keep the wider lower bound for ecosystem co-installability
rather than forcing >=0.18.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Zethson

Zethson commented Jun 21, 2026

Copy link
Copy Markdown
Member

Let's update https://github.com/conda-forge/spatialdata-feedstock/blob/main/recipe/meta.yaml#L3 then as well and fix the broken recipe (doesn't support Python 3.13+) @LucaMarconato

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants