When it comes to building APIs, having clean and modular code is key to making sure your project can scale and be easily maintained. FastAPI, the modern Python web framework everyone’s talking about, has got you covered with this awesome feature called dependency injection. This thing is a game-changer, allowing you to keep your code neat and testable.
So, what exactly is dependency injection? Simply put, it’s a design pattern where an object gets its dependencies rather than creating them itself. In FastAPI, this means you can declare what your path operation functions need to work and let FastAPI handle supplying those needs.
Let’s dive into a basic example to see how this magic works in FastAPI. Say you’ve got multiple routes that need to deal with common query parameters like q
, skip
, and limit
. Normally, you’d end up repeating the same code over and over in each route handler. Super annoying, right? Well, check this out:
from fastapi import FastAPI, Depends
from typing import Union
app = FastAPI()
async def common_parameters(q: Union[str, None] = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
With this setup, the common_parameters
function returns a dictionary of query parameters. By throwing Depends(common_parameters)
into your route handlers, FastAPI injects the function’s result into read_items
and read_users
. Voila! You write the shared logic once and FastAPI handles the rest.
Now, what about dealing with database connections? Here’s where dependency injection really pulls its weight. You can create a dependency function that sets up and tears down database connections, keeping everything neat and ensuring good resource management.
from fastapi import FastAPI, Depends
from sqlalchemy.orm import sessionmaker
app = FastAPI()
# Let’s assume you’ve got a SessionLocal class for your database sessions
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/whoami")
async def who_am_i(db=Depends(get_db)):
# Use the db session here
user = db.query(User).first()
return {"user": user.id}
In this example, get_db
sets up a database session and then closes it after the request is done. This way, your route handlers don’t get bogged down with database connection details—they just focus on handling requests.
But wait, there’s more! Dependency injection is super valuable for handling security and authentication. You can build dependencies to check for valid API keys or authenticate users before they can access certain routes.
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
app = FastAPI()
# Define a security scheme
security_scheme = HTTPBearer()
async def get_current_user(db=Depends(get_db), token: HTTPAuthorizationCredentials=Depends(security_scheme)):
if not token:
raise HTTPException(status_code=401)
# Validate the token and get the user
user = db.query(User).filter(User.api_key == token.credentials).first()
if not user:
raise HTTPException(status_code=401)
return user
@app.get("/protected")
async def protected_route(user=Depends(get_current_user)):
return {"message": f"Hello, {user.name}!"}
The get_current_user
function checks for a valid API key in the Authorization
header and fetches the user from the database. If the key’s bogus or missing, it throws an HTTP exception. This ensures only authenticated users can hit the protected routes.
One of the coolest things about dependency injection is sharing logic across multiple routes. This means you cut down on redundant code and make your app more maintainable.
from fastapi import FastAPI, Depends
app = FastAPI()
async def validate_age(age: int):
if age < 18:
raise HTTPException(status_code=400, detail="You are not eligible")
return age
@app.get("/user/")
async def user(age: int = Depends(validate_age)):
return {"message": "You are eligible"}
@app.get("/admin/")
async def admin(age: int = Depends(validate_age)):
return {"message": "You are eligible"}
Here, validate_age
checks if the provided age is valid. Both /user/
and /admin/
use this dependency to make sure only users with a proper age access these endpoints.
Okay, but what if you need something a bit more complex? You can totally use classes for your dependencies too.
from fastapi import FastAPI, Depends
app = FastAPI()
class DependencyClass:
def __init__(self, id: str, name: str, age: int):
self.id = id
self.name = name
self.age = age
def validate(self):
if self.age < 18:
raise HTTPException(status_code=400, detail="You are not eligible")
@app.get("/user/")
async def user(dep=Depends(DependencyClass)):
dep.validate()
return {"id": dep.id, "name": dep.name, "age": dep.age}
@app.get("/admin/")
async def admin(dep=Depends(DependencyClass)):
dep.validate()
return {"id": dep.id, "name": dep.name, "age": dep.age}
In this scenario, DependencyClass
manages the validation logic and gets used as a dependency for both routes. Handy, right?
And here’s a cherry on top: FastAPI supports caching dependencies to boost performance. By default, if a dependency is hit multiple times in the same request, FastAPI will reuse the result from the first call.
from fastapi import FastAPI, Depends
app = FastAPI()
async def expensive_dependency():
# Simulate an expensive operation
import time
time.sleep(2)
return "Result"
@app.get("/items/")
async def read_items(result: str = Depends(expensive_dependency)):
return {"result": result}
@app.get("/users/")
async def read_users(result: str = Depends(expensive_dependency)):
return {"result": result}
Even though expensive_dependency
is called twice in the same request, it’ll only get executed once. The result is reused for both routes, which is a huge performance win.
So, to wrap it up, dependency injection in FastAPI is one powerful tool. It helps keep your code clean, modular, and scalable. With this feature, you can share logic across multiple routes, manage database connections like a pro, enforce security, and improve performance. It’s a no-brainer for anyone looking to maintain organized and testable code, whether you’re juggling simple query parameters or dealing with complex authentication logic.