响应模型是指在接口调用之后,服务器返回给客户端的数据模型。这个数据模型可以是一个简单的字符串,也可以是一个复杂的数据结构,如 JSON 或 XML 格式的数据。本篇文章将详细介绍 FastAPI 中的响应模型。
目录
1 响应模型
1.1 response_model
1.2 response_model_exclude_unset
1.3 response_model_include 和 response_model_exclude
2 额外的模型
2.1 **user_in.dict()
① Pydantic 的 .dict()
② 解包
③ 额外关键字
2.2 减少重复代码
2.3 响应状态码
① demo
② HTTP 状态码
③ 名称捷径
1 响应模型
1.1 response_model
你可以在任意的路径操作中使用 response_model 参数来声明用于响应的模型:
- @app.get()
- @app.post()
- @app.put()
- @app.delete()
- 等等。
from typing import Any, List, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None #默认值
price: float
tax: Union[float, None] = None #默认值
tags: List[str] = [] #默认值
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
return item
@app.get("/items/", response_model=List[Item])
async def read_items():
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]
注意:response_model 是「装饰器」方法(get,post 等)的一个参数。不像之前的所有参数和请求体,它不属于路径操作函数。
它接收的类型与你将为 Pydantic 模型属性所声明的类型相同,因此它可以是一个 Pydantic 模型,但也可以是一个由 Pydantic 模型组成的 list,例如 List[Item]。
FastAPI 将使用此 response_model 来做以下事:
- 将输出数据转换为其声明的类型;
- 校验数据;
- 在自动生成文档系统中使用;
- 会将输出数据限制在该模型定义内!
📌 文档
1.2 response_model_exclude_unset
可以设置路径操作装饰器中的 response_model_exclude_unset=True 参数,然后响应中将不会包含那些默认值,而是仅有实际设置的值:
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item1(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: float = 10.5
tags: List[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items3/{item_id}", response_model=Item1, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
若此时向路径操作发送 ID 为 foo 的商品的请求,则响应(不包括默认值)将为:
{
"name": "Foo",
"price": 50.2
}
📌 默认值字段有实际值的数据
若此时向路径操作发送 ID 为 bar 的商品的请求,则响应为:
{
"name": "Bar",
"description": "The bartenders",
"price": 62,
"tax": 20.2
}
这些值将包含在响应中。
📌 具有与默认值相同值的数据
如果数据具有与默认值相同的值,例如 ID 为 baz 的项:
{
"name": "Baz",
"description": None,
"price": 50.2,
"tax": 10.5,
"tags": []
}
即使 description、tax 和 tags 具有与默认值相同的值,FastAPI 也能识别到这一点,它们的值被显式地所设定(而不是取自默认值),因此这些与默认值相同的值将包含在 JSON 响应中。
📌 你还可以使用:
- response_model_exclude_defaults=True:等于其默认值的字段(无论是否设置)是否应从返回的字典中排除
- response_model_exclude_none=True:是否应从返回的字典中排除等于 none 的字段
1.3 response_model_include 和 response_model_exclude
它们接收一个由属性名称 str 组成的 set 来包含(忽略其他的)或者排除(包含其他的)这些属性。
比如,如果要求返回的属性只有 name,那么可以使用 response_model_include,如果要求返回的属性排除掉 name,那么可以使用 response_model_exclude。
...
@app.get(
"/items/{item_id}/name",
response_model=Item1,
response_model_include={"name", "description"}
)
async def read_item_name(item_id: str):
return items[item_id]
@app.get("/items/{item_id}/public", response_model=Item1, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
return items[item_id]
注意:{"name", "description"} 语法创建一个具有这两个值的 set。等同于 set(["name", "description"])。
2 额外的模型
2.1 **user_in.dict()
① Pydantic 的 .dict()
Pydantic 模型具有 .dict() 方法,该方法返回一个拥有模型数据的 dict。
首先我们像下面这样创建一个 Pydantic 对象 user_in:
from typing import Union
from pydantic import BaseModel, EmailStr
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
class UserInDB(BaseModel):
username: str
hashed_password: str = None
email: EmailStr
full_name: Union[str, None] = None
user_in = UserIn(username="yinyu", password="secret", email="yinyu.doe@example.com")
然后调用并打印:
user_dict = user_in.dict()
print(user_dict)
我们将获得一个这样的 Python dict:
{
'username': 'yinyu',
'password': 'secret',
'email': 'yinyu.doe@example.com',
'full_name': None,
}
② 解包
如果我们将 user_dict 这样的 dict 以 **user_dict 形式传递给一个函数(或类),Python将对其进行「解包」,它会将 user_dict 的键和值作为关键字参数直接传递。
从上面的 user_dict 继续编写:
user_db = UserInDB(**user_dict)
会产生类似于以下的结果:
UserInDB(
username="yinyu",
hashed_password=None,
email="yinyu.doe@example.com",
full_name=None,
)
需要注意的是,UserIn 和 UserInDB 可不是同一个模型,password 字段也不等同于 hashed_password 字段,也就是说在字段名一致的前提下,对应字段才会进行解包。
或者可以确切地进行如下表示:
UserInDB(
username = user_dict["username"],
hashed_password = user_dict["hashed_password"], #user_dic中没有该字段,那么会取默认值
email = user_dict["email"],
full_name = user_dict["full_name"]
)
③ 额外关键字
如上所述,hashed_password 字段没有取到值,这是因为字段名的不一致。
那么如何给这个 hashed_password 额外关键字设值呢,接上边代码如下:
user_dbb = UserInDB(**user_dict,hashed_password = "secret11")
最终结果如下:
UserInDB(
username="yinyu",
hashed_password="secret11",
email="yinyu.doe@example.com",
full_name=None,
)
2.2 减少重复代码
这是 FastAPI 的核心思想之一,上边的模型共享了大量数据,并拥有重复的属性名称和类型。
那么我们可以声明一个 UserBase 模型作为其他模型的基类。然后我们可以创建继承该模型属性(类型声明,校验等)的子类。
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
class UserIn1(UserBase):
password: str
class UserInDB1(UserBase):
hashed_password: str
2.3 响应状态码
① demo
与指定响应模型的方式相同,你也可以在以下任意的路径操作中使用 status_code 参数来声明用于响应的 HTTP 状态码:
- @app.get()
- @app.post()
- @app.put()
- @app.delete()
- 等等。
from fastapi import FastAPI
app = FastAPI()
@app.post("/items1/", status_code=201)
async def create_item(name: str):
return {"name": name}
status_code 参数接收一个表示 HTTP 状态码的数字。
📌 文档
② HTTP 状态码
在 HTTP 协议中,你将发送 3 位数的数字状态码作为响应的一部分。
简而言之:
- 100 及以上状态码用于「消息」响应。很少直接使用,具有这些状态代码的响应不能带有响应体。
- 200 及以上状态码用于「成功」响应,最常用。
- 200 是默认状态代码,它表示一切「正常」。
- 另一个例子会是 201,「已创建」。它通常在数据库中创建了一条新记录后使用。
- 一个特殊的例子是 204,「无内容」。此响应在没有内容返回给客户端时使用,因此该响应不能包含响应体。
- 300 及以上状态码用于「重定向」。具有这些状态码的响应可能有或者可能没有响应体,但 304「未修改」是个例外,该响应不得含有响应体。
- 400 及以上状态码用于「客户端错误」响应。这些可能是你第二常使用的类型。
- 一个例子是 404,用于「未找到」响应。
- 对于来自客户端的一般错误,你可以只使用 400。
- 500 及以上状态码用于服务器端错误。当你的应用程序代码或服务器中的某些部分出现问题时,它将自动返回这些状态代码之一。
③ 名称捷径
你不必去记住每个代码的含义。你可以使用来自 fastapi.status 的便捷变量。
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/items2/", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
return {"name": name}
这样使用你就可以使用编辑器的自动补全功能来查找它们: