Docs / Recommended libraries & conventions for a Python app
Guidedeploymill://guides/conventions/python

Recommended libraries & conventions — Python (deploymill)

You are an agent about to write or extend application code in a deploymill-managed Python app. This guide is deploymill's paved road: the libraries and patterns that are known to work with the platform's constraints, plus where deploymill has no opinion and you should defer to the user or the repo.

Read it together with deploymill://guides/stack/python, which documents the platform contract — the things that make the deploy succeed or fail. This guide is about choices; the stack guide is about requirements.

How to read these recommendations

Recommendations come in three strengths. Treat them differently:

  • Contract (must). Lives in deploymill://guides/stack/python, not here. Violate it and the deploy breaks (bind to 0.0.0.0, listen on port 8000, ephemeral filesystem, slim runtime stage, no runtime pip install).
  • Paved road (should). The defaults below. They're not arbitrary — every one is either already in the template deploymill scaffolds for you or already used by the database playbook (deploymill://guides/database/python). Following them keeps the app consistent with what the rest of the tooling assumes. You can deviate; each row says when that's reasonable and what you have to preserve if you do.
  • Taste (deploymill has no opinion). Formatting, test runner, folder layout beyond the template. deploymill is an orchestration layer, not a framework — it won't dictate these. Defer to the user, or to the repo's .deploymill/conventions.md if one exists (see the bottom of this guide).

The rule of thumb: the closer a choice sits to a platform constraint, the more weight the recommendation carries. A pooled DB client is a strong recommendation because Neon caps connections; a formatter is no recommendation at all.

Paved road

These are the choices the python template and the database guide already commit to. Stay on them unless you have a concrete reason.

ConcernDefaultWhy (tie to the platform)When to deviate
RuntimePython 3.12 (python:3.12-slim)The scaffolded Dockerfile's base image.Only with a matching base-image bump in the Dockerfile.
Dependency metadatapyproject.toml (PEP 621)What the template ships; the build reads deps from it.
Installeruv (uv pip install --system)The Dockerfile uses it for fast installs onto PATH in the runtime image. No venv (the stack guide explains why a venv just adds layers).
Web frameworkFastAPI + uvicorn[standard]What the template scaffolds; the CMD passes --host 0.0.0.0 --port 8000 to satisfy the bind/port contract.Django/Flask — keep the host/port contract and /healthz, update CMD.
Postgres clientpsycopg[binary,pool] (>=3.2), one ConnectionPool at module scope[binary] avoids build tooling in the image; the single pool respects Neon's connection limit (a per-request pool exhausts it).Async app → asyncpg / async SQLAlchemy; swap the pool import, the rest of the playbook holds.
Migrationsalembic (>=1.13), alembic upgrade head on container startIdempotent and runs before traffic, so a half-applied schema never serves.
Health routes/healthz (liveness), /db (DB reachability)deploymill's liveness convention and the post-deploy DB check.Keep a stable replacement path if you move them.

What the paved road steers you away from (from the database guide's "What NOT to do"): don't reach for the SQLAlchemy ORM unless the user explicitly asks. Plain psycopg + Alembic keeps the surface area small and is enough for most scaffolded apps. (Alembic itself uses SQLAlchemy Core for migration ops — that's fine and separate from using the ORM in app code.)

When the user needs a capability the template doesn't ship

These aren't in the scaffold, so there's no forced default. The picks below are recommended-but-optional — reach for them when the user asks for the capability, and feel free to substitute if they have a preference. Each one is tied to something deploymill already gives you, or something FastAPI already pulls in.

  • Request/response models & validation → Pydantic. FastAPI already depends on it, so it costs nothing and is the idiomatic choice. Optional only in the sense that you might not need models at all.
  • Auth → a SQL-backed library over the managed Postgres you already provision via database: { provider: "neon" } — no extra infra. If the user wants a hosted IdP (Clerk, Auth0) instead, that's fine; host-pinned callback URLs must be set per-environment via set_env_vars (and per-preview via create_preview envOverrides — see deploymill://guides/previews). Read deploymill://guides/auth before wiring auth — it covers the platform facts that trip up login on deploy/preview (sessions in Postgres, host-pinned base URL, secrets via the vault, HTTPS-vs-secure-cookies).
  • Concurrency → uvicorn --workers N (rule of thumb 2N+1 vs cores) or gunicorn -w N, per the stack guide. Tune in CMD.

Things deploymill has no opinion on

Don't impose a default here. If the user hasn't said and there's no .deploymill/conventions.md, pick the least surprising option and move on — these have no platform consequence:

  • Code formatting / linting (Ruff, Black, isort).
  • Test framework (pytest, unittest).
  • Folder structure beyond what the template scaffolds.
  • Logging library, naming conventions.

Project-level overrides: .deploymill/conventions.md

A repo (or the user) may pin its own house style. Before applying the defaults above, check for a .deploymill/conventions.md file in the repo (read it with get_file). If present, it overrides this guide wherever the two disagree — it's the team's explicit choice and wins over deploymill's generic paved road. The platform contract in the stack guide still holds regardless; a project can't opt out of binding to 0.0.0.0:8000.

This is the same file-as-truth pattern as .deploymill/project.json: conventions live in the repo, version with the code, and travel with the project.