Fast Step by Step
FastAPI is a modern, fast (high-performance) web framework for building APIs with Python 3.6+ based on standard Python type hints. Here’s a comprehensive guide to get you started.
Installation
Section titled “Installation”python -m venv venvsource venv/bin/activatepip install fastapi uvicornBasic Setup
Section titled “Basic Setup”from fastapi import FastAPI
app = FastAPI()
@app.get("/")async def root(): return {"message": "Hello World"}
@app.get("/items/{item_id}")async def read_item(item_id: int): return {"item_id": item_id}Run the server:
uvicorn main:app --reloadPath Parameters
Section titled “Path Parameters”from enum import Enum
class ModelName(str, Enum): alexnet = "alexnet" resnet = "resnet" lenet = "lenet"
@app.get("/models/{model_name}")async def get_model(model_name: ModelName): if model_name == ModelName.alexnet: return {"model_name": model_name, "message": "Deep Learning FTW!"} if model_name.value == "lenet": return {"model_name": model_name, "message": "LeCNN all the images"} return {"model_name": model_name, "message": "Have some residuals"}
# Path parameters with paths@app.get("/files/{file_path:path}")async def read_file(file_path: str): return {"file_path": file_path}Query Parameters
Section titled “Query Parameters”from typing import Optional
@app.get("/items/")async def read_items(skip: int = 0, limit: int = 10, q: Optional[str] = None): items = [{"item_id": i} for i in range(skip, skip + limit)] if q: items = [item for item in items if q in str(item)] return itemsRequest Body
Section titled “Request Body”from pydantic import BaseModelfrom typing import Optional
class Item(BaseModel): name: str description: Optional[str] = None price: float tax: Optional[float] = None
@app.post("/items/")async def create_item(item: Item): item_dict = item.dict() if item.tax: price_with_tax = item.price + item.tax item_dict.update({"price_with_tax": price_with_tax}) return item_dict
# Multiple parameters@app.put("/items/{item_id}")async def update_item(item_id: int, item: Item, q: Optional[str] = None): result = {"item_id": item_id, **item.dict()} if q: result.update({"q": q}) return resultResponse Model
Section titled “Response Model”class UserOut(BaseModel): username: str email: str full_name: Optional[str] = None
class UserIn(BaseModel): username: str password: str email: str full_name: Optional[str] = None
@app.post("/user/", response_model=UserOut)async def create_user(user: UserIn): # Password would be hashed here return userError Handling
Section titled “Error Handling”from fastapi import HTTPException, statusfrom fastapi.responses import JSONResponse
@app.get("/items/{item_id}")async def read_item(item_id: int): if item_id not in items_db: raise HTTPException( status_code=404, detail="Item not found", headers={"X-Error": "There goes my error"}, ) return {"item": items_db[item_id]}
# Custom exception handlerfrom fastapi import Requestfrom fastapi.responses import JSONResponse
class UnicornException(Exception): def __init__(self, name: str): self.name = name
@app.exception_handler(UnicornException)async def unicorn_exception_handler(request: Request, exc: UnicornException): return JSONResponse( status_code=418, content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."}, )
@app.get("/unicorns/{name}")async def read_unicorn(name: str): if name == "yolo": raise UnicornException(name=name) return {"unicorn_name": name}Dependencies
Section titled “Dependencies”from fastapi import Depends, Header, HTTPException
async def common_parameters(q: Optional[str] = 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
# Class-based dependenciesclass CommonQueryParams: def __init__(self, q: Optional[str] = None, skip: int = 0, limit: int = 100): self.q = q self.skip = skip self.limit = limit
@app.get("/items/")async def read_items(commons: CommonQueryParams = Depends()): return commons
# Dependency with yield (for database connections)async def get_db(): db = DBSession() try: yield db finally: db.close()Database Integration
Section titled “Database Integration”SQLAlchemy Example
Section titled “SQLAlchemy Example”from sqlalchemy import create_engine, Column, Integer, Stringfrom sqlalchemy.ext.declarative import declarative_basefrom sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"engine = create_engine(SQLALCHEMY_DATABASE_URL)SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)Base = declarative_base()
class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) email = Column(String, unique=True, index=True) hashed_password = Column(String)
# Dependencydef get_db(): db = SessionLocal() try: yield db finally: db.close()
@app.post("/users/")async def create_user(user: UserCreate, db: Session = Depends(get_db)): db_user = User(email=user.email, hashed_password=fake_hash(user.password)) db.add(db_user) db.commit() db.refresh(db_user) return db_userAuthentication
Section titled “Authentication”from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestFormfrom jose import JWTError, jwtfrom passlib.context import CryptContext
SECRET_KEY = "your-secret-key"ALGORITHM = "HS256"
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def verify_password(plain_password, hashed_password): return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password): return pwd_context.hash(password)
def create_access_token(data: dict): to_encode = data.copy() encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)): credentials_exception = HTTPException( status_code=401, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise credentials_exception except JWTError: raise credentials_exception return username
@app.post("/token")async def login(form_data: OAuth2PasswordRequestForm = Depends()): # Verify user credentials user = authenticate_user(form_data.username, form_data.password) if not user: raise HTTPException( status_code=401, detail="Incorrect username or password", ) access_token = create_access_token(data={"sub": user.username}) return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me")async def read_users_me(current_user: str = Depends(get_current_user)): return {"username": current_user}Middleware
Section titled “Middleware”from fastapi.middleware.cors import CORSMiddlewareimport time
# CORS middlewareapp.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"],)
# Custom middleware@app.middleware("http")async def add_process_time_header(request: Request, call_next): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time response.headers["X-Process-Time"] = str(process_time) return responseTesting
Section titled “Testing”from fastapi.testclient import TestClientfrom main import app
client = TestClient(app)
def test_read_main(): response = client.get("/") assert response.status_code == 200 assert response.json() == {"message": "Hello World"}
def test_create_item(): response = client.post( "/items/", json={"name": "Foo", "price": 45.2}, ) assert response.status_code == 200 assert response.json() == { "name": "Foo", "price": 45.2, "description": None, "tax": None, }Deployment
Section titled “Deployment”Using Uvicorn
Section titled “Using Uvicorn”uvicorn main:app --host 0.0.0.0 --port 8000Using Gunicorn with Uvicorn workers
Section titled “Using Gunicorn with Uvicorn workers”pip install gunicorngunicorn -w 4 -k uvicorn.workers.UvicornWorker main:appDockerfile
Section titled “Dockerfile”FROM python:3.9
WORKDIR /code
COPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]Advanced Features
Section titled “Advanced Features”Background Tasks
Section titled “Background Tasks”from fastapi import BackgroundTasks
def write_notification(email: str, message=""): with open("log.txt", mode="w") as email_file: content = f"notification for {email}: {message}" email_file.write(content)
@app.post("/send-notification/{email}")async def send_notification(email: str, background_tasks: BackgroundTasks): background_tasks.add_task(write_notification, email, message="some notification") return {"message": "Notification sent in the background"}WebSockets
Section titled “WebSockets”from fastapi import WebSocket
@app.websocket("/ws")async def websocket_endpoint(websocket: WebSocket): await websocket.accept() while True: data = await websocket.receive_text() await websocket.send_text(f"Message text was: {data}")Static Files
Section titled “Static Files”from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="static"), name="static")