库简介
FastAPI是一个现代、快速(高性能)的Web框架,用于构建API。它基于Python 3.6+的类型提示,提供了自动生成API文档、数据验证、序列化和异步支持等强大功能。FastAPI的设计目标是提供最佳的开发体验,同时保持高性能,其性能可与NodeJS和Go相媲美。
安装方法
pip install fastapi uvicorn
入门示例
基础API示例
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: bool = None
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
@app.post("/items/")
def create_item(item: Item):
return item
运行应用
# 在命令行中运行
uvicorn main:app --reload
进阶实战
用户认证系统
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
# 安全配置
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
app = FastAPI()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 用户模型
class User(BaseModel):
username: str
email: str = None
full_name: str = None
disabled: bool = None
class UserInDB(User):
hashed_password: str
# 模拟数据库
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", # "secret"
"disabled": False,
}
}
# 认证函数
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 get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# 这里应该从数据库验证用户
user_dict = fake_users_db.get(form_data.username)
if not user_dict:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
user = UserInDB(**user_dict)
if not verify_password(form_data.password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
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
user = fake_users_db.get(username)
if user is None:
raise credentials_exception
return User(**user)
完整的博客API系统
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
from typing import List, Optional
import uvicorn
# 安全配置
SECRET_KEY = "your-secret-key-here-change-in-production"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 模拟数据库
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe@example.com",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW", # "secret"
"disabled": False,
}
}
# 数据模型
class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None
class UserInDB(User):
hashed_password: str
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
class PostCreate(BaseModel):
title: str
content: str
class Post(PostCreate):
id: int
author: str
created_at: datetime
# 应用实例
app = FastAPI(title="Blog API", version="1.0.0")
# 安全工具
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# 模拟帖子存储
posts_db = []
post_id_counter = 1
# 工具函数
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 get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
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=status.HTTP_401_UNAUTHORIZED,
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
token_data = TokenData(username=username)
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=token_data.username)
if user is None:
raise credentials_exception
return user
async def get_current_active_user(current_user: User = Depends(get_current_user)):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
# API端点
@app.post("/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user
@app.post("/users/", response_model=User)
async def create_user(user: User):
if user.username in fake_users_db:
raise HTTPException(status_code=400, detail="Username already registered")
hashed_password = get_password_hash("default_password") # 实际应用中应从请求中获取密码
db_user = UserInDB(
username=user.username,
email=user.email,
full_name=user.full_name,
hashed_password=hashed_password,
disabled=False
)
fake_users_db[user.username] = db_user.dict()
return user
# 博客帖子相关端点
@app.post("/posts/", response_model=Post)
async def create_post(
post: PostCreate,
current_user: User = Depends(get_current_active_user)
):
global post_id_counter, posts_db
new_post = Post(
id=post_id_counter,
title=post.title,
content=post.content,
author=current_user.username,
created_at=datetime.utcnow()
)
posts_db.append(new_post)
post_id_counter += 1
return new_post
@app.get("/posts/", response_model=List[Post])
async def read_posts(skip: int = 0, limit: int = 10):
return posts_db[skip:skip + limit]
@app.get("/posts/{post_id}", response_model=Post)
async def read_post(post_id: int):
for post in posts_db:
if post.id == post_id:
return post
raise HTTPException(status_code=404, detail="Post not found")
@app.put("/posts/{post_id}", response_model=Post)
async def update_post(
post_id: int,
post_update: PostCreate,
current_user: User = Depends(get_current_active_user)
):
for i, post in enumerate(posts_db):
if post.id == post_id:
if post.author != current_user.username:
raise HTTPException(status_code=403, detail="Not authorized to update this post")
updated_post = Post(
id=post_id,
title=post_update.title,
content=post_update.content,
author=post.author,
created_at=post.created_at
)
posts_db[i] = updated_post
return updated_post
raise HTTPException(status_code=404, detail="Post not found")
@app.delete("/posts/{post_id}")
async def delete_post(
post_id: int,
current_user: User = Depends(get_current_active_user)
):
for i, post in enumerate(posts_db):
if post.id == post_id:
if post.author != current_user.username:
raise HTTPException(status_code=403, detail="Not authorized to delete this post")
del posts_db[i]
return {"message": "Post deleted successfully"}
raise HTTPException(status_code=404, detail="Post not found")
# 运行应用
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
高级功能
WebSocket支持
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import List
app = FastAPI()
class ConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
await manager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
await manager.send_personal_message(f"你说了: {data}", websocket)
await manager.broadcast(f"客户端#{client_id}说: {data}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"客户端#{client_id}离开了聊天室")
依赖注入系统
from fastapi import FastAPI, Depends, Header, HTTPException
app = FastAPI()
# 简单的依赖
async def common_parameters(q: 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
# 带验证的依赖
async def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
return x_token
@app.get("/secure-items/", dependencies=[Depends(verify_token)])
async def read_secure_items():
return [{"item": "Foo"}, {"item": "Bar"}]
# 类作为依赖
class Pagination:
def __init__(self, skip: int = 0, limit: int = 100):
self.skip = skip
self.limit = limit
@app.get("/users/")
async def read_users(pagination: Pagination = Depends()):
return {"skip": pagination.skip, "limit": pagination.limit}
中间件支持
from fastapi import FastAPI, Request
import time
app = FastAPI()
@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 response
@app.middleware("http")
async def log_requests(request: Request, call_next):
print(f"收到请求: {request.method} {request.url}")
try:
response = await call_next(request)
print(f"请求完成: {response.status_code}")
return response
except Exception as e:
print(f"请求出错: {e}")
raise
最佳实践
1. 项目结构组织
myproject/
├── app/
│ ├── __init__.py
│ ├── main.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── endpoints/
│ │ │ ├── __init__.py
│ │ │ ├── users.py
│ │ │ └── posts.py
│ │ └── dependencies.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── config.py
│ │ └── security.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── post.py
│ └── schemas/
│ ├── __init__.py
│ ├── user.py
│ └── post.py
├── tests/
│ ├── __init__.py
│ ├── test_users.py
│ └── test_posts.py
├── requirements.txt
└── README.md
2. 配置管理
# app/core/config.py
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str = "My FastAPI App"
secret_key: str
algorithm: str = "HS256"
access_token_exp