Mathew K Analytics

Lesson 7 · FastAPI deep dive

FastAPI Tutorial #7: Dependency Injection with Depends()

Video seven of the eighteen-part series: sharing logic across endpoints the FastAPI way. Depends, dependencies with parameters, yield-based setup and…

⬇ Download notebookOpen in Colab ↗

📓 Full notebook

Download .ipynb

FastAPI Deep-Dive, Video 7: Dependency Injection Basics#

  • Video seven of the eighteen-part series: sharing logic across endpoints the FastAPI way.
  • Depends, dependencies with parameters, yield-based setup and teardown, and overriding for tests.
  • Let's get into it.

Part 1: The Problem - Repeated Logic Across Endpoints#

from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.get('/items')
def list_items(skip: int = 0, limit: int = 10):
    return {'skip': skip, 'limit': limit, 'source': 'items'}
@app.get('/orders')
def list_orders(skip: int = 0, limit: int = 10):
    return {'skip': skip, 'limit': limit, 'source': 'orders'}
client = TestClient(app)
print(client.get('/items?skip=5&limit=20').json())
{'skip': 5, 'limit': 20, 'source': 'items'}

Part 2: Depends() - a Simple Function Dependency#

from fastapi import Depends
def pagination(skip: int = 0, limit: int = 10):
    return {'skip': skip, 'limit': limit}
@app.get('/items-v2')
def list_items_v2(page: dict = Depends(pagination)):
    return {**page, 'source': 'items'}
@app.get('/orders-v2')
def list_orders_v2(page: dict = Depends(pagination)):
    return {**page, 'source': 'orders'}
print(client.get('/items-v2?skip=5&limit=20').json())
print(client.get('/orders-v2').json())
{'skip': 5, 'limit': 20, 'source': 'items'}
{'skip': 0, 'limit': 10, 'source': 'orders'}

Part 3: Dependencies Can Use Query, Path, and Header Just Like Handlers#

from fastapi import Query
def search_params(q: str = Query(default='', min_length=0, max_length=50), active_only: bool = True):
    return {'q': q, 'active_only': active_only}
@app.get('/search-items')
def search_items(params: dict = Depends(search_params)):
    return params
print(client.get('/search-items?q=mug&active_only=false').json())
print(client.get('/search-items').json())
{'q': 'mug', 'active_only': False}
{'q': '', 'active_only': True}

Part 4: Multiple Dependencies on One Endpoint#

def get_token(x_token: str = 'anonymous'):
    return x_token
@app.get('/dashboard')
def dashboard(page: dict = Depends(pagination), token: str = Depends(get_token)):
    return {'page': page, 'token': token}
print(client.get('/dashboard?skip=2&limit=5').json())
{'page': {'skip': 2, 'limit': 5}, 'token': 'anonymous'}

Part 5: Depends() with yield - Setup and Teardown#

events = []
def fake_db_session():
    events.append('open')
    try:
        yield {'connected': True}
    finally:
        events.append('close')
@app.get('/records')
def list_records(db: dict = Depends(fake_db_session)):
    events.append('use')
    return db
print(client.get('/records').json())
print(events)
{'connected': True}
['open', 'use', 'close']

Part 6: Router-Wide and App-Wide Dependencies#

from fastapi import HTTPException, Header
checked = []
def verify_access(x_token: str = Header(default='anonymous')):
    checked.append(x_token)
    if x_token != 'secret':
        raise HTTPException(status_code=403, detail='forbidden')
@app.get('/admin/stats', dependencies=[Depends(verify_access)])
def admin_stats():
    return {'users': 42}
print(client.get('/admin/stats', headers={'x-token': 'secret'}).json())
print(client.get('/admin/stats').status_code)
print(checked)
{'users': 42}
403
['secret', 'anonymous']

Part 7: Dependencies Are Cached Per Request#

call_count = {'n': 0}
def counted_dependency():
    call_count['n'] += 1
    return call_count['n']
@app.get('/twice')
def uses_twice(a: int = Depends(counted_dependency), b: int = Depends(counted_dependency)):
    return {'a': a, 'b': b, 'total_calls': call_count['n']}
print(client.get('/twice').json())
{'a': 1, 'b': 1, 'total_calls': 1}

Part 8: Overriding Dependencies for Testing#

def real_greeting():
    return 'hello from the real dependency'
@app.get('/greet')
def greet(message: str = Depends(real_greeting)):
    return {'message': message}
print(client.get('/greet').json())
def fake_greeting():
    return 'hello from the fake test dependency'
app.dependency_overrides[real_greeting] = fake_greeting
print(client.get('/greet').json())
app.dependency_overrides = {}
{'message': 'hello from the real dependency'}
{'message': 'hello from the fake test dependency'}

Part 9: Sub-Dependencies - a Dependency Depending on Another#

def get_raw_token(x_token: str = Header(default='anonymous')):
    return x_token
def get_current_user(token: str = Depends(get_raw_token)):
    if token == 'secret':
        return {'username': 'admin'}
    return {'username': 'guest'}
@app.get('/profile')
def profile(user: dict = Depends(get_current_user)):
    return user
print(client.get('/profile', headers={'x-token': 'secret'}).json())
print(client.get('/profile').json())
{'username': 'admin'}
{'username': 'guest'}

Part 10: A Real Pattern - Pagination Plus Resource Lookup#

catalog_app = FastAPI()
PRODUCTS = [{'id': 1, 'name': 'Mug'}, {'id': 2, 'name': 'Plate'}, {'id': 3, 'name': 'Cup'}]
def pagination_params(skip: int = 0, limit: int = 10):
    return {'skip': skip, 'limit': limit}
def get_product_or_404(product_id: int):
    for product in PRODUCTS:
        if product['id'] == product_id:
            return product
    raise HTTPException(status_code=404, detail='product not found')
@catalog_app.get('/catalog')
def browse_catalog(page: dict = Depends(pagination_params)):
    items = PRODUCTS[page['skip']: page['skip'] + page['limit']]
    return {'items': items, 'page': page}
@catalog_app.get('/catalog/{product_id}')
def get_catalog_item(product: dict = Depends(get_product_or_404)):
    return product
catalog_client = TestClient(catalog_app)
print(catalog_client.get('/catalog?limit=2').json())
print(catalog_client.get('/catalog/2').json())
print(catalog_client.get('/catalog/999').status_code)
{'items': [{'id': 1, 'name': 'Mug'}, {'id': 2, 'name': 'Plate'}], 'page': {'skip': 0, 'limit': 2}}
{'id': 2, 'name': 'Plate'}
404

Wrap-Up: What You Learned#

  • Depends lets a plain function be declared once and reused across as many endpoints as needed.
  • Dependencies can use Query, Path, and Header helpers, keeping constraints in one shared place.
  • A single endpoint can declare multiple independent Depends parameters at once.
  • A yield-based dependency runs setup before the yield and teardown after, once per request.
  • dependencies=[Depends(...)] runs a check for its side effects without injecting a value.
  • The same dependency used more than once in a request is only called once, and cached.
  • app.dependency_overrides swaps a dependency for tests without touching endpoint code.
  • Dependencies can depend on other dependencies, forming a resolved chain automatically.
  • Pagination and resource-lookup dependencies are a common, reusable production pattern.
  • That wraps up dependency injection basics. Next up: Class-based and Sub-Dependencies.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.