ci: resolve the declared dependency floors on every PR - #188
Merged
Merged
Conversation
Every existing leg resolves highest, so the declared lower bounds ship untested. That is how 0.13.2 went out with `faststream>=0.7.1` and stayed green until 0.7.6 landed, made `address` a required field of `SubscriberSpec` and `PublisherSpec`, and broke AsyncAPI generation for anyone who resolved fresh. The new `lowest` leg resolves `--resolution lowest-direct` and runs the suite against it, one interpreter per distinct floor set. The dev harness needed floors of its own first: an unbounded `pytest` there resolves to 2.0.0, which does not build.
Benchmark gate✅ gate passed
Gated (fails the build): |
This was referenced Sep 20, 2026
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.
Why
Every CI leg resolves highest.
pytestandfreethreadedrunuv sync, andscheduled-dep-checkre-runs the same checks daily, so the upper bound is wellcovered: a new upstream release turns main red within a day. Nothing installs
the declared lower bounds, so they ship untested.
That is not hypothetical. 0.13.2 declared
faststream>=0.7.1,<0.8and was greenthe whole time. faststream 0.7.6 then made
addressa required field ofSubscriberSpecandPublisherSpec; a fresh resolve of an unchanged pin pickedit up and AsyncAPI generation started raising
TypeError. It was found in arunning service, not in CI. #182 noted the cause at the time: "Nothing in CI
resolves at the lower bound, which is why the previous floor went stale
unnoticed." #185, #186 and #187 then corrected six floors by hand in one day,
which is what an untested constraint looks like.
What
A
lowestjob in_checks.ymlthat resolves--resolution lowest-directandruns the suite against it, on the same triggers as the rest (every PR, every
push to main, and the daily schedule).
One interpreter per distinct floor set, since the markers define exactly three:
3.12 resolves the same set as 3.11 and would add nothing.
The dev group needed floors before this could work:
uv sync --resolution lowest-directreads an unboundedpytestaspytest==2.0.0, whose sdist doesnot build on a modern interpreter.
pytest>=8,pytest-asyncio>=0.24andpytest-cov>=5are the versions this suite actually needs (asyncio_modeandasyncio_default_fixture_loop_scopeare both 0.24-era options).Verified
Ran the exact step sequence locally on all three legs before opening this. Each
resolves to the floors in the table and passes:
The 107 skips are the Postgres-backed tests, which skip without a database; the
job runs the same
postgres:17service the other legs do, so they execute in CI.Two judgment calls worth a look
uv pip install httpxas a step. fastapi's floor (0.113) pulls starlette0.38.6, whose
TestClientpredates httpx2 support, sotests/test_fastapi.pycannot even collect without httpx. No test imports httpx directly; it is purely
starlette's dependency. Adding it to the dev group instead would install a second
HTTP client in every other leg to serve this one, so it is a step here. The
alternative is raising the fastapi floor to the first release whose starlette
takes httpx2, which trades a CI line for a narrower supported range. Happy to
switch if you prefer that.
3.14 is noisy. pytest-asyncio 0.24 calls
asyncio.iscoroutinefunction,deprecated in 3.14, so that leg emits ~33k warnings. It passes. A marked
pytest-asyncio>=1.0; python_version >= '3.14'would silence it at the cost ofno longer testing the declared floor there, so I left it.
Not covered
This gates the floors from here on. It does not help anyone already pinned to
faststream-outbox~=0.13.2, whose range still resolves the broken pair; that wasconsidered and declined separately.