Lesson 8 · FastAPI deep dive
FastAPI Tutorial #8: Class-Based & Sub-Dependencies
Video eight of the eighteen-part series: dependencies that carry their own configuration. Callable classes as dependencies, Depends(Class) shorthand, bound…
- CourseFastAPI deep dive
- Lesson8 of 12
- Video17 min
- FormatJupyter notebook · 10 code cells
What you'll learn
- The Problem - Functions Can't Easily Carry Configuration
- A Callable Class as a Dependency
- Different Instances, Different Configuration
- Depends(Class) - Letting FastAPI Instantiate It
- The Shorthand - Depends() With No Argument
- A Bound Method as a Dependency
- A Class Dependency With Its Own Sub-Dependency
- Overriding a Class Dependency for Testing
Data
No separate download needed — the notebook creates or downloads everything it uses.
📓 Full notebook
Download .ipynbFastAPI Deep-Dive, Video 8: Class-based and Sub-Dependencies#
- Video eight of the eighteen-part series: dependencies that carry their own configuration.
- Callable classes as dependencies, Depends(Class) shorthand, bound methods, and sub-dependency chains.
- Let's get into it.
Part 1: The Problem - Functions Can't Easily Carry Configuration#
from fastapi import FastAPI, Depends
from fastapi.testclient import TestClient
app = FastAPI()
def pagination(skip: int = 0, limit: int = 10):
return {'skip': skip, 'limit': limit}
@app.get('/items')
def list_items(page: dict = Depends(pagination)):
return page
client = TestClient(app)
print(client.get('/items?limit=500').json())
Part 2: A Callable Class as a Dependency#
class Paginator:
def __init__(self, max_limit: int = 100):
self.max_limit = max_limit
def __call__(self, skip: int = 0, limit: int = 10):
return {'skip': skip, 'limit': min(limit, self.max_limit)}
strict_paginator = Paginator(max_limit=20)
@app.get('/items-v2')
def list_items_v2(page: dict = Depends(strict_paginator)):
return page
print(client.get('/items-v2?limit=500').json())
Part 3: Different Instances, Different Configuration#
loose_paginator = Paginator(max_limit=1000)
@app.get('/items-v3')
def list_items_v3(page: dict = Depends(loose_paginator)):
return page
print(client.get('/items-v3?limit=500').json())
print(client.get('/items-v2?limit=500').json())
Part 4: Depends(Class) - Letting FastAPI Instantiate It#
class CommonParams:
def __init__(self, q: str = '', skip: int = 0, limit: int = 10):
self.q = q
self.skip = skip
self.limit = limit
@app.get('/search')
def search(params: CommonParams = Depends(CommonParams)):
return {'q': params.q, 'skip': params.skip, 'limit': params.limit}
print(client.get('/search?q=mug&limit=5').json())
Part 5: The Shorthand - Depends() With No Argument#
@app.get('/search-v2')
def search_v2(params: CommonParams = Depends()):
return {'q': params.q, 'skip': params.skip, 'limit': params.limit}
print(client.get('/search-v2?q=plate').json())
Part 6: A Bound Method as a Dependency#
class FeatureFlags:
def __init__(self, enabled_features):
self.enabled_features = enabled_features
def check(self, feature: str = 'beta'):
return {'feature': feature, 'enabled': feature in self.enabled_features}
flags = FeatureFlags(enabled_features={'beta', 'dark_mode'})
@app.get('/flags')
def flags_status(status: dict = Depends(flags.check)):
return status
print(client.get('/flags?feature=dark_mode').json())
print(client.get('/flags?feature=missing').json())
Part 7: A Class Dependency With Its Own Sub-Dependency#
def get_request_id(x_request_id: str = 'unset'):
return x_request_id
class AuditLogger:
def __call__(self, request_id: str = Depends(get_request_id)):
return {'logged_request_id': request_id}
@app.get('/audited')
def audited_endpoint(audit: dict = Depends(AuditLogger())):
return audit
print(client.get('/audited').json())
Part 8: Overriding a Class Dependency for Testing#
class RealRateLimiter:
def __call__(self):
return {'limited': True, 'source': 'real'}
real_limiter = RealRateLimiter()
@app.get('/limited')
def limited_endpoint(status: dict = Depends(real_limiter)):
return status
print(client.get('/limited').json())
def fake_rate_limiter():
return {'limited': False, 'source': 'fake'}
app.dependency_overrides[real_limiter] = fake_rate_limiter
print(client.get('/limited').json())
app.dependency_overrides = {}
Part 9: Class Dependencies With yield#
class ManagedResource:
def __init__(self, name: str):
self.name = name
self.events = []
def __call__(self):
self.events.append(f'{self.name}-open')
try:
yield {'resource': self.name}
finally:
self.events.append(f'{self.name}-close')
cache_resource = ManagedResource('cache')
@app.get('/cache-check')
def cache_check(resource: dict = Depends(cache_resource)):
return resource
print(client.get('/cache-check').json())
print(cache_resource.events)
Part 10: A Real Pattern - a Configurable Rate Limiter Plus Lookup#
api_app = FastAPI()
class MaxCallsLimiter:
def __init__(self, max_calls: int):
self.max_calls = max_calls
self.calls = 0
def __call__(self):
self.calls += 1
if self.calls > self.max_calls:
raise HTTPException(status_code=429, detail='rate limit exceeded')
return {'calls_so_far': self.calls}
from fastapi import HTTPException
cheap_limiter = MaxCallsLimiter(max_calls=2)
PRODUCTS = {1: 'Mug', 2: 'Plate'}
def get_product_name(product_id: int):
if product_id not in PRODUCTS:
raise HTTPException(status_code=404, detail='not found')
return PRODUCTS[product_id]
@api_app.get('/limited-products/{product_id}')
def limited_lookup(name: str = Depends(get_product_name), usage: dict = Depends(cheap_limiter)):
return {'name': name, 'usage': usage}
api_client = TestClient(api_app)
print(api_client.get('/limited-products/1').json())
print(api_client.get('/limited-products/2').json())
print(api_client.get('/limited-products/1').status_code)
Wrap-Up: What You Learned#
- A callable class with call works as a dependency, letting init hold configuration.
- Different instances of the same class can carry different configuration independently.
- Depends(Class) lets FastAPI instantiate the class itself, treating init like a handler.
- Depends() with no argument infers the class from the parameter's own type annotation.
- A bound method on an already-configured instance works as a dependency too.
- A class dependency can declare its own Depends sub-dependencies, just like a function.
- dependency_overrides works the same way for class-based dependencies as for functions.
- A class dependency's call supports yield for setup and teardown, scoped to the instance.
- Class-based and function-based dependencies combine freely on the same endpoint.
- That wraps up class-based and sub-dependencies. Next up: APIRouter - Organizing Routes.
Found this useful?
All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.



