Nox automates testing across Python versions and environments. pip install nox. Create noxfile.py. import nox. Session: @nox.session(python=["3.11","3.12"]) def tests(session): session.install("-e",".[dev]"); session.run("pytest", *session.posargs). Install: session.install("pytest", "pytest-cov"). Run: session.run("ruff", "check", "."). Posargs: nox -- --tb=short -k auth passes through to pytest. Parametrize: @nox.session @nox.parametrize("db", ["postgres","sqlite"]). Reuse venv: @nox.session(reuse_venv=True). Tag: @nox.session(tags=["lint"]). Notify: session.notify("docs"). nox.options.sessions = ["tests", "lint"] — default sessions. nox.options.reuse_existing_virtualenvs = True. Run: nox -s tests. List: nox --list. Skip install: nox -s tests --no-install. Specific py: nox -s tests-3.12. Select tags: nox -t lint. Skip venv: nox -s tests --no-venv. Error on POSIX: session.error("message") — marks session as failed. Notify: session.notify("other_session") — runs another session after this one. Chdir: session.chdir("subdir"). Log: session.log("msg"). Warn: session.warn("msg"). External: session.run("make", "docs", external=True) — allow non-Python executables. nox --no-error-on-missing-interpreters — skip missing Python versions in CI. nox --envdir /tmp/nox-envs — custom venv location. Claude Code generates Nox test matrix configurations, lint pipelines, and CI-ready noxfiles.
CLAUDE.md for Nox
## Nox Stack
- Version: nox >= 2024.0
- Config: noxfile.py at project root
- Session: @nox.session(python=["3.11","3.12"], reuse_venv=True)
- Install: session.install("pkg") | session.install("-e", ".[dev]")
- Run: session.run("pytest", "--cov", *session.posargs)
- posargs: nox -s tests -- --tb=short -k auth → passed as session.posargs
- Tags: @nox.session(tags=["lint"]) | nox -t lint
- CI: nox -s lint tests | --no-error-on-missing-interpreters
Nox Test Automation Pipeline
# noxfile.py — test and lint automation with Nox
# Usage:
# nox — run default sessions (lint + tests)
# nox -s tests — run just tests
# nox -s lint — run just linting
# nox -s tests -- -k auth --tb=short — pass args to pytest
# nox -l — list all sessions
from __future__ import annotations
import nox
# ── Global options ────────────────────────────────────────────────────────────
nox.options.sessions = ["lint", "type_check", "tests"]
nox.options.reuse_existing_virtualenvs = True
nox.options.error_on_missing_interpreters = False
PYTHON_VERSIONS = ["3.11", "3.12"]
SOURCES = ["src", "tests", "noxfile.py"]
# ── 1. Linting and formatting ─────────────────────────────────────────────────
@nox.session(python="3.12", tags=["lint"])
def lint(session: nox.Session) -> None:
"""
Run Ruff linter and formatter check.
Fix issues automatically with: nox -s lint -- --fix
"""
session.install("ruff")
fix = "--fix" in session.posargs
args = ["--fix"] if fix else []
session.run("ruff", "check", *args, *SOURCES, external=False)
session.run("ruff", "format", "--check" if not fix else "", *SOURCES)
@nox.session(python="3.12", tags=["lint"])
def format_check(session: nox.Session) -> None:
"""Check code formatting without modifying files."""
session.install("ruff", "black", "isort")
session.run("black", "--check", "--diff", *SOURCES)
session.run("isort", "--check-only", "--diff", *SOURCES)
@nox.session(python="3.12", tags=["lint", "security"])
def security(session: nox.Session) -> None:
"""Run Bandit security linter and pip-audit for dependency CVEs."""
session.install("bandit[toml]", "pip-audit")
session.run("bandit", "-r", "src", "-c", "pyproject.toml", "-q")
session.run("pip-audit", "--strict")
# ── 2. Type checking ──────────────────────────────────────────────────────────
@nox.session(python="3.12", tags=["lint", "typecheck"])
def type_check(session: nox.Session) -> None:
"""
Run mypy static type checking.
Install the project in editable mode so mypy can find all modules.
"""
session.install("-e", ".[dev]")
session.install("mypy", "types-requests", "types-PyYAML")
session.run(
"mypy",
"src",
"--ignore-missing-imports",
"--strict",
"--show-error-codes",
*session.posargs,
)
# ── 3. Tests ──────────────────────────────────────────────────────────────────
@nox.session(python=PYTHON_VERSIONS, tags=["test"])
def tests(session: nox.Session) -> None:
"""
Run the full test suite with coverage.
Pass pytest args after '--': nox -s tests -- -k auth --tb=short
"""
session.install("-e", ".[dev]")
session.install("pytest", "pytest-cov", "pytest-xdist", "pytest-timeout")
session.run(
"pytest",
"tests/",
"--cov=src",
"--cov-report=term-missing",
"--cov-report=xml:coverage.xml",
"--cov-fail-under=80",
"-q",
"--timeout=60",
*session.posargs,
)
@nox.session(python="3.12", tags=["test"])
def tests_fast(session: nox.Session) -> None:
"""
Fast unit-only test run (skip integration and slow tests).
Use for rapid development feedback.
"""
session.install("-e", ".[dev]")
session.install("pytest", "pytest-xdist")
session.run(
"pytest",
"tests/unit/",
"-x", # stop on first failure
"-q",
"-n", "auto", # parallel with pytest-xdist
"-m", "not slow",
*session.posargs,
)
@nox.session(python="3.12", tags=["test"])
def integration(session: nox.Session) -> None:
"""
Integration tests that require external services (DB, cache, etc.).
Typically run only in CI or when INTEGRATION=1.
"""
import os
if not os.environ.get("INTEGRATION"):
session.skip("Set INTEGRATION=1 to run integration tests")
session.install("-e", ".[dev]")
session.install("pytest", "pytest-asyncio")
session.run(
"pytest",
"tests/integration/",
"-v",
"--timeout=120",
*session.posargs,
)
# ── 4. Documentation ──────────────────────────────────────────────────────────
@nox.session(python="3.12", tags=["docs"])
def docs(session: nox.Session) -> None:
"""Build Sphinx or MkDocs documentation."""
session.install("-e", ".[docs]")
session.run("mkdocs", "build", "--strict", external=True)
@nox.session(python="3.12", tags=["docs"], reuse_venv=True)
def docs_serve(session: nox.Session) -> None:
"""Serve documentation locally for development."""
session.install("-e", ".[docs]")
session.run("mkdocs", "serve", external=True)
# ── 5. Database / migration sessions ─────────────────────────────────────────
@nox.parametrize("db", ["postgresql", "sqlite"])
@nox.session(python="3.12", tags=["test"])
def tests_db(session: nox.Session, db: str) -> None:
"""
Run tests against multiple database backends.
Matrix: 3.12 × {postgresql, sqlite}
"""
session.install("-e", ".[dev]")
session.install("pytest", "pytest-cov")
session.env["DATABASE_BACKEND"] = db
if db == "postgresql":
if not session.env.get("DATABASE_URL"):
session.skip("Set DATABASE_URL=postgresql://... to run Postgres tests")
session.run(
"pytest",
"tests/",
"-m", f"db",
"-q",
f"--db={db}",
*session.posargs,
)
# ── 6. Coverage report ────────────────────────────────────────────────────────
@nox.session(python="3.12", tags=["coverage"])
def coverage(session: nox.Session) -> None:
"""
Generate HTML coverage report from a previous test run.
Requires tests to have written .coverage file first.
"""
session.install("coverage[toml]")
session.run("coverage", "html", "--fail-under=80")
session.run("coverage", "report", "--show-missing")
session.log("HTML coverage report generated in htmlcov/")
# ── 7. Release / packaging ────────────────────────────────────────────────────
@nox.session(python="3.12", tags=["release"])
def build(session: nox.Session) -> None:
"""Build source and wheel distributions."""
session.install("build", "twine")
session.run("python", "-m", "build")
session.run("twine", "check", "dist/*")
session.log("Artifacts in dist/")
@nox.session(python="3.12", tags=["release"])
def publish(session: nox.Session) -> None:
"""
Upload to PyPI. Requires TWINE_TOKEN env var.
Run with: nox -s publish
"""
import os
if not os.environ.get("TWINE_TOKEN"):
session.error("Set TWINE_TOKEN env var to publish")
session.install("twine")
session.run("twine", "upload", "dist/*")
# ── 8. Dev environment bootstrap ─────────────────────────────────────────────
@nox.session(python="3.12", reuse_venv=True, tags=["dev"])
def dev(session: nox.Session) -> None:
"""
Set up a complete local dev environment.
Run once: nox -s dev
Then activate: source .nox/dev/bin/activate
"""
session.install("-e", ".[dev]")
session.install(
"pre-commit",
"ipython",
"ipdb",
)
session.run("pre-commit", "install", external=True)
session.log("Dev environment ready. Run: source .nox/dev/bin/activate")
For the Makefile alternative — Makefiles require shell syntax and don’t handle Python virtualenv creation, dependency isolation between tasks, or cross-platform Windows support, while a noxfile.py session uses session.install() to create an isolated venv per task so nox -s lint never conflicts with nox -s tests dependencies, and @nox.session(python=["3.11","3.12"]) generates two parameterized sessions from one definition that CI can run in parallel with nox -s tests-3.11 tests-3.12. For the tox alternative — tox uses an .ini configuration format with limited Python expressiveness while Nox sessions are regular Python functions where if not os.environ.get("INTEGRATION"): session.skip(...) conditionally skips integration tests, @nox.parametrize("db", ["postgresql","sqlite"]) generates a cross-product matrix the way pytest.mark.parametrize does, and session.posargs forwards nox -- -k auth directly to pytest without escaping shell arguments. The Claude Skills 360 bundle includes Nox skill sets covering session decorator with python and tags, session.install editable project, session.run with posargs, lint/type-check/tests/docs session patterns, @nox.parametrize matrix testing, skip and error conditional logic, reuse_venv for fast iteration, coverage HTML report, build and publish sessions, and dev bootstrap session. Start with the free tier to try test automation code generation.