What dependency injection actually means
A handler needs a database session, an HTTP client, the current user. Three ways to give it them:
# A. Module-level global — easy, untestable, leaks state across requests.
db = SessionLocal()
# B. Construct inside the handler — couples the handler to the construction.
@app.post("/users")
def create_user(payload):
db = SessionLocal()
...
# C. Inject — the handler declares dependencies, the framework supplies them.
@app.post("/users")
def create_user(payload, db: Session = Depends(get_db)):
...
Approach C is dependency injection. The handler signature is the contract; the framework walks that signature, finds matching providers, and supplies them at call time. The provider for db can be swapped (test fake, alternate engine, read replica) without touching the handler.
This pattern is older than FastAPI — Spring, Angular, and Django's class-based view system all do it. FastAPI's twist is that providers are themselves plain callables (including async ones, including generators for cleanup), so the same machinery handles auth checks, DB sessions, and request-scoped caches.
