Skip to content

Commit 8aef060

Browse files
authored
Add nox and nox-uv for DX testing improvements (#516)
1 parent 36ad867 commit 8aef060

File tree

5 files changed

+375
-2
lines changed

5 files changed

+375
-2
lines changed

CLAUDE.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,44 @@ uv sync --locked --dev # Install package in development mode with dev dependenci
1313
### Code Quality
1414

1515
```bash
16-
ur run ruff format . # Format code
16+
uv run ruff format . # Format code
1717
uv run ruff format --check . # Check formatting without making changes
1818
uv run ruff check . # Lint code
1919
uv run mypy # Type checking
2020
```
2121

2222
### Testing
2323

24+
The SDK uses [nox](https://nox.thea.codes/) with [nox-uv](https://github.com/dantebben/nox-uv) for multi-version Python testing. This ensures compatibility across all supported Python versions (3.8-3.14).
25+
26+
**Quick testing with the test script:**
27+
28+
```bash
29+
./scripts/test.sh # Run tests on all Python versions
30+
./scripts/test.sh 3.12 # Run tests on Python 3.12 only
31+
./scripts/test.sh 3.11 3.12 # Run tests on Python 3.11 and 3.12
32+
./scripts/test.sh --coverage # Run tests with coverage on all versions
33+
./scripts/test.sh --ci # Run full CI checks (lint, type, tests)
34+
./scripts/test.sh --fresh # Recreate virtual environments
35+
./scripts/test.sh 3.12 -- -k "test_sso" -v # Pass pytest arguments
36+
```
37+
38+
**Direct nox commands:**
39+
40+
```bash
41+
uv run nox # Run tests on all Python versions
42+
uv run nox -s tests-3.12 # Run tests on specific Python version
43+
uv run nox -s coverage # Run tests with coverage
44+
uv run nox -s lint # Run linting
45+
uv run nox -s typecheck # Run type checking
46+
uv run nox -s ci # Run all CI checks
47+
uv run nox -l # List all available sessions
48+
```
49+
50+
**Single-version testing (faster for development):**
51+
2452
```bash
25-
uv run pytest # Run all tests
53+
uv run pytest # Run all tests on current Python
2654
uv run pytest tests/test_sso.py # Run specific test file
2755
uv run pytest -k "test_name" # Run tests matching pattern
2856
uv run pytest --cov=workos # Run tests with coverage

noxfile.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Nox configuration for multi-version Python testing.
2+
3+
This configuration uses nox-uv for fast, reproducible environment management
4+
with uv's lockfile. Run `nox` to test against all supported Python versions,
5+
or use `nox -s tests-3.12` to test a specific version.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import nox
11+
from nox_uv import session
12+
13+
# Use uv as the default venv backend for speed
14+
nox.options.default_venv_backend = "uv"
15+
16+
# Reuse virtual environments by default for faster local iteration
17+
nox.options.reuse_venv = "yes"
18+
19+
# All Python versions supported by the SDK (must match CI matrix)
20+
PYTHON_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
21+
22+
# Default sessions to run
23+
nox.options.sessions = ["tests"]
24+
25+
26+
@session(python=PYTHON_VERSIONS, uv_groups=["test"])
27+
def tests(s: nox.Session) -> None:
28+
"""Run the test suite against all supported Python versions."""
29+
args = s.posargs or []
30+
s.run("pytest", *args)
31+
32+
33+
@session(python=PYTHON_VERSIONS, uv_groups=["test"])
34+
def coverage(s: nox.Session) -> None:
35+
"""Run tests with coverage reporting."""
36+
s.run("pytest", "--cov=workos", "--cov-report=term-missing", *s.posargs)
37+
38+
39+
@session(uv_only_groups=["lint"])
40+
def lint(s: nox.Session) -> None:
41+
"""Run linting with ruff."""
42+
s.run("ruff", "check", ".")
43+
44+
45+
@session(uv_only_groups=["lint"])
46+
def format(s: nox.Session) -> None:
47+
"""Check code formatting with ruff."""
48+
s.run("ruff", "format", "--check", ".")
49+
50+
51+
@session(uv_only_groups=["lint"])
52+
def format_fix(s: nox.Session) -> None:
53+
"""Apply code formatting with ruff."""
54+
s.run("ruff", "format", ".")
55+
56+
57+
@session(uv_groups=["type_check"])
58+
def typecheck(s: nox.Session) -> None:
59+
"""Run type checking with mypy."""
60+
s.run("mypy")
61+
62+
63+
@session(uv_groups=["test", "lint", "type_check"])
64+
def ci(s: nox.Session) -> None:
65+
"""Run all CI checks (format, lint, typecheck, tests) for a single Python version.
66+
67+
This is useful for quick local validation before pushing.
68+
"""
69+
s.run("ruff", "format", "--check", ".")
70+
s.run("ruff", "check", ".")
71+
s.run("mypy")
72+
s.run("pytest")

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dev = [
2525
{ include-group = "test" },
2626
{ include-group = "lint" },
2727
{ include-group = "type_check" },
28+
{ include-group = "nox" },
2829
]
2930
test = [
3031
"pytest==8.3.4",
@@ -34,6 +35,10 @@ test = [
3435
]
3536
lint = ["ruff==0.14.5"]
3637
type_check = ["mypy==1.14.1"]
38+
nox = [
39+
"nox>=2024.10.9 ; python_version >= '3.9'",
40+
"nox-uv>=0.7.0 ; python_version >= '3.9'",
41+
]
3742

3843

3944
[tool.mypy]

scripts/test.sh

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Usage:
4+
# ./scripts/test.sh # Run tests on all Python versions
5+
# ./scripts/test.sh 3.12 # Run tests on Python 3.12 only
6+
# ./scripts/test.sh 3.11 3.12 # Run tests on Python 3.11 and 3.12
7+
# ./scripts/test.sh --coverage # Run tests with coverage on all versions
8+
# ./scripts/test.sh --ci # Run full CI checks (lint, type, tests)
9+
# ./scripts/test.sh --fresh # Recreate virtual environments
10+
#
11+
# Additional pytest arguments can be passed after --, e.g.:
12+
# ./scripts/test.sh 3.12 -- -k "test_sso" -v
13+
14+
set -e
15+
16+
# Check if uv is available
17+
if ! command -v uv &>/dev/null; then
18+
echo "Error: uv is not installed or not in PATH"
19+
echo "Install uv: https://docs.astral.sh/uv/getting-started/installation/"
20+
exit 1
21+
fi
22+
23+
# Parse arguments
24+
PYTHON_VERSIONS=()
25+
NOX_ARGS=()
26+
PYTEST_ARGS=()
27+
SESSION="tests"
28+
FRESH=false
29+
PARSING_PYTEST_ARGS=false
30+
31+
for arg in "$@"; do
32+
if [[ "$PARSING_PYTEST_ARGS" == true ]]; then
33+
PYTEST_ARGS+=("$arg")
34+
elif [[ "$arg" == "--" ]]; then
35+
PARSING_PYTEST_ARGS=true
36+
elif [[ "$arg" == "--coverage" ]]; then
37+
SESSION="coverage"
38+
elif [[ "$arg" == "--ci" ]]; then
39+
SESSION="ci"
40+
elif [[ "$arg" == "--fresh" ]]; then
41+
FRESH=true
42+
elif [[ "$arg" =~ ^3\.[0-9]+$ ]]; then
43+
PYTHON_VERSIONS+=("$arg")
44+
else
45+
NOX_ARGS+=("$arg")
46+
fi
47+
done
48+
49+
# Build the nox command
50+
CMD=(uv run nox -s)
51+
52+
if [[ ${#PYTHON_VERSIONS[@]} -gt 0 ]]; then
53+
# Run specific Python versions
54+
SESSIONS=""
55+
for ver in "${PYTHON_VERSIONS[@]}"; do
56+
if [[ -n "$SESSIONS" ]]; then
57+
SESSIONS="$SESSIONS,"
58+
fi
59+
SESSIONS="${SESSIONS}${SESSION}-${ver}"
60+
done
61+
CMD+=("$SESSIONS")
62+
else
63+
# Run all versions
64+
CMD+=("$SESSION")
65+
fi
66+
67+
# Add fresh flag if requested
68+
if [[ "$FRESH" == true ]]; then
69+
CMD+=(--reuse-venv=never)
70+
fi
71+
72+
# Add any additional nox args
73+
if [[ ${#NOX_ARGS[@]} -gt 0 ]]; then
74+
CMD+=("${NOX_ARGS[@]}")
75+
fi
76+
77+
# Add pytest args if provided
78+
if [[ ${#PYTEST_ARGS[@]} -gt 0 ]]; then
79+
CMD+=(-- "${PYTEST_ARGS[@]}")
80+
fi
81+
82+
echo "Running: ${CMD[*]}"
83+
exec "${CMD[@]}"

0 commit comments

Comments
 (0)