Fix CI and CD: twine metadata, mypy/numpy/python version clash - #15
Merged
Merged
Conversation
Three related failures were making CI and CD red on main and on every
open PR.
CD: the "Distribution build" job failed on `twine check --strict` with
"Invalid distribution metadata: '2.5' is not a valid metadata version".
hatchling emits Metadata-Version 2.5, which only Twine 7 understands.
build-and-inspect-python-package gained Twine 7 in v3.0.0, but v3 also
stopped force-tagging minor releases, so the floating `@v2` tag could
never pick the fix up. Pin to v3.0.1.
CI: the "Format" job failed in mypy while parsing numpy's own stubs:
"Type statement is only supported in Python 3.12 and greater". The
Format job runs on Python 3.14, where numpy resolves to 2.5.x, whose
stubs use PEP 695 `type` statements. mypy was targeting 3.10, so it
refused to parse them. numpy 2.5 itself requires Python >=3.12, so the
target has to be at least 3.12 for its stubs to parse.
Align the stale 3.10 references, which sat below the project's own
`requires-python = ">=3.11"`:
- mypy python_version 3.10 -> 3.12
- test matrix 3.10 -> 3.11, so the minimum supported version is
actually exercised (it previously was not tested at all)
- drop the 3.10 classifier
Raising the mypy target uncovered a real bug that the 3.10 target had
been masking: `reverse_negative_flood_fill` returns a bool array, which
was passed to `beam_shape_erode` (which expects floats) and then handed
to `fits.writeto`, where astropy rejects it. This is the same failure
Beth reported via #14. Cast the flood-fill mask to float32 at the point
it is created, so both the erosion step and the FITS write get a float
array, and drop the now-redundant `type: ignore[assignment]`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTPJweMZavwh86CQ7qH7rb
Target mypy at the project's actual minimum (3.11) rather than 3.12. numpy 2.5 requires Python >=3.12 and its stubs use PEP 695 `type` statements, which mypy refuses to parse while targeting anything below 3.12. Pinning the hook's environment to 3.11 resolves numpy to the 2.4.x line, whose stubs parse cleanly at that target, so the type check now matches `requires-python = ">=3.11"` instead of silently skipping over it. The Format job installs 3.11 alongside 3.x so the hook can find it; 3.x stays last and remains the interpreter pre-commit itself runs on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTPJweMZavwh86CQ7qH7rb
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
AlecThomson
pushed a commit
that referenced
this pull request
Sep 21, 2026
Per review on #14: Revert .pre-commit-config.yaml to the version on main. The copied flint config regressed several things here: it pointed mypy at `files: flint|tests` (the wrong package for this repo, so mypy checked nothing), dropped the `exclude` for the copier answers file, downgraded blacken-docs to black 24, commented out the prettier and pygrep hooks, renamed `ruff-check` to the deprecated `ruff` alias, and dropped the numpy/capn-crunch/pytest-stub dependencies that mypy needs to resolve this package's own types. The underlying numpy / mypy / python version clash is addressed explicitly instead, via the CI fixes merged in from #15: mypy targets 3.11 (the project's real minimum) with the hook pinned to a 3.11 environment, so numpy resolves to the 2.4.x stubs that parse at that target. Annotate CustomFormatter.format, which mypy requires now that it actually checks this package (`disallow_untyped_defs` is on for eye_patch.*). The bool mask fix is now applied at the point the flood-fill mask is created rather than only at `fits.writeto`, which also fixes the type error passing it into `beam_shape_erode`. The `.astype(np.float32)` at the write is kept: it is no longer load-bearing for the bool bug, but `beam_shape_erode` still returns float64 on the multi-scale path, so the cast keeps the mask on disk at float32. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTPJweMZavwh86CQ7qH7rb
The docstring claimed a bool return for no/single scale and int32 otherwise. Neither held: the single-scale path casts to the input dtype, and the multi-scale path returns the float64 array it accumulates into. Now that the mask is float from the point it is created, the bool return is gone entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTPJweMZavwh86CQ7qH7rb
codecov flagged 0% patch coverage, which turned out to be pointing at a real gap rather than a threshold quirk: every existing mask test runs with `flood_fill=False`, so nothing exercised the branch that produced the bool array. That is why this shipped. Covers the path end to end and asserts the written mask is floating point. Verified it fails without the fix, raising the same KeyError from astropy's image HDU while writing the mask that Beth hit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTPJweMZavwh86CQ7qH7rb
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTPJweMZavwh86CQ7qH7rb
codecov was still reporting 0% patch coverage. The remaining uncovered changed line was the `beam_shape_erode` call itself: no test ran `create_snr_mask_from_fits` with `beam_shape_erode=True`, so the one place the bool mask was actually passed into a float-typed parameter, which is what mypy flagged, went unexercised. Adds a fixture whose image carries the beam keywords, and a test running flood fill and beam erosion together through the public entry point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTPJweMZavwh86CQ7qH7rb
The two tests added here were unannotated. mypy did not catch it because disallow_untyped_defs is only switched on for eye_patch.*, not tests. The new fixture takes `tmp_path` rather than `tmpdir`, since the latter is `py.path.local` and the surrounding fixture was converting it to a Path anyway. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTPJweMZavwh86CQ7qH7rb
AlecThomson
pushed a commit
that referenced
this pull request
Sep 22, 2026
main now carries the CI/CD fix (#15), the actions bump (#13) and the pre-commit hook bumps (#8), so this branch needed bringing up to date. The only conflict was in tests/test_masking.py, where this branch held an earlier iteration of the flood-fill tests that landed on main via #15. Resolved in favour of main's version, which is a strict superset: the same test, now annotated, plus the fixture and beam-erosion test added there. Nothing from this branch was lost; the PR never touched tests itself. What remains here is the original contribution: the logging sub-module, the float32 cast at fits.writeto, and the kernel-too-small ValueError. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTPJweMZavwh86CQ7qH7rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the red CI and CD on
main, which was also blocking #8, #13 and #14.main's last CI run was 2026-05-22, so it looked green but was stale. Freshworkflow_dispatchruns onmaintoday confirmed both workflows fail(CI,
CD).
CD —
Distribution buildtwine check --strictfailed with:hatchling emits
Metadata-Version: 2.5, which only Twine 7 understands.build-and-inspect-python-packagepicked up Twine 7 in v3.0.0 — but v3 alsostopped force-tagging minor releases, so the floating
@v2tag we were on couldnever receive the fix. This one was not going to heal on its own.
Pinned to
@v3.0.1.CI —
Formatmypy failed while parsing numpy's own stubs:
The
Formatjob runs on3.x(3.14), where numpy resolves to 2.5.x, whose stubsuse PEP 695
typestatements. mypy was targeting 3.10 and refused to parse them.This is the numpy / mypy / python version clash from #14. The root of it is that
requires-pythonwas raised to>=3.11in e9fba2e ("Add the codes") when thereal code landed, but three leftovers from the scientific-python/cookie template
were never updated with it:
Programming Language :: Python :: 3.10classifier[tool.mypy] python_version = "3.10""3.10"— and 3.11 was never in the matrix at all3.10 is not installable anyway: numpy, astropy, scipy, reproject and capn-crunch
all require
>=3.11.So rather than raising the mypy target to 3.12, this keeps the type check honest
at the project's real minimum:
python_version = "3.11"language_version: python3.11, which resolves numpy tothe 2.4.x line whose stubs parse at that target
Formatjob installs 3.11 alongside3.xso the hook can find it (3.xstays last and remains the interpreter pre-commit itself runs on)
["3.11", "3.12", "3.13", "3.14"], so the minimum supportedversion is actually exercised for the first time
A real bug this uncovered
Type-checking against a supported Python surfaced a genuine bug that the 3.10
target had been masking:
reverse_negative_flood_fillreturns a bool array. It was passed tobeam_shape_erode(which expects floats) and then handed tofits.writeto,where astropy rejects it. That is exactly the failure Beth reported via #14.
Fixed by casting the flood-fill mask to
float32where it is created, so boththe erosion step and the FITS write get a float array, and dropping the
now-redundant
type: ignore[assignment].Because
beam_shape_erodederives its return dtype from its input, fixing thedtype at the source also removes the bool return it could previously produce —
so its return-dtype docstring, which was wrong in both branches it described,
is corrected here too.
This overlaps #14, which applied
.astype(np.float32)at thewritetocall.That cast is kept on #14: it is no longer load-bearing for the bool bug, but the
multi-scale path of
beam_shape_erodestill returns float64, so it is what keepsthe mask on disk at float32.
Test coverage
codecov flagged 0% patch coverage, and it was right twice over — both gaps sat
directly on the path of this bug:
flood_fill=False, so nothing exercised thebranch that built the bool array
create_snr_mask_from_fitswithbeam_shape_erode=True, so theone place the bool mask was passed into a float-typed parameter was never run
Two tests added for those paths. The first was verified to fail without the fix,
raising the same
KeyErrorfrom astropy's image HDU on write that Beth hit.Verification
pre-commit run --hook-stage manual --all-files— all hooks pass locallypytest— 22 passedthe CI failure and the fix, not just the local 3.11 / numpy 2.4.6 one
Once this lands, #13 and #8 need only a branch update to go green.
🤖 Generated with Claude Code
https://claude.ai/code/session_01GTPJweMZavwh86CQ7qH7rb