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 async when 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 with await 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 (or pyright) 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 (uv or poetry lockfiles). Reproducible builds matter.
  • Prefer uv for speed; it's dramatically faster than pip for installs and resolution.