Appendix A - Production Hardening Toolkit¶
The hardening template you accumulate over 24 weeks. By the end, this should be a publishable python-project-template repo.
A.1 Project Layout¶
my-project/
├── pyproject.toml # PEP 621 metadata, ruff/pyright/pytest config
├── uv.lock # uv-managed lockfile
├── src/
│ └── my_project/
│ ├── __init__.py
│ ├── __main__.py
│ ├── domain/ # pure, no I/O
│ ├── adapters/ # DB, HTTP, LLM clients
│ ├── service/ # orchestration
│ └── entrypoints/ # FastAPI, CLI
├── tests/
│ ├── unit/
│ ├── integration/
│ └── property/
├── perf/ # pytest-benchmark suites
├── loadtest/ # k6 / locust
├── docs/
└── .github/workflows/ci.yml
A.2 Tooling Stack (canonical 2026)¶
| Concern | Tool |
|---|---|
| Build / dep | uv (primary), hatch (alt) |
| Lint + format | ruff |
| Type check | pyright (strict), mypy (secondary) |
| Test | pytest, pytest-asyncio, pytest-cov, pytest-xdist, pytest-benchmark, pytest-randomly |
| Property test | hypothesis |
| Mutation test | mutmut |
| Profile (CPU) | py-spy, scalene, pyinstrument |
| Profile (mem) | memray, tracemalloc |
| Security | bandit (via ruff S), pip-audit, safety |
| Docs | mkdocs-material + mkdocstrings |
| Pre-commit | pre-commit |
| Container | distroless or python:3.13-slim, multi-stage with uv pip install --system |
| Observability | structlog, prometheus_client, opentelemetry-* |
A.3 The make check Target¶
check: lint format-check typecheck test
lint:
ruff check src tests
format-check:
ruff format --check src tests
typecheck:
pyright src
test:
pytest -x --cov=src --cov-report=term-missing
bench:
pytest perf/ --benchmark-only
load:
k6 run loadtest/scenario.js
A.4 CI Matrix¶
- Python: 3.12, 3.13, 3.13t (free-threaded), 3.14 (when stable).
- OS: ubuntu-latest, macos-latest.
- Steps:
make check,make bench(non-failing, archived),mutmut run --max-children 4(weekly cron).
A.5 Profiling Recipes¶
- "Why is my service slow?" →
py-spy record -o flame.svg -- python -m my_project - "Where is my memory going?" →
memray run --live python -m my_project - "Is the event loop stalling?" → set
loop.slow_callback_duration = 0.05; watch logs. - "Why is import slow?" →
python -X importtime -c "import my_project" 2> import.log - "What's the GC doing?" →
gc.set_debug(gc.DEBUG_STATS)for an hour in staging.
A.6 Deployment Hardening¶
python -Ostrips asserts; never rely on asserts for security checks.PYTHONHASHSEED=randomis default in 3.3+; do not unset.PYTHONFAULTHANDLER=1for crash tracebacks on segfault from C extensions.PYTHONMALLOC=mallocif running undervalgrind.- Drop privileges (
gosu,setuid) before exec'ing the Python process. - Distroless or slim base; pin via SHA, not tag.
- One worker per CPU for CPU-light I/O-bound on stock CPython; one process for free-threaded once stable.