Skip to content

Commit 971d88a

Browse files
committed
Add CI: ruff, pyright, py3.11-3.13 tests w/ coverage gate, wheel smoke test, pip-audit (Linux only)
1 parent 671395d commit 971d88a

1 file changed

Lines changed: 165 additions & 0 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
schedule:
8+
# Weekly (Mon 06:00 UTC) so pip-audit catches newly disclosed CVEs even
9+
# without a code change.
10+
- cron: "0 6 * * 1"
11+
12+
jobs:
13+
lint:
14+
name: Ruff (lint + format)
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v7
18+
19+
- uses: actions/setup-python@v7
20+
with:
21+
python-version: "3.12"
22+
23+
- name: Install ruff
24+
run: |
25+
python -m pip install --upgrade pip
26+
pip install -e ".[dev]"
27+
28+
- name: Ruff lint
29+
run: ruff check .
30+
31+
- name: Ruff format (check only)
32+
run: ruff format --check .
33+
34+
typecheck:
35+
name: Pyright
36+
runs-on: ubuntu-latest
37+
steps:
38+
- uses: actions/checkout@v7
39+
40+
- uses: actions/setup-python@v7
41+
with:
42+
python-version: "3.12"
43+
44+
- name: Install package + pyright
45+
run: |
46+
python -m pip install --upgrade pip
47+
pip install -e ".[dev]"
48+
49+
- name: Pyright
50+
run: pyright
51+
52+
test:
53+
name: Tests (py${{ matrix.python-version }})
54+
runs-on: ubuntu-latest
55+
strategy:
56+
fail-fast: false
57+
matrix:
58+
python-version: ["3.11", "3.12", "3.13"]
59+
steps:
60+
- uses: actions/checkout@v7
61+
62+
- uses: actions/setup-python@v7
63+
with:
64+
python-version: ${{ matrix.python-version }}
65+
66+
- name: Install dependencies
67+
run: |
68+
python -m pip install --upgrade pip
69+
pip install -e ".[dev]"
70+
71+
- name: Run tests with coverage
72+
run: coverage run -m pytest tests -q
73+
74+
# `coverage report` enforces `fail_under` (85% from pyproject.toml).
75+
- name: Coverage report (enforces fail_under)
76+
run: coverage report
77+
78+
- name: Coverage XML
79+
if: always()
80+
run: coverage xml
81+
82+
- name: Upload coverage report
83+
if: always()
84+
uses: actions/upload-artifact@v7
85+
with:
86+
name: coverage-py${{ matrix.python-version }}
87+
path: coverage.xml
88+
if-no-files-found: ignore
89+
90+
package:
91+
name: Build wheel, install & smoke test
92+
runs-on: ubuntu-latest
93+
steps:
94+
- uses: actions/checkout@v7
95+
96+
- uses: actions/setup-python@v7
97+
with:
98+
python-version: "3.12"
99+
100+
- name: Build sdist + wheel
101+
run: |
102+
python -m pip install --upgrade pip build
103+
python -m build
104+
105+
- name: Install the built wheel into a clean venv
106+
run: |
107+
python -m venv /tmp/wheeltest
108+
/tmp/wheeltest/bin/python -m pip install --upgrade pip
109+
/tmp/wheeltest/bin/python -m pip install dist/*.whl
110+
111+
- name: Smoke test the installed wheel
112+
# Run from a neutral directory: if we ran inside the checkout, the
113+
# source `app/` tree would shadow the installed wheel on sys.path
114+
# and we'd be testing the source, not the built artifact.
115+
run: |
116+
set -euxo pipefail
117+
NEUTRAL_DIR=$(mktemp -d)
118+
cd "$NEUTRAL_DIR"
119+
PY=/tmp/wheeltest/bin/python
120+
"$PY" - <<'PY'
121+
import importlib.metadata as md
122+
import app
123+
loc = app.__file__
124+
assert "site-packages" in loc, f"imported source, not wheel: {loc}"
125+
md.version("python-agent-web")
126+
print("wheel import OK:", loc)
127+
PY
128+
129+
- name: Upload distributions
130+
uses: actions/upload-artifact@v7
131+
with:
132+
name: dist
133+
path: dist/*
134+
135+
audit:
136+
name: pip-audit (dependency CVEs)
137+
runs-on: ubuntu-latest
138+
steps:
139+
- uses: actions/checkout@v7
140+
141+
- uses: actions/setup-python@v7
142+
with:
143+
python-version: "3.12"
144+
145+
- name: Install pip-audit
146+
run: |
147+
python -m pip install --upgrade pip
148+
pip install "pip-audit>=2.7"
149+
150+
# Audit the PROJECT's dependency closure resolved from pyproject.toml
151+
# (fastapi, uvicorn, sqlalchemy, ... + transitives) — NOT the whole
152+
# runner environment, which would flag unrelated tooling like
153+
# pip/setuptools. Record accepted/unfixable advisories in
154+
# .pip-audit-known-vulnerabilities.
155+
- name: pip-audit
156+
run: |
157+
IGNORE_VULNS=""
158+
if [ -f .pip-audit-known-vulnerabilities ]; then
159+
while IFS= read -r vuln_id || [ -n "$vuln_id" ]; do
160+
# Skip blank lines and comments
161+
case "$vuln_id" in ''|\#*) continue ;; esac
162+
IGNORE_VULNS="$IGNORE_VULNS --ignore-vuln $vuln_id"
163+
done < .pip-audit-known-vulnerabilities
164+
fi
165+
pip-audit . --strict --progress-spinner=off $IGNORE_VULNS

0 commit comments

Comments
 (0)