From f3d487bc09de3ffa5c56319fd62b02230827f456 Mon Sep 17 00:00:00 2001 From: Alexey Shalaev <75322386+AlexeyShalaev@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:41:34 +0400 Subject: [PATCH] fix: cap ruff and mypy at the series the generated config was checked with The dev group gave ruff and mypy floors only, and a render ships no uv.lock, so a new project linted with whatever was newest on the day it was rendered. A ruff minor promotes preview rules into the families the config selects, so make check could turn red on an unchanged project, and the template's own CI tested a different toolchain every day. ruff and mypy are now capped at the minor series a render resolves today and passes its gate with (0.17 and 2.4). pre-commit keeps its floor only: it decides no rule. The template CI pins copier and jinja2-time, so a CI result can be reproduced later. Dependabot cannot read pyproject.toml.jinja, so a weekly Tool bounds workflow renders the template with the caps lifted, runs the generated gate on what that resolves, and fails when a newer series is out. Closes #17 --- .github/workflows/ci.yml | 2 +- .github/workflows/tool-bounds.yml | 86 +++++++++++++++++++++++++++++++ CONTRIBUTING.md | 2 +- template/pyproject.toml.jinja | 8 ++- 4 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/tool-bounds.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8748ac..75e93bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - name: Render with copier run: | - uvx --with jinja2-time copier copy --defaults --trust --vcs-ref HEAD \ + uvx --from copier==9.19.1 --with jinja2-time==0.2.0 copier copy --defaults --trust --vcs-ref HEAD \ --data project_name=demo-lib \ --data project_slug=demo-lib \ --data package_name=demo_lib \ diff --git a/.github/workflows/tool-bounds.yml b/.github/workflows/tool-bounds.yml new file mode 100644 index 0000000..997c0af --- /dev/null +++ b/.github/workflows/tool-bounds.yml @@ -0,0 +1,86 @@ +name: Tool bounds + +# The generated dev group caps ruff and mypy at the minor series the generated +# config was checked with. Dependabot cannot read pyproject.toml.jinja, so this +# is what says a cap has fallen behind: it renders the template with the caps +# lifted, runs the generated gate on whatever that resolves, and fails when a +# newer series is out. The gate result in the log says whether raising the cap +# is a one-line change or needs work in the config first. +# +# Its own workflow, not a job in CI: CI's gate requires every job to succeed, +# and a job that runs on a schedule only would be skipped on every pull request. +on: + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: + +permissions: + contents: read + +jobs: + newest: + name: Caps against the newest ruff and mypy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v7 + + - name: Render with copier + run: | + uvx --from copier==9.19.1 --with jinja2-time==0.2.0 copier copy --defaults --trust --vcs-ref HEAD \ + --data project_name=demo-lib \ + --data project_slug=demo-lib \ + --data package_name=demo_lib \ + --data project_description="Demo library rendered from the template" \ + --data author_name="Bedrock Python" \ + --data author_email="maintainers@example.com" \ + . "${RUNNER_TEMP}/demo-lib" + + - name: Lift the caps + working-directory: ${{ runner.temp }}/demo-lib + run: | + cp pyproject.toml "${RUNNER_TEMP}/capped.toml" + sed -i -E 's/"(ruff|mypy)>=([^,"]+),<[^"]+"/"\1>=\2"/' pyproject.toml + uv sync --group dev --all-extras + + - name: The generated gate on the newest versions + id: gate + continue-on-error: true + working-directory: ${{ runner.temp }}/demo-lib + run: | + make check + make test-unit + + - name: The caps admit the newest versions + working-directory: ${{ runner.temp }}/demo-lib + env: + GATE: ${{ steps.gate.outcome }} + run: | + uv run --with packaging python - "${RUNNER_TEMP}/capped.toml" <<'PY' + import os + import sys + import tomllib + from importlib.metadata import version + + from packaging.requirements import Requirement + + with open(sys.argv[1], "rb") as capped: + dev = tomllib.load(capped)["dependency-groups"]["dev"] + stale = [] + for entry in dev: + if isinstance(entry, str): + req = Requirement(entry) + if req.name in ("ruff", "mypy"): + newest = version(req.name) + print(f"{req.name}: newest {newest}, template allows {req.specifier}") + if newest not in req.specifier: + stale.append(f"{req.name} {newest} is out, the template allows {req.specifier}") + for line in stale: + print(f"::error::{line}") + passed = os.environ["GATE"] == "success" + if stale: + print(f"The generated gate {'passes' if passed else 'FAILS'} with the caps lifted.") + elif not passed: + print("::error::The generated gate fails on the versions the caps admit.") + sys.exit(1 if stale or not passed else 0) + PY diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fce10a8..69591f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ Render the template and run the generated project's own gate — that is exactly does: ```bash -uvx --with jinja2-time copier copy --defaults --trust --vcs-ref HEAD \ +uvx --from copier==9.19.1 --with jinja2-time==0.2.0 copier copy --defaults --trust --vcs-ref HEAD \ --data project_name=demo-lib --data project_slug=demo-lib --data package_name=demo_lib \ --data project_description="Demo" --data author_name="You" --data author_email="you@example.com" \ . /tmp/demo-lib diff --git a/template/pyproject.toml.jinja b/template/pyproject.toml.jinja index 9c60bdf..6e7be8c 100644 --- a/template/pyproject.toml.jinja +++ b/template/pyproject.toml.jinja @@ -38,9 +38,13 @@ docs = [ "zensical>=0.0.37", "mkdocstrings-python>=2.0.3", ] +# ruff and mypy decide whether `make check` is green, so they stay inside the +# minor series this config was checked with: a ruff minor promotes rules into +# the families `select` asks for. Moving to the next series is a change of its +# own, with CI on it. dev = [ - "ruff>=0.15.1", - "mypy>=1.19.1", + "ruff>=0.17.1,<0.18", + "mypy>=2.4.0,<2.5", "pre-commit>=4.5.1", { include-group = "test" }, ]