Skip to content

Commit ad13eff

Browse files
author
Ryan Winkler
authored
Merge pull request #665 from dannyvfilms/ci/uv-locked-workflows
ci: run checks from locked uv environment
2 parents 40827bf + 73e9c91 commit ad13eff

2 files changed

Lines changed: 124 additions & 82 deletions

File tree

.github/workflows/app-tests.yml

Lines changed: 13 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -51,91 +51,21 @@ jobs:
5151

5252
- name: Checkout repository
5353
uses: actions/checkout@v6
54-
with:
55-
fetch-depth: 0
5654

5755
- name: Set up Python
5856
uses: actions/setup-python@v6
5957
with:
6058
python-version: ${{ matrix.python-version }}
6159

62-
- name: Install Dependencies
63-
run: |
64-
python -m pip install --upgrade pip
65-
pip install -r requirements-dev.txt
66-
playwright install
60+
- name: Set up uv
61+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
62+
with:
63+
version: "0.12.3"
6764

68-
- name: Ruff
69-
env:
70-
BASE_SHA: ${{ github.event.pull_request.base.sha }}
71-
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
72-
IS_PR: ${{ github.event_name == 'pull_request' }}
65+
- name: Install dependencies
7366
run: |
74-
if [ "$IS_PR" != "true" ]; then
75-
echo "Not a pull request - running full check."
76-
ruff check src
77-
exit $?
78-
fi
79-
python - <<'PY'
80-
import json, os, re, subprocess, sys
81-
82-
base, head = os.environ["BASE_SHA"], os.environ["HEAD_SHA"]
83-
84-
# Lines added/modified by this PR, per file.
85-
diff = subprocess.run(
86-
["git", "diff", "--unified=0", f"{base}...{head}", "--", "src"],
87-
capture_output=True, text=True, check=True,
88-
).stdout
89-
90-
changed: dict[str, set[int]] = {}
91-
path = None
92-
hunk = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@")
93-
for line in diff.splitlines():
94-
if line.startswith("+++ b/"):
95-
path = line[6:]
96-
elif line.startswith("@@") and path:
97-
m = hunk.match(line)
98-
if m:
99-
start = int(m.group(1))
100-
count = int(m.group(2) or 1)
101-
changed.setdefault(path, set()).update(
102-
range(start, start + count)
103-
)
104-
105-
changed = {p: lines for p, lines in changed.items() if p.endswith(".py")}
106-
107-
if not changed:
108-
print("No changed Python lines under src/ - nothing to lint.")
109-
sys.exit(0)
110-
111-
proc = subprocess.run(
112-
["ruff", "check", "src", "--output-format=json", "--no-cache"],
113-
capture_output=True, text=True,
114-
)
115-
if proc.returncode not in (0, 1):
116-
sys.stderr.write(proc.stderr)
117-
sys.exit(proc.returncode)
118-
diagnostics = json.loads(proc.stdout or "[]")
119-
120-
repo = os.getcwd()
121-
offending = []
122-
for d in diagnostics:
123-
rel = os.path.relpath(d["filename"], repo)
124-
row = (d.get("location") or {}).get("row")
125-
if row is not None and row in changed.get(rel, ()):
126-
offending.append((rel, row, d["code"], d["message"]))
127-
128-
total = len(diagnostics)
129-
print(f"{total} violations exist in src/; gating on changed lines only.")
130-
131-
if offending:
132-
print(f"\n{len(offending)} violation(s) on lines this PR changed:\n")
133-
for rel, row, code, msg in sorted(offending):
134-
print(f" {rel}:{row}: {code} {msg}")
135-
sys.exit(1)
136-
137-
print("No violations on changed lines.")
138-
PY
67+
uv sync --locked --no-default-groups --group test
68+
uv run --no-sync playwright install
13969
14070
- name: Set environment variables from secrets
14171
run: |
@@ -145,7 +75,7 @@ jobs:
14575
# the only variable settings.py treats as mandatory, so fall back to an
14676
# ephemeral one: nothing signed during a test run outlives the job.
14777
if [ -z "${{ secrets.SECRET }}" ]; then
148-
echo "SECRET=$(python -c 'import secrets; print(secrets.token_urlsafe(50))')" >> $GITHUB_ENV
78+
echo "SECRET=$(uv run --no-sync python -c 'import secrets; print(secrets.token_urlsafe(50))')" >> $GITHUB_ENV
14979
fi
15080
if [ -n "${{ secrets.TMDB_API }}" ]; then echo "TMDB_API=${{ secrets.TMDB_API }}" >> $GITHUB_ENV; fi
15181
if [ -n "${{ secrets.MAL_API }}" ]; then echo "MAL_API=${{ secrets.MAL_API }}" >> $GITHUB_ENV; fi
@@ -160,14 +90,15 @@ jobs:
16090
# flaky, and a pull request from a fork cannot read repository secrets,
16191
# so they fail there regardless of the change. Run them deliberately
16292
# with scripts/test.sh --network.
163-
coverage run src/manage.py test app users integrations lists events \
93+
uv run --no-sync coverage run src/manage.py test \
94+
app users integrations lists events \
16495
--parallel --exclude-tag network
16596
16697
- name: Build Coverage Report
16798
run: |
168-
coverage combine
169-
coverage report
170-
coverage xml
99+
uv run --no-sync coverage combine
100+
uv run --no-sync coverage report
101+
uv run --no-sync coverage xml
171102
172103
- name: Upload coverage to Codecov
173104
uses: codecov/codecov-action@v6

.github/workflows/lint.yml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
name: Lint
2+
3+
on:
4+
pull_request:
5+
paths-ignore:
6+
- ".github/workflows/**"
7+
push:
8+
branches:
9+
- latest
10+
- release
11+
workflow_dispatch:
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
lint:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@v6
22+
with:
23+
fetch-depth: 0
24+
25+
- name: Set up Python
26+
uses: actions/setup-python@v6
27+
with:
28+
python-version: "3.12"
29+
30+
- name: Set up uv
31+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
32+
with:
33+
version: "0.12.3"
34+
35+
- name: Check dependency lock
36+
run: uv lock --check
37+
38+
- name: Install lint dependencies
39+
run: uv sync --locked --only-group lint
40+
41+
- name: Ruff
42+
env:
43+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
44+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
45+
IS_PR: ${{ github.event_name == 'pull_request' }}
46+
run: |
47+
if [ "$IS_PR" != "true" ]; then
48+
echo "Not a pull request - running full check."
49+
uv run --no-sync ruff check src
50+
exit $?
51+
fi
52+
uv run --no-sync python - <<'PY'
53+
import json, os, re, subprocess, sys
54+
55+
base, head = os.environ["BASE_SHA"], os.environ["HEAD_SHA"]
56+
57+
# Lines added/modified by this PR, per file.
58+
diff = subprocess.run(
59+
["git", "diff", "--unified=0", f"{base}...{head}", "--", "src"],
60+
capture_output=True, text=True, check=True,
61+
).stdout
62+
63+
changed: dict[str, set[int]] = {}
64+
path = None
65+
hunk = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@")
66+
for line in diff.splitlines():
67+
if line.startswith("+++ b/"):
68+
path = line[6:]
69+
elif line.startswith("@@") and path:
70+
m = hunk.match(line)
71+
if m:
72+
start = int(m.group(1))
73+
count = int(m.group(2) or 1)
74+
changed.setdefault(path, set()).update(
75+
range(start, start + count)
76+
)
77+
78+
changed = {p: lines for p, lines in changed.items() if p.endswith(".py")}
79+
80+
if not changed:
81+
print("No changed Python lines under src/ - nothing to lint.")
82+
sys.exit(0)
83+
84+
proc = subprocess.run(
85+
["ruff", "check", "src", "--output-format=json", "--no-cache"],
86+
capture_output=True, text=True,
87+
)
88+
if proc.returncode not in (0, 1):
89+
sys.stderr.write(proc.stderr)
90+
sys.exit(proc.returncode)
91+
diagnostics = json.loads(proc.stdout or "[]")
92+
93+
repo = os.getcwd()
94+
offending = []
95+
for d in diagnostics:
96+
rel = os.path.relpath(d["filename"], repo)
97+
row = (d.get("location") or {}).get("row")
98+
if row is not None and row in changed.get(rel, ()):
99+
offending.append((rel, row, d["code"], d["message"]))
100+
101+
total = len(diagnostics)
102+
print(f"{total} violations exist in src/; gating on changed lines only.")
103+
104+
if offending:
105+
print(f"\n{len(offending)} violation(s) on lines this PR changed:\n")
106+
for rel, row, code, msg in sorted(offending):
107+
print(f" {rel}:{row}: {code} {msg}")
108+
sys.exit(1)
109+
110+
print("No violations on changed lines.")
111+
PY

0 commit comments

Comments
 (0)