响应模型 response_model 等
响应状态码、Response、JSONResponse等
错误处理HTTPException
后台任务
响应模型
response_model
在装饰器方法(get,post 等)中的参数response_model
- 将输出数据转换为其声明的类型
- 校验数据,将输出数据限制在该模型定义内
- 在 OpenAPI 的路径操作中为响应添加一个 JSON Schema,并在自动生成文档系统中使用
from typing import List, Optional
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: List[str] = []
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
return item # 修改为 return {"item": item} 看看效果
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
from typing import Optional
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Optional[str] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Optional[str] = None
@app.post("/user/", response_model=UserOut) # response_model 声明为了不包含密码的 UserOut 模型
async def create_user(user: UserIn):
return user # 即使返回包含密码的相同输入的用户user,FastAPI 也会过滤掉未在输出模型中声明的所有数据
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
response_model_exclude_xx
- response_model_exclude_unset:设置为True,不返回未被设置的值
- response_model_exclude_defaults:设置为True,不返回包含默认值的值
- response_model_exclude_none:设置为True,不返回值为null的值
from typing import List, Optional
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float = 12.3
tax: Optional[float] = None
tags: List[str] = []
# 修改为esponse_model_exclude_unset、response_model_exclude_defaults看看效果
@app.post("/items/", response_model=Item, response_model_exclude_none=True)
async def create_item(item: Item):
return item
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
使用postman访问
127.0.0.1:8000/items
发送{"name":"jerry"}
看看效果
response_model_include 和 response_model_exclude
- 它们接收一个由属性名称 str 组成的 set(如果使用 list 或 tuple仍然有效(FastAPI 仍会将其转换为 set)),返回的数据将包含(include)或排除(exclude) set 中的这些属性
from typing import Optional
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Optional[str] = None
# 一定要同时声明响应模型response_model才生效
@app.post("/user/", response_model=UserIn, response_model_exclude={"password"})
async def create_user(user: UserIn):
return user
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
定义模型基类减少重复代码
from typing import Optional
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: Optional[str] = None
class UserIn(UserBase):
password: str
class UserOut(UserBase):
pass
class UserInDB(UserBase):
hashed_password: str
# 模拟密码hash
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
# 密码hash后组装成UserInDB
def fake_save_user(user_in: UserIn):
hashed_password = fake_password_hasher(user_in.password)
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
print("User saved! ..not really")
return user_in_db
@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in)
return user_saved
if __name__ == '__main__':
# user_in = UserIn(username="jerry", password="123458", email="jerry@monkey.com")
# user_in_db = fake_save_user(user_in)
# print(user_in_db, type(user_in_db))
uvicorn.run("main:app", reload=True)
响应
我们知道 FastAPI 默认使用 JSONResponse 返回响应(使用jsonable_encoder将这些类型的返回值转换成 JSON 格式)
- 可以使用 jsonable_encoder 将数据转换为兼容 JSON 的类型
响应状态码
- 状态码:一个表示 HTTP 状态码的数字如200、301、404等,也可使用 fastapi.status 的便捷变量如HTTP_200_OK、HTTP_301_MOVED_PERMANENTLY、HTTP_404_NOT_FOUND等
- 在装饰器方法@app.get()、@app.post()等中的参数status_code
- 响应Response类中接收的参数status_code,如
return Response(status_code=201)
- 在路径操作中声明 Response 类型的参数如 response,然后在代码逻辑中设置status_code,如
response.status_code=status.HTTP_201_CREATED
路径操作中声明的
response: Response
还可以用来添加/设置 response headers(如response.headers["X-Token"] = "screct token")和 response cookies(response.set_cookie)
Response
- 全部的响应都继承自主类 Response 如 JSONResponse、HTMLResponse、PlainTextResponse、RedirectResponse、StreamingResponse、FileResponse、UJSONResponse及性能更好的ORJSONResponse等
- 可以返回任意 Response 、任意 Response 的子类或一个自定义的 Response 子类
- Response 类接受如下参数:
- content:str 或者 bytes
- status_code :int 类型的 HTTP 状态码
- headers:字符串组成的 dict,可添加自定义response headers如 X-Token
- media_type:媒体类型的 str,比如 "text/html"
import uvicorn
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/legacy/")
def get_legacy_data():
data = """<?xml version="1.0"?>
<shampoo>
<Header>
Apply shampoo here.
</Header>
<Body>
You'll have to use soap here.
</Body>
</shampoo>
"""
return Response(content=data, media_type="application/xml", status_code=201, headers={"x-token": "jerry"})
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
HTMLResponse 和 PlainTextResponse
- HTMLResponse:接受文本或字节并返回 HTML 响应
- PlainTextResponse:接受文本或字节并返回纯文本响应
- 方式1:直接返回Response类/子类的实例
- 方式2:将Response类/子类作为路径操作的response_class参数传入(参数response_class来定义响应的媒体类型)
import uvicorn
from fastapi import FastAPI
from fastapi.responses import HTMLResponse, PlainTextResponse
app = FastAPI()
# 方式1
@app.get("/items/")
async def read_items():
html_content = """
<html>
<head>
<title>Some HTML in here</title>
</head>
<body>
<h1>Look ma! HTML!</h1>
</body>
</html>
"""
return HTMLResponse(content=html_content, status_code=200)
# 方式2
@app.get("/", response_class=PlainTextResponse)
async def main():
return "Hello World"
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
RedirectResponse
- 返回 HTTP 重定向。默认情况下使用 307 状态代码(临时重定向)
import uvicorn
from fastapi import FastAPI
from fastapi.responses import RedirectResponse
app = FastAPI()
@app.get("/typer")
async def redirect_typer():
return RedirectResponse("https://baidu.com") # 接收重定向的url参数
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
StreamingResponse 和 FileResponse
- StreamingResponse:采用异步生成器或普通生成器/迭代器,然后流式传输响应主体,例子及更多细节见官方文档
- FileResponse:异步传输文件作为响应
import uvicorn
from fastapi import FastAPI
from fastapi.responses import FileResponse
some_file_path = "/Users/xmly/Desktop/jerry.mp4" # large-video-file path
app = FastAPI()
@app.get("/")
async def main():
return FileResponse(some_file_path)
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
错误处理
- 向客户端返回 HTTP 错误响应,可以raise HTTPException
- 添加自定义响应头:有些场景下要为 HTTP 错误添加自定义响应头,在HTTPException 中的 参数headers中设置
import uvicorn
from fastapi import FastAPI, HTTPException
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
@app.get('/items/{item_id}')
async def read_items(item_id: str):
if item_id not in items:
raise HTTPException(status_code=404, detail="item not found", headers={"X-Error": "There goes my error"})
return {"item": items[item_id]}
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
detail参数类型是tying.Any,也就是说detail不仅限于 str,还支持传递 dict、list 等数据结构
自定义异常处理器
- 使用
@app.exception_handler()
添加自定义异常控制器
import uvicorn
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
items = {"foo": "The Foo Wrestlers"}
class MyException(Exception):
def __init__(self, name: str):
self.name = name
@app.exception_handler(MyException)
async def my_exception_handler(request: Request, exc: MyException):
return JSONResponse(
status_code=418,
content={"message": f"Oops! {exc.name} not found"},
)
@app.get('/items/{item_id}')
async def read_items(item_id: str):
if item_id not in items:
raise MyException(item_id)
return {"item": items[item_id]}
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
覆盖默认异常处理器
- 覆盖默认异常处理器时需要导入 RequestValidationError,并用
@app.excption_handler(RequestValidationError)
装饰异常处理器 - 相关例子见官方文档
后台任务
- 不需要等待任务完成才收到响应,使用BackgroundTasks
- BackgroundTasks.add_task
import time
import uvicorn
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
def bg_task(t):
time.sleep(t)
print("bg task done")
@app.get('/items/{item_id}')
async def read_items(item_id: str, background_tasks: BackgroundTasks):
background_tasks.add_task(bg_task, t=10)
return {"item": item_id}
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
- 在依赖注入系统使用后台任务可参看依赖注入章节