From 737032161b421aef41b01c57ee3998f35aad29e2 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sun, 20 Sep 2026 16:52:28 +0300 Subject: [PATCH] ci: resolve the declared dependency floors on every PR 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. --- .github/workflows/_checks.yml | 50 +++++++++++++++++++++++++++++++++++ pyproject.toml | 8 +++--- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/.github/workflows/_checks.yml b/.github/workflows/_checks.yml index d730194..60021cc 100644 --- a/.github/workflows/_checks.yml +++ b/.github/workflows/_checks.yml @@ -84,6 +84,56 @@ jobs: PYTHONUNBUFFERED: 1 POSTGRES_DSN: postgresql+asyncpg://outbox:outbox@127.0.0.1:5432/outbox + lowest: + # The declared floors are part of what we ship, and nothing else in CI installs + # them: `pytest` and `scheduled-dep-check` both resolve highest. That is how + # 0.13.2 shipped `faststream>=0.7.1` and stayed green until 0.7.6 broke a user. + # This leg resolves the lower bound instead. One interpreter per distinct floor + # set: 3.11 for the unmarked floors, 3.13 and 3.14 for the marked ones. + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.11" + - "3.13" + - "3.14" + services: + postgres: + image: postgres:17 + env: + POSTGRES_USER: outbox + POSTGRES_PASSWORD: outbox + POSTGRES_DB: outbox + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U outbox" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/setup-uv@v8.2.0 + with: + enable-cache: true + cache-dependency-glob: "**/pyproject.toml" + - run: uv python install ${{ matrix.python-version }} + - run: uv python pin ${{ matrix.python-version }} + - run: uv sync --all-extras --no-install-project --resolution lowest-direct + # fastapi's floor pulls a starlette whose TestClient predates httpx2 support. + # Installed here rather than declared in the dev group: at the resolved-highest + # end starlette takes the httpx2 that group already declares, and adding httpx + # there would pin a second client into every other leg to serve this one. + - run: uv pip install httpx + # --no-cov: the coverage gate rides the resolved-highest leg in `test-ci`. This + # one asks whether the floors work, which a coverage failure would only obscure. + - run: uv run --no-sync pytest . --no-cov + env: + PYTHONDONTWRITEBYTECODE: 1 + PYTHONUNBUFFERED: 1 + POSTGRES_DSN: postgresql+asyncpg://outbox:outbox@127.0.0.1:5432/outbox + freethreaded: runs-on: ubuntu-latest env: diff --git a/pyproject.toml b/pyproject.toml index 064d9c0..e76a658 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,9 +62,11 @@ all = ["faststream-outbox[asyncpg,validate,fastapi,prometheus,opentelemetry]"] [dependency-groups] dev = [ - "pytest", - "pytest-asyncio", - "pytest-cov", + # Floored so `uv sync --resolution lowest-direct` resolves a usable harness: an + # unbounded name there resolves to the project's first-ever release. + "pytest>=8", + "pytest-asyncio>=0.24", + "pytest-cov>=5", "asyncpg>=0.29; python_version < '3.13'", "asyncpg>=0.30; python_version == '3.13'", "asyncpg>=0.31; python_version >= '3.14'",