codeEngineering
Python Service Fundamentals
Async vs sync, type hints, project structure, and config — the foundations every Python service rests on.
1 item
Links1
01NotesNote
Async vs sync — pick deliberately
- Use
asyncwhen the work is I/O-bound (DB, HTTP, queue calls) and you want concurrency on a single thread via the event loop. - Use sync + a thread/process pool when the work is CPU-bound (hashing, image processing, ML inference). Async does not speed up CPU work — it just lets I/O overlap.
- The cardinal async sin: calling a blocking function inside an
async def. One blocking call (requests.get,time.sleep, a sync DB driver) stalls the entire event loop. Offload withawait asyncio.to_thread(...)or use an async-native library.
Type hints are not optional
- Annotate everything. Pydantic, FastAPI, SQLModel, and modern ORMs all derive behavior from types.
- Run
mypy(orpyright) in CI. Types catch a whole class of bugs before runtime and document intent for free.
Project layout that scales
app/
api/ # routers / views — thin, no business logic
services/ # business logic, orchestration
repositories/ # data access; the only layer that touches the ORM
models/ # ORM models + Pydantic schemas
core/ # config, logging, security, settings
workers/ # background tasks, Temporal workflows
Keep routers thin: validate input, call a service, serialize output. Business logic lives in services; data access lives in repositories. This separation is what lets you swap FastAPI for Django, or Postgres for something else, without rewriting your domain.
Config & secrets
- Load config from the environment via Pydantic
BaseSettings. Never hardcode; never commit secrets. - Fail fast on startup if a required env var is missing — a crash at boot beats a 500 at 2 a.m.
Dependency hygiene
- Pin versions (
uvorpoetrylockfiles). Reproducible builds matter. - Prefer
uvfor speed; it's dramatically faster than pip for installs and resolution.