解析请求
前言
FastAPI 对各类请求参数的解析:路径参数、查询参数、请求体(JSON)、表单、文件上传、请求头、Cookie,以及直接使用 Request 对象。各参数类型的校验器(Path / Query / Form / Body / Header / Cookie)详细参数见 参数校验。
Request 对象
当需要直接解析原始请求报文时,可以把 Request 对象显式声明到视图函数参数上:
from fastapi import Request
@router.get("/reqobj", summary="获取request对象")
async def get_req_obj(request: Request):
form_data = await request.form() # form() 是协程函数
body_data = await request.body() # body() 是协程函数
return {
"url": request.url,
"base_url": request.base_url,
"method": request.method,
"headers": dict(request.headers),
"cookies": dict(request.cookies),
"form_data": dict(form_data),
"body_data": body_data,
"client_host": request.client.host if request.client else None,
"json_data": await request.json() if body_data else None, # json() 是协程函数
}
注意
await request.form()、await request.body()、await request.json() 都是协程方法,且请求体只能读取一次,读取后再次读取为空。
解析路径参数
@app.get("/items/{item_id}")
async def read_item(item_id):
return {"item_id": item_id}
声明路径参数的类型后,FastAPI 会自动校验并转换:
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
如果同时存在静态路由 /items/item_id 和动态路由 /items/{item_id},访问 /items/item_id 时按照先定义者优先的顺序匹配,所以一般把静态路由定义在前面。
含有文件路径的路径参数
如果路径参数是一段文件路径,需要将参数类型声明为 path:
@app.get("/{filepath:path}")
async def filepath_param(filepath: str):
return {"filepath": filepath}
枚举路径参数
路径参数有几个预设值时,可以用枚举类限制:
from enum import Enum
class CityEnum(str, Enum):
beijing = "beijing"
shanghai = "shanghai"
guangzhou = "guangzhou"
shenzhen = "shenzhen"
@router.get("/city/{cityname}", summary="枚举城市名称")
async def get_city_info(cityname: CityEnum):
if cityname == CityEnum.beijing:
return {"city": "北京", "country": "中国"}
if cityname == CityEnum.shanghai:
return {"city": "上海", "country": "中国"}
if cityname == CityEnum.guangzhou:
return {"city": "广州", "country": "中国"}
return {"city": "深圳", "country": "中国"}
解析查询参数
声明查询参数并指定默认值;不指定默认值即为必填:
from fastapi import FastAPI
app = FastAPI()
fake_items_db = [
{"item_name": "Foo"},
{"item_name": "Bar"},
{"item_name": "Baz"},
]
@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
return fake_items_db[skip : skip + limit]
请求示例:
curl 'http://127.0.0.1:8000/items/?skip=0&limit=10'
可选参数:
@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str | None = None):
if q:
return {"item_id": item_id, "q": q}
return {"item_id": item_id}
解析 JSON 请求体参数
这里用路由组组织代码。目录结构:
myfastapi/
├── main.py
├── models/
│ ├── __init__.py
│ └── reqresp.py
└── routes/
├── __init__.py
└── jsonbody.py
myfastapi/main.py:
from fastapi import FastAPI
app = FastAPI(
title="Hello FastAPI",
description="Quickstart of FastAPI",
version="0.1.0",
)
from routes import jsonbody
app.include_router(jsonbody.router)
简单键值请求体
请求体示例:
{
"username": "string",
"password": "string",
"age": 0,
"address": "string"
}
models/reqresp.py:
from pydantic import BaseModel
class ReqBody1(BaseModel):
username: str
password: str
age: int
address: str | None = None
routes/jsonbody.py:
from fastapi import APIRouter
from starlette.responses import JSONResponse
from models import reqresp
router = APIRouter(prefix="/jsonbody", tags=["解析json请求体"])
@router.post("/test1", summary="测试解析json请求1")
def jtest1(req: reqresp.ReqBody1):
return JSONResponse(
{
"code": 200,
"data": {
"username": req.username,
"age": req.age,
},
}
)
也可以使用 fastapi.Body 类(单字段级别的请求体声明):
from fastapi import Body
@router.post("/test2", summary="测试解析json请求2")
def jtest2(
username: str = Body(...), # 必填
password: str = Body(...), # 必填
age: int = Body(..., ge=0), # 大于等于 0
address: str | None = Body(None), # 可选字段
):
return {"username": username, "age": age}
二层嵌套
请求体示例:
{
"user": {
"username": "string",
"sex": "string",
"age": 0
},
"job": {
"name": "string",
"job_no": 0,
"dept_no": 0
},
"company": "string"
}
models/reqresp.py:
class ReqBody3_User(BaseModel):
username: str
sex: str
age: int
class ReqBody3_Job(BaseModel):
name: str
job_no: int
dept_no: int
class ReqBody3(BaseModel):
user: ReqBody3_User
job: ReqBody3_Job
company: str
routes/jsonbody.py:
@router.post("/test3", summary="测试解析json请求3")
def jtest3(req: reqresp.ReqBody3):
return req