10.FastAPI响应体

FastAPI支持声明响应体模型进行响应数据的处理。 可以在任意路由操作中使用 response_model 参数来声明用于响应的模型:

  • @app.get()
  • @app.post()
  • @app.put()
  • @app.delete()
  • 等等。

response_model是装饰器方法(get,post 等)的一个参数,而不是路由操作函数的参数;其类型与 Pydantic 模型属性所声明的类型相同,因此它可以是一个 Pydantic 模型,但也可以是一个由 Pydantic 模型组成的 list,例如 List[Item]。

在FastAPI 中,使用 response_model 的作用:

  • 将输出数据转换为其声明的类型。
  • 校验数据。
  • 在 OpenAPI 的路径操作中为响应添加一个 JSON Schema。
  • 并在自动生成文档系统中使用。
  • 会将输出数据限制在该模型定义内。

10.1响应模型

代码示例:

from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UsrIn(BaseModel):
name: str
sex: str
acc: str
pwd: str
class UsrOut(BaseModel):
name: str
sex: str
acc: str
@app.post(path='/add_a_usr', response_model=UsrOut)
async def add_a_usr(usr: UsrIn):
return usr

在上面的代码中,输入模型为UsrIn,输出模型为UsrOut,通过输出模型来屏蔽pwd字段的输出。执行请求:

curl -H "Content-Type: application/json;" -X POST -d "{\"name\":\"zhaomm\",\"sex\":\"woman\",\"acc\":\"zhaomm\",\"pwd\":\"Zhao68641018\"}" http://127.0.0.1:8000/add_a_usr
{
"name":"zhaomm",
"sex":"woman",
"acc":"zhaomm"
}

10.2响应模型默认值

在FastAPI中,响应模型也可以具有默认值。

代码示例:

from fastapi import FastAPI
from fastapi import Body
from typing import Optional
from typing import List
from pydantic import BaseModel
app = FastAPI()
class ProductOut(BaseModel):
id: str = Body(...)
name: str = Body(...)
price: Optional[float] = Body(10.0)
desc: Optional[str] = Body(None)
@app.get(path='/find_products', response_model=List[ProductOut])
async def find_products():
products = [
{'id': 'p1', 'name': '玫瑰花'},
{'id': 'p2', 'name': '百合花', 'price': 12.8},
{'id': 'p3', 'name': '月季花', 'price': 19.8, 'desc': '月季花'}
]
return products

执行请求:

curl http://127.0.0.1:8000/find_products
[
{
"id":"p1",
"name":"玫瑰花",
"price":10.0,
"desc":null
},{
"id":"p2",
"name":"百合花",
"price":12.8,
"desc":null
},{
"id":"p3",
"name":"月季花",
"price":19.8,
"desc":"月季花"
}
]

从上面的代码执行结果可以看出:当在实际输出中没有定义对应的字段属性值时,使用响应模型的默认值输出。如果希望响应结果中不输出默认值,仅输出实际数据,则可以在 可以设置路由操作装饰器的 response_model_exclude_unset 参数为真,下面代码在路由中增加该参数,代码如下:

from fastapi import FastAPI
from fastapi import Body
from typing import Optional
from typing import List
from pydantic import BaseModel
app = FastAPI()
class ProductOut(BaseModel):
id: str = Body(...)
name: str = Body(...)
price: Optional[float] = Body(10.0)
desc: Optional[str] = Body(None)
@app.get(path='/find_products', response_model=List[ProductOut], response_model_exclude_unset=True)
async def find_products():
products = [
{'id': 'p1', 'name': '玫瑰花'},
{'id': 'p2', 'name': '百合花', 'price': 12.8},
{'id': 'p3', 'name': '月季花', 'price': 19.8, 'desc': '月季花'}
]
return products

执行请求:

curl http://127.0.0.1:8000/find_products
[
{
"id":"p1",
"name":"玫瑰花"
},{
"id":"p2",
"name":"百合花",
"price":12.8
},{
"id":"p3",
"name":"月季花",
"price":19.8,
"desc":"月季花"
}
]

在以上情况下,如果实际数据值与默认值相同,那么FastAPI仍然会输出,也就是说,当设置response_model_exclude_unset=True时,其输出是按照实际值输出的。下面将代码{'id': 'p2', 'name': '百合花', 'price': 12.8}中的price字段值修改为10.0,然后执行请求:

curl http://127.0.0.1:8000/find_products
[
{
"id":"p1",
"name":"玫瑰花"
},{
"id":"p2",
"name":"百合花",
"price":10.0
},{
"id":"p3",
"name":"月季花",
"price":19.8,
"desc":"月季花"
}
]

从上面的执行结果可以看出:虽然实际数据值与默认值相同,但仍然会输出。如果此时,不希望输出与默认值相同的数据值,可以设置 response_model_exclude_defaults参数为True,代码片段如下:

class ProductOut(BaseModel):
id: str = Body(...)
name: str = Body(...)
price: Optional[float] = Body(10.0)
desc: Optional[str] = Body(None)
@app.get(path='/find_products', response_model=List[ProductOut], response_model_exclude_defaults=True)
async def find_products():
products = [
{'id': 'p1', 'name': '玫瑰花'},
{'id': 'p2', 'name': '百合花', 'price': 10.0},
{'id': 'p3', 'name': '月季花', 'price': 19.8, 'desc': '月季花'}
]
return products

执行请求:

curl http://127.0.0.1:8000/find_products
[
{
"id":"p1",
"name":"玫瑰花"
},{
"id":"p2",
"name":"百合花"
},{
"id":"p3",
"name":"月季花",
"price":19.8,
"desc":"月季花"
}
]

在上面的执行结果中:{"id":"p2","name":"百合花"}没有输出price字段的实际数据值,因为与默认值相同。

另外还有一个装饰器参数: response_model_exclude_none,如果设置该参数为True,则表示在响应模型中不输出None值。代码片段如下:

@app.get(path='/find_products', response_model=List[ProductOut], response_model_exclude_none=True)
async def find_products():
products = [
{'id': 'p1', 'name': '玫瑰花'},
{'id': 'p2', 'name': '百合花', 'price': 10.0},
{'id': 'p3', 'name': '月季花', 'price': 19.8, 'desc': '月季花'}
]
return products

执行请求:

curl http://127.0.0.1:8000/find_products
[
{
"id":"p1",
"name":"玫瑰花",
"price":10.0
},{
"id":"p2",
"name":"百合花",
"price":10.0
},{
"id":"p3",
"name":"月季花",
"price":19.8,
"desc":"月季花"
}
]

10.3显式声明响应模型的字段

可以使用装饰器参数response_model_include 和 response_model_exclude来显式指明包含或者排除的字段,这两个参数接受由字符串组成的set类型,也可以使用list或tuple,但使用list或tuple最终会转换为set类型。

在Python中,set类型使用一组花括号定义,如:{'sex', 'name'}。

代码示例:

@app.get(path='/find_products', response_model=List[ProductOut], response_model_include={'price', 'desc'})
async def find_products():
products = [
{'id': 'p1', 'name': '玫瑰花'},
{'id': 'p2', 'name': '百合花', 'price': 10.0},
{'id': 'p3', 'name': '月季花', 'price': 19.8, 'desc': '月季花'}
]
return products

执行请求:

curl http://127.0.0.1:8000/find_products
[
{
"price":10.0,
"desc":null
},{
"price":10.0,
"desc":null
},{
"price":19.8,
"desc":"月季花"
}
]

10.4响应模型Union

在FastAPI中,响应模型可以设置为多个类型的Union,响应为多种类型中的一种;定义该类型使用标准的Python类型提示typing.Union,原则上应该是先使用详细的类型,然后使用粗略的类型。代码示例:

from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class BaseItem(BaseModel):
description: str
type: str
class CarItem(BaseItem):
type = "car"
length: int
class PlaneItem(BaseItem):
type = "plane"
size: int
items = {
"item1": {"description": "All my friends drive a low rider", "type": "car", "length": 200},
"item2": {
"description": "Music is my aeroplane, it's my aeroplane",
"type": "plane",
"size": 5,
},
}
@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem])
async def read_item(item_id: str):
return items[item_id]

执行请求:

curl http://127.0.0.1:8000/items/item1
{
"description":"All my friends drive a low rider",
"type":"car",
"length":200
}
C:\Users\Administrator>curl http://127.0.0.1:8000/items/item2
{
"description":"Music is my aeroplane, it's my aeroplane",
"type":"plane",
"size":5
}

10.5任意 dict 构成的响应

在FastAPI中,如果事先不确定返回值的键名称,但可以确定返回的数据类型,那么,可以使用只包含类型的dict作为响应模型,使用 typing.Dict。示例代码如下:

from fastapi import FastAPI
from typing import Dict
app = FastAPI()
@app.get(path='/test', response_model=Dict[str, int])
async def test():
return {'foo': 20}

执行请求:

curl http://127.0.0.1:8000/test
{"foo":20}