Opened by Claude Code on my behalf and at my request while investigating #19044.
Description
Follow-up to #19044, which adds a [tool.mypy] allowlist and a CI job that type checks the modules listed in it.
The new environment declares its tool dependency with no version constraint:
[testenv:mypy]
deps =
mypy
This is not a new habit introduced by that PR — [testenv:stubtest] on main already does the same thing, and stubtest ships as part of mypy, so both environments float together. But #19044 makes the consequence sharper, because the entire premise of the allowlist is "the modules on this list are green, and stay green." That signal is only worth having if a red job implies an astropy change. With an unpinned checker, a mypy release can redden the job on a PR that touches nothing related, and the failure lands on whichever contributor happens to open a PR that morning.
Type checkers are unusually prone to this compared to most test dependencies: new releases routinely add error codes, tighten inference, and change how existing constructs are analysed. Code that is legitimately correct and passed yesterday can be reported today, by design.
This is not hypothetical
mypy's recent release cadence, from PyPI:
| Version |
Released |
| 1.19.0 |
2025-11-28 |
| 1.20.0 |
2026-03-31 |
| 2.0.0 |
2026-05-06 |
| 2.1.0 |
2026-05-11 |
| 2.2.0 |
2026-07-08 |
| 2.3.0 |
2026-07-13 |
| 2.3.1 |
2026-08-15 |
Twelve releases in roughly twelve months, including a major version bump.
The commits in #19044 are themselves an illustration: they were authored 2025-12-10, when mypy 1.19 was current, and rebased 2026-09-17, by which point 2.3.1 was. The PR sat across a 1.x → 2.x transition. In this instance the allowlist happens to still pass under 2.3.1 — I verified that locally — but that was luck rather than a property the configuration guarantees.
Proposal
At minimum, give both environments a lower bound, so the configuration records which mypy version the allowlist was actually validated against:
[testenv:mypy]
deps =
mypy >=2.3
I want to be upfront that a lower bound alone does not solve the problem described above — it prevents older mypy from being used, while the failure mode here is a newer one. Genuinely decoupling the job from upstream releases needs one of:
I lean towards treating this as a special case of #17596 rather than inventing a separate mechanism for it, and would defer to @neutrinoceros on that. Flagging it here so the decision is made deliberately rather than inherited by default.
Additional context
Whatever is decided should be applied to [testenv:stubtest] at the same time. #19044 also switches that environment from .stubtest.ini to reading [tool.mypy] in pyproject.toml, so after it merges the two jobs share both a config file and an unpinned checker.
See also #20443, on the third-party stub packages that the same environment is missing. That issue mentions this pinning question in passing; this one is the dedicated discussion.
Found while surveying astropy.cosmology for allowlist candidates in #19044.
Opened by Claude Code on my behalf and at my request while investigating #19044.
Description
Follow-up to #19044, which adds a
[tool.mypy]allowlist and a CI job that type checks the modules listed in it.The new environment declares its tool dependency with no version constraint:
This is not a new habit introduced by that PR —
[testenv:stubtest]onmainalready does the same thing, andstubtestships as part of mypy, so both environments float together. But #19044 makes the consequence sharper, because the entire premise of the allowlist is "the modules on this list are green, and stay green." That signal is only worth having if a red job implies an astropy change. With an unpinned checker, a mypy release can redden the job on a PR that touches nothing related, and the failure lands on whichever contributor happens to open a PR that morning.Type checkers are unusually prone to this compared to most test dependencies: new releases routinely add error codes, tighten inference, and change how existing constructs are analysed. Code that is legitimately correct and passed yesterday can be reported today, by design.
This is not hypothetical
mypy's recent release cadence, from PyPI:
Twelve releases in roughly twelve months, including a major version bump.
The commits in #19044 are themselves an illustration: they were authored 2025-12-10, when mypy 1.19 was current, and rebased 2026-09-17, by which point 2.3.1 was. The PR sat across a
1.x→2.xtransition. In this instance the allowlist happens to still pass under 2.3.1 — I verified that locally — but that was luck rather than a property the configuration guarantees.Proposal
At minimum, give both environments a lower bound, so the configuration records which mypy version the allowlist was actually validated against:
I want to be upfront that a lower bound alone does not solve the problem described above — it prevents older mypy from being used, while the failure mode here is a newer one. Genuinely decoupling the job from upstream releases needs one of:
mypy >=2.3,<2.4), with a periodic bump. Cheap, but someone has to do the bumping, and the bump PR is where the new errors surface — which is arguably the right place for them.I lean towards treating this as a special case of #17596 rather than inventing a separate mechanism for it, and would defer to @neutrinoceros on that. Flagging it here so the decision is made deliberately rather than inherited by default.
Additional context
Whatever is decided should be applied to
[testenv:stubtest]at the same time. #19044 also switches that environment from.stubtest.inito reading[tool.mypy]inpyproject.toml, so after it merges the two jobs share both a config file and an unpinned checker.See also #20443, on the third-party stub packages that the same environment is missing. That issue mentions this pinning question in passing; this one is the dedicated discussion.
Found while surveying
astropy.cosmologyfor allowlist candidates in #19044.