Injecting Dependencies with Annotated
As we refactor our application into multiple routers, we'll notice that many of our endpoints require the same resources, most commonly a database session.
Instead of manually creating and closing database sessions in every endpoint, FastAPI provides a powerful Dependency Injection system. SessionDep in main.py already uses it.
The Depends Function
FastAPI's Depends function tells the framework: "Before you run this endpoint, run this other function first, and give me its result."
For our database, we can create a simple generator function that yields a session and then closes it after the request is finished:
def get_session() -> Generator[Session]:
with Session(get_engine()) as session:
yield session
What exactly is happening here?
When FastAPI needs to resolve this dependency, it executes the code up until the yield statement. The with Session(get_engine()) as session: context manager opens a database connection and creates a session, which is then yielded to your route handler.
Once your route handler finishes and returns a response to the user, FastAPI resumes execution of the generator after the yield. At that point, the context manager exits, which automatically closes the database session.
Automatic Rollbacks
If an unhandled exception (like an IntegrityError) bubbles up out of your route handler, the Session context manager intercepts it during cleanup. SQLModel's context manager is designed to automatically roll back any active, uncommitted transaction when an exception occurs inside its with block. The route handler doesn't have to call session.rollback() manually.
We could use this directly in an endpoint like so:
from fastapi import Depends, FastAPI
from sqlmodel import Session
from .database import get_session
app = FastAPI()
@app.get("/projects")
def list_projects(session: Session = Depends(get_session)):
# ... use session
return []
While this works, typing session: Session = Depends(get_session) over and over again is repetitive and clutters our function signatures.
Cleaner Dependencies with Annotated
Python 3.9 introduced typing.Annotated, which allows us to attach extra metadata to a type hint. FastAPI is designed to read this metadata.
We can create a central dependencies.py file to define our reusable, annotated dependencies:
from typing import Annotated
from fastapi import Depends
from sqlmodel import Session
from .database import get_session
# Create a reusable dependency type
SessionDep = Annotated[Session, Depends(get_session)]
Any endpoint that needs a database session can simply use SessionDep as the type hint. FastAPI will see the Depends(get_session) metadata and automatically inject the session.
from fastapi import FastAPI
from .dependencies import SessionDep
app = FastAPI()
@app.get("/projects")
def list_projects(session: SessionDep):
# session is automatically injected and managed.
pass
This makes our endpoint signatures much cleaner and easier to read. As we add authentication later, we'll use this exact same pattern to inject the CurrentUser.