From b4004bb629a6be6de75d3fb07fcc663a3a0c3656 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 14 Aug 2026 12:11:35 +0800 Subject: [PATCH] Test across Python 3.9-3.14 in CI CI ran a single Python 3.11 job while pyproject declares `requires-python = ">=3.9"`, so five of the six supported versions were never exercised. Add a matrix over the full declared range, with fail-fast disabled so one version failing does not hide the others. The matrix immediately caught a real failure. Nothing is pinned -- no lockfile is committed -- so each Python resolves its own dependency set, and 3.12+ picks up numpy 2.5, whose stubs give np.isnan a concrete ndarray return type. Assigning None to that variable afterwards is an error mypy only sees on those versions; 3.9 through 3.11 resolve to numpy <= 2.4 and pass. Annotate nan_mask as Optional[np.ndarray]. This is a typing-only change with no runtime effect, and the suite passes on all six versions either way. Also add, matching OmniWatermask: - `fetch-depth: 0`, so setuptools-scm can read tags. Without it CI was installing version 0.1.dev1+g rather than the real version -- the same silent-version hazard the conda-forge recipe guards against. - A weekly schedule. With nothing pinned, upstream releases reach users through a fresh resolve; this surfaces them on our schedule instead of theirs. OpenCV 5.0.0 already satisfies our `>=4.10` floor and is what every version above resolves to today. - `workflow_dispatch`, and a per-version uv cache suffix. Verified locally across all six: lint, mypy and 573 tests pass on each. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 19 ++++++++++++++++++- multiclean/multiclean.py | 3 +++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0cc6345..f29b4cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,18 +3,35 @@ name: CI on: push: pull_request: + schedule: + # Sundays 00:00 UTC. No lockfile is committed, so uv resolves fresh each + # run and this catches upstream releases that break us before users hit them. + - cron: "0 0 * * 0" + workflow_dispatch: jobs: check: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # The full range `requires-python = ">=3.9"` claims. Resolving fresh on + # each means the older entries also pin older numpy/OpenCV, which is + # where the interesting differences turn up — 3.12+ picks up numpy 2.5, + # whose stricter stubs the older versions never exercise. + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v6 + with: + fetch-depth: 0 # full history so setuptools-scm can read tags - uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version: ${{ matrix.python-version }} - uses: astral-sh/setup-uv@v8.0.0 + with: + cache-suffix: ${{ matrix.python-version }} - run: uv sync --all-extras --dev diff --git a/multiclean/multiclean.py b/multiclean/multiclean.py index d334746..36cfa90 100644 --- a/multiclean/multiclean.py +++ b/multiclean/multiclean.py @@ -73,6 +73,9 @@ def clean_array( background_class_values = list(set(all_class_values) - set(target_class_values)) + # Annotated because the None branches below are otherwise incompatible with + # the concrete ndarray type numpy>=2.5's stubs infer for np.isnan. + nan_mask: Optional[np.ndarray] if is_float and not fill_nan: nan_mask = np.isnan(array) if not nan_mask.any():