Skip to content

Commit 00ac431

Browse files
fix: cap ruff and mypy at the series the generated config was checked with (#20)
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
1 parent 8bf4563 commit 00ac431

4 files changed

Lines changed: 94 additions & 4 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121

2222
- name: Render with copier
2323
run: |
24-
uvx --with jinja2-time copier copy --defaults --trust --vcs-ref HEAD \
24+
uvx --from copier==9.19.1 --with jinja2-time==0.2.0 copier copy --defaults --trust --vcs-ref HEAD \
2525
--data project_name=demo-lib \
2626
--data project_slug=demo-lib \
2727
--data package_name=demo_lib \

‎.github/workflows/tool-bounds.yml‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: Tool bounds
2+
3+
# The generated dev group caps ruff and mypy at the minor series the generated
4+
# config was checked with. Dependabot cannot read pyproject.toml.jinja, so this
5+
# is what says a cap has fallen behind: it renders the template with the caps
6+
# lifted, runs the generated gate on whatever that resolves, and fails when a
7+
# newer series is out. The gate result in the log says whether raising the cap
8+
# is a one-line change or needs work in the config first.
9+
#
10+
# Its own workflow, not a job in CI: CI's gate requires every job to succeed,
11+
# and a job that runs on a schedule only would be skipped on every pull request.
12+
on:
13+
schedule:
14+
- cron: "0 6 * * 1"
15+
workflow_dispatch:
16+
17+
permissions:
18+
contents: read
19+
20+
jobs:
21+
newest:
22+
name: Caps against the newest ruff and mypy
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v7
26+
- uses: astral-sh/setup-uv@v7
27+
28+
- name: Render with copier
29+
run: |
30+
uvx --from copier==9.19.1 --with jinja2-time==0.2.0 copier copy --defaults --trust --vcs-ref HEAD \
31+
--data project_name=demo-lib \
32+
--data project_slug=demo-lib \
33+
--data package_name=demo_lib \
34+
--data project_description="Demo library rendered from the template" \
35+
--data author_name="Bedrock Python" \
36+
--data author_email="maintainers@example.com" \
37+
. "${RUNNER_TEMP}/demo-lib"
38+
39+
- name: Lift the caps
40+
working-directory: ${{ runner.temp }}/demo-lib
41+
run: |
42+
cp pyproject.toml "${RUNNER_TEMP}/capped.toml"
43+
sed -i -E 's/"(ruff|mypy)>=([^,"]+),<[^"]+"/"\1>=\2"/' pyproject.toml
44+
uv sync --group dev --all-extras
45+
46+
- name: The generated gate on the newest versions
47+
id: gate
48+
continue-on-error: true
49+
working-directory: ${{ runner.temp }}/demo-lib
50+
run: |
51+
make check
52+
make test-unit
53+
54+
- name: The caps admit the newest versions
55+
working-directory: ${{ runner.temp }}/demo-lib
56+
env:
57+
GATE: ${{ steps.gate.outcome }}
58+
run: |
59+
uv run --with packaging python - "${RUNNER_TEMP}/capped.toml" <<'PY'
60+
import os
61+
import sys
62+
import tomllib
63+
from importlib.metadata import version
64+
65+
from packaging.requirements import Requirement
66+
67+
with open(sys.argv[1], "rb") as capped:
68+
dev = tomllib.load(capped)["dependency-groups"]["dev"]
69+
stale = []
70+
for entry in dev:
71+
if isinstance(entry, str):
72+
req = Requirement(entry)
73+
if req.name in ("ruff", "mypy"):
74+
newest = version(req.name)
75+
print(f"{req.name}: newest {newest}, template allows {req.specifier}")
76+
if newest not in req.specifier:
77+
stale.append(f"{req.name} {newest} is out, the template allows {req.specifier}")
78+
for line in stale:
79+
print(f"::error::{line}")
80+
passed = os.environ["GATE"] == "success"
81+
if stale:
82+
print(f"The generated gate {'passes' if passed else 'FAILS'} with the caps lifted.")
83+
elif not passed:
84+
print("::error::The generated gate fails on the versions the caps admit.")
85+
sys.exit(1 if stale or not passed else 0)
86+
PY

‎CONTRIBUTING.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Render the template and run the generated project's own gate — that is exactly
2020
does:
2121

2222
```bash
23-
uvx --with jinja2-time copier copy --defaults --trust --vcs-ref HEAD \
23+
uvx --from copier==9.19.1 --with jinja2-time==0.2.0 copier copy --defaults --trust --vcs-ref HEAD \
2424
--data project_name=demo-lib --data project_slug=demo-lib --data package_name=demo_lib \
2525
--data project_description="Demo" --data author_name="You" --data author_email="you@example.com" \
2626
. /tmp/demo-lib

‎template/pyproject.toml.jinja‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,13 @@ docs = [
3838
"zensical>=0.0.37",
3939
"mkdocstrings-python>=2.0.3",
4040
]
41+
# ruff and mypy decide whether `make check` is green, so they stay inside the
42+
# minor series this config was checked with: a ruff minor promotes rules into
43+
# the families `select` asks for. Moving to the next series is a change of its
44+
# own, with CI on it.
4145
dev = [
42-
"ruff>=0.15.1",
43-
"mypy>=1.19.1",
46+
"ruff>=0.17.1,<0.18",
47+
"mypy>=2.4.0,<2.5",
4448
"pre-commit>=4.5.1",
4549
{ include-group = "test" },
4650
]

0 commit comments

Comments
 (0)