Skip to content

Commit d38ee43

Browse files
add nox and test against different Python and Django versions (#91)
1 parent 074c10b commit d38ee43

File tree

5 files changed

+204
-15
lines changed

5 files changed

+204
-15
lines changed

.github/workflows/lint.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ on:
66
branches: [main]
77
workflow_call:
88

9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.head_ref }}
11+
cancel-in-progress: true
12+
913
permissions:
1014
contents: read
1115

1216
env:
1317
CARGO_TERM_COLOR: always
1418
FORCE_COLOR: "1"
1519
PYTHONUNBUFFERED: "1"
16-
UV_VERSION: "0.4.x"
1720

1821
jobs:
1922
pre-commit:
@@ -25,7 +28,7 @@ jobs:
2528
uses: astral-sh/setup-uv@v5
2629
with:
2730
enable-cache: true
28-
version: ${{ env.UV_VERSION }}
31+
pyproject-file: pyproject.toml
2932

3033
- uses: actions/cache@v4
3134
with:

.github/workflows/release.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ on:
1212
tags:
1313
- "*"
1414
pull_request:
15+
branches:
16+
- "release/*"
1517
workflow_dispatch:
1618

19+
concurrency:
20+
group: test-${{ github.head_ref }}
21+
cancel-in-progress: true
22+
1723
permissions:
1824
contents: read
1925

.github/workflows/test.yml

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ on:
55
push:
66
branches: [main]
77
workflow_call:
8+
inputs:
9+
os:
10+
description: "Comma-delineated list of OS targets to run tests on"
11+
required: false
12+
type: string
813

914
concurrency:
10-
group: test-${{ github.head_ref }}
15+
group: ${{ github.workflow }}-${{ github.head_ref }}
1116
cancel-in-progress: true
1217

1318
env:
@@ -16,15 +21,30 @@ env:
1621
PYTHONUNBUFFERED: "1"
1722

1823
jobs:
24+
generate-matrix:
25+
runs-on: ubuntu-latest
26+
outputs:
27+
matrix: ${{ steps.set-matrix.outputs.matrix }}
28+
steps:
29+
- uses: actions/checkout@v4
30+
31+
- name: Install uv
32+
uses: astral-sh/setup-uv@v5
33+
with:
34+
enable-cache: true
35+
pyproject-file: pyproject.toml
36+
37+
- id: set-matrix
38+
run: |
39+
uv run noxfile.py --session gha_matrix -- "${{ inputs.os }}"
40+
1941
test:
42+
name: Python ${{ matrix.python-version }}, Django ${{ matrix.django-version }} (${{ matrix.os }})
2043
runs-on: ${{ matrix.os }}
44+
needs: generate-matrix
2145
strategy:
2246
fail-fast: false
23-
matrix:
24-
os:
25-
- macos-latest
26-
- ubuntu-latest
27-
- windows-latest
47+
matrix: ${{ fromJSON(needs.generate-matrix.outputs.matrix) }}
2848
steps:
2949
- uses: actions/checkout@v4
3050

@@ -34,10 +54,6 @@ jobs:
3454
enable-cache: true
3555
pyproject-file: pyproject.toml
3656

37-
- name: Install dependencies and build
38-
run: |
39-
uv sync --frozen
40-
uv run maturin build
41-
4257
- name: Run tests
43-
run: cargo test --verbose
58+
run: |
59+
uv run noxfile.py --session "tests(python='${{ matrix.python-version }}', django='${{ matrix.django-version }}')"

Justfile

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ mod docs ".just/docs.just"
88
default:
99
@just --list
1010

11+
[private]
12+
nox SESSION *ARGS:
13+
uv run noxfile.py --session "{{ SESSION }}" -- "{{ ARGS }}"
14+
1115
bumpver *ARGS:
1216
uv run --with bumpver bumpver {{ ARGS }}
1317

@@ -17,4 +21,10 @@ clean:
1721
# run pre-commit on all files
1822
lint:
1923
@just --fmt
20-
uv run --with pre-commit-uv pre-commit run --all-files
24+
@just nox lint
25+
26+
test *ARGS:
27+
@just nox test {{ ARGS }}
28+
29+
testall *ARGS:
30+
@just nox tests {{ ARGS }}

noxfile.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env -S uv run --quiet
2+
# /// script
3+
# requires-python = ">=3.13"
4+
# dependencies = [
5+
# "nox",
6+
# ]
7+
# ///
8+
9+
from __future__ import annotations
10+
11+
import json
12+
import os
13+
from pathlib import Path
14+
15+
import nox
16+
17+
nox.options.default_venv_backend = "uv|virtualenv"
18+
nox.options.reuse_existing_virtualenvs = True
19+
20+
PY39 = "3.9"
21+
PY310 = "3.10"
22+
PY311 = "3.11"
23+
PY312 = "3.12"
24+
PY313 = "3.13"
25+
PY_VERSIONS = [PY39, PY310, PY311, PY312, PY313]
26+
PY_DEFAULT = PY_VERSIONS[0]
27+
PY_LATEST = PY_VERSIONS[-1]
28+
29+
DJ42 = "4.2"
30+
DJ50 = "5.0"
31+
DJ51 = "5.1"
32+
DJMAIN = "main"
33+
DJMAIN_MIN_PY = PY312
34+
DJ_VERSIONS = [DJ42, DJ50, DJ51, DJMAIN]
35+
DJ_LTS = [
36+
version for version in DJ_VERSIONS if version.endswith(".2") and version != DJMAIN
37+
]
38+
DJ_DEFAULT = DJ_LTS[0]
39+
DJ_LATEST = DJ_VERSIONS[-2]
40+
41+
42+
def version(ver: str) -> tuple[int, ...]:
43+
"""Convert a string version to a tuple of ints, e.g. "3.10" -> (3, 10)"""
44+
return tuple(map(int, ver.split(".")))
45+
46+
47+
def should_skip(python: str, django: str) -> bool:
48+
"""Return True if the test should be skipped"""
49+
50+
if django == DJMAIN and version(python) < version(DJMAIN_MIN_PY):
51+
# Django main requires Python 3.10+
52+
return True
53+
54+
if django == DJ51 and version(python) < version(PY310):
55+
# Django 5.1 requires Python 3.10+
56+
return True
57+
58+
if django == DJ50 and version(python) < version(PY310):
59+
# Django 5.0 requires Python 3.10+
60+
return True
61+
62+
return False
63+
64+
65+
@nox.session
66+
def test(session):
67+
session.notify(f"tests(python='{PY_DEFAULT}', django='{DJ_DEFAULT}')")
68+
69+
70+
@nox.session
71+
@nox.parametrize(
72+
"python,django",
73+
[
74+
(python, django)
75+
for python in PY_VERSIONS
76+
for django in DJ_VERSIONS
77+
if not should_skip(python, django)
78+
],
79+
)
80+
def tests(session, django):
81+
session.run_install(
82+
"uv",
83+
"sync",
84+
"--frozen",
85+
"--inexact",
86+
"--no-install-package",
87+
"django",
88+
"--python",
89+
session.python,
90+
env={"UV_PROJECT_ENVIRONMENT": session.virtualenv.location},
91+
)
92+
93+
if django == DJMAIN:
94+
session.install(
95+
"django @ https://github.com/django/django/archive/refs/heads/main.zip"
96+
)
97+
else:
98+
session.install(f"django=={django}")
99+
100+
command = ["cargo", "test"]
101+
if session.posargs:
102+
args = []
103+
for arg in session.posargs:
104+
if arg:
105+
args.extend(arg.split(" "))
106+
command.extend(args)
107+
session.run(*command, external=True)
108+
109+
110+
@nox.session
111+
def lint(session):
112+
session.run(
113+
"uv",
114+
"run",
115+
"--with",
116+
"pre-commit-uv",
117+
"--python",
118+
PY_LATEST,
119+
"pre-commit",
120+
"run",
121+
"--all-files",
122+
)
123+
124+
125+
@nox.session
126+
def gha_matrix(session):
127+
os_args = session.posargs[0] if session.posargs else ""
128+
os_list = [os.strip() for os in os_args.split(",") if os_args.strip()] or [
129+
"ubuntu-latest"
130+
]
131+
132+
sessions = session.run("nox", "-l", "--json", external=True, silent=True)
133+
versions_list = [
134+
{
135+
"django-version": session["call_spec"]["django"],
136+
"python-version": session["python"],
137+
}
138+
for session in json.loads(sessions)
139+
if session["name"] == "tests"
140+
]
141+
142+
matrix = {
143+
"include": [{**combo, "os": os} for os in os_list for combo in versions_list]
144+
}
145+
146+
if os.environ.get("GITHUB_OUTPUT"):
147+
with Path(os.environ["GITHUB_OUTPUT"]).open("a") as fh:
148+
print(f"matrix={matrix}", file=fh)
149+
else:
150+
print(matrix)
151+
152+
153+
if __name__ == "__main__":
154+
nox.main()

0 commit comments

Comments
 (0)