Saltar a contenido

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 -O strips asserts; never rely on asserts for security checks.
  • PYTHONHASHSEED=random is default in 3.3+; do not unset.
  • PYTHONFAULTHANDLER=1 for crash tracebacks on segfault from C extensions.
  • PYTHONMALLOC=malloc if running under valgrind.
  • 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.

Comments