from typing import Annotatedfrom sqlalchemy.orm import Session from fastapi import FastAPI, Dependsimport models from models import Todos from database import engine, SessionLocalapp = FastAPI()models.Base.metadata.create_all(bind=engine)def get_db():db = SessionLocal()try:yield dbfinally:db.close()# Dependency injection db_dependency = Annotated[Session, Depends(get_db)]@app.get("/") async def read_all(db: db_dependency):return db.query(Todos).all()
In this context, Annotated is a way to tell FastAPI: "This variable is a SQLAlchemy Session, but I want you to get its value by running the get_db function."
Think of it as adding "metadata" or instructions to a standard Python type.
Breaking it down
Without Annotated, your function signature might look like this: async def read_all(db: Session = Depends(get_db)):
While that works, Annotated is the modern, preferred approach for three main reasons:
1. Separation of Concerns
It separates the type (what the code uses) from the logic (how FastAPI finds it).
-
The Type:
Session(used by your IDE for autocomplete and linting). -
The Metadata:
Depends(get_db)(used by FastAPI to inject the database).
2. Reusability (The "Don't Repeat Yourself" Principle)
In your code, you defined db_dependency. This is a huge win for clean code. Instead of typing out the dependency logic in every single route, you just use your custom alias:
# Instead of this in every route:
@app.get("/")
async def read_all(db: Session = Depends(get_db)): ...# You do this:
@app.get("/")
async def read_all(db: db_dependency): ...
If you ever decide to change get_db to a different function, you only have to change it in one place (the Annotated definition) rather than in fifty different API endpoints.
3. Better Tooling Support
Standard Python tools (like Mypy or Pyright) sometimes get confused by db: Session = Depends(). Using Annotated keeps the type hint "pure," making your IDE much better at suggesting methods like .query(), .add(), or .commit().
How it looks in the Type Hierarchy
A Quick Tip
Since you are using models.Base.metadata.create_all(bind=engine) at the top of your file, your tables are created when the app starts. However, in larger production apps, people usually move that to a migration tool like Alembic.
