AI Hints
Home›FastAPI Tutorial›Building the API
FastAPI · Lesson 7 of 12

Dependencies

⏱ 2 min read▶ Video at 14:24🎓 Free
Watch this lesson in the FastAPI courseWatch on YouTube ↗Full FastAPI course

Maya's developers are getting tired. The same pagination parameters appear in every endpoint. The same check for a missing menu item is copied again and again. And every order lookup opens a database connection, that must be closed afterwards. Copy and paste is how bugs are born. FastAPI has a clean answer. Dependency injection.

Repeating yourself
From the video: Repeating yourself
The same pagination parameters in every endpoint
The same “does this item exist?” check, copy-pasted
Open a database connection… and remember to close it
Dependency injectionFastAPI’s clean answer
Copy and paste is how bugs are born

What is a dependency?

A dependency is just a function. You add it to an endpoint with Depends, and FastAPI calls it for you, and passes in the result. You describe what an endpoint needs, and FastAPI provides it. That is the whole idea.

main.pypython
def pagination(skip: int = 0, limit: int = 2):
    return {"skip": skip, "limit": limit}

@app.get("/menu/")
def list_menu(page: dict = Depends(pagination)):
    ...

A reusable pagination dependency

Here is a dependency for pagination. It takes skip and limit as query parameters, and returns them in a dictionary. With Annotated, we give it a short name. Now any endpoint can ask for pagination in one line, and the docs show those parameters automatically.

main.pypython
def pagination(skip: int = 0, limit: int = 2):
    return {"skip": skip, "limit": limit}

PageDep = Annotated[dict, Depends(pagination)]

@app.get("/menu/")
def list_menu(page: PageDep):
    ...

One dependency, two endpoints

Both the menu and the orders endpoint use it. Skip and limit work in both places, and we wrote the logic only once.

terminal
curl -s 'localhost:8000/menu/?limit=2'
[{"name":"Chocolate Cake","price":18.0},{"name":"Sourdough Loaf","price":7.0}]
curl -s 'localhost:8000/orders/?skip=1&limit=1'
{"db_connected":true,"orders":[{"id":1002,"item":"Sourdough Loaf"}]}
Logic written once, used in both places

A dependency that checks for you

Dependencies can raise errors, too. This one looks up a menu item, and raises a four hundred and four, if it is missing. Endpoints that need an item simply ask for it, and never repeat the check. Item three is found. Item ninety nine returns our error.

main.pypython
def get_item_or_404(item_id: int):
    item = MENU.get(item_id)
    if item is None:
        raise HTTPException(status_code=404,
                            detail="Menu item not found")
    return item

@app.get("/menu/{item_id}")
def read_item(item: ItemDep):
    return item
terminal
curl -s localhost:8000/menu/3
{"name":"Croissant","price":4.0}
curl -s localhost:8000/menu/99
{"detail":"Menu item not found"}
Endpoints never repeat the check

yield: set up and clean up

A dependency with yield can also clean up. Our get db function opens a connection, and yields it to the endpoint. The code after yield runs when the request ends. Look at the server log. Opening, then the request, then closing, for every call that uses it.

main.pypython
def get_db():
    print("opening database connection")
    db = {"connected": True}
    try:
        yield db
    finally:
        print("closing database connection")
terminal
fastapi dev main.py
INFO:     127.0.0.1:52605 - "GET /menu/?limit=2 HTTP/1.1" 200 OK
opening database connection
INFO:     127.0.0.1:52607 - "GET /orders/?skip=1&limit=1 HTTP/1.1" 200 OK
closing database connection
INFO:     127.0.0.1:52609 - "GET /menu/3 HTTP/1.1" 200 OK
INFO:     127.0.0.1:52611 - "GET /menu/99 HTTP/1.1" 404 Not Found
Before yield: set up
After yield: runs when the request ends

The complete code for this lesson

Here is the full main.py exactly as it runs at the end of this lesson. Save it, install FastAPI with pip install "fastapi[standard]", and start it with fastapi dev main.py, then open http://127.0.0.1:8000/docs.

main.pypython
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI(title="Sweet Crumbs API")

MENU = {
    1: {"name": "Chocolate Cake", "price": 18.0},
    2: {"name": "Sourdough Loaf", "price": 7.0},
    3: {"name": "Croissant", "price": 4.0},
    4: {"name": "Flat White", "price": 5.0},
}
ORDERS = [
    {"id": 1001, "item": "Chocolate Cake"},
    {"id": 1002, "item": "Sourdough Loaf"},
    {"id": 1003, "item": "Croissant"},
]


def pagination(skip: int = 0, limit: int = 2):
    return {"skip": skip, "limit": limit}


PageDep = Annotated[dict, Depends(pagination)]


def get_item_or_404(item_id: int):
    item = MENU.get(item_id)
    if item is None:
        raise HTTPException(status_code=404, detail="Menu item not found")
    return item


ItemDep = Annotated[dict, Depends(get_item_or_404)]


def get_db():
    print("opening database connection")
    db = {"connected": True}
    try:
        yield db
    finally:
        print("closing database connection")


DbDep = Annotated[dict, Depends(get_db)]


@app.get("/menu/")
def list_menu(page: PageDep):
    items = list(MENU.values())
    return items[page["skip"] : page["skip"] + page["limit"]]


@app.get("/menu/{item_id}")
def read_item(item: ItemDep):
    return item


@app.get("/orders/")
def list_orders(page: PageDep, db: DbDep):
    start = page["skip"]
    return {"db_connected": db["connected"], "orders": ORDERS[start : start + page["limit"]]}

Summary

Dependencies keep your code dry, easy to test, and easy to change. Soon we will use them for the database session, and for login. And login is next. Maya needs to protect the staff only endpoints. In the next lesson, authentication. See you there.

Key takeaways
  • A dependency is just a function
  • Depends() injects the result
  • yield sets up and cleans up
Next up: Authentication — protect the staff-only endpoints