额外模型¶
继续前面的示例,通常会有多个相关的模型。
对于用户模型来说尤其如此,因为:
- **输入模型**需要能够包含密码。
- **输出模型**不应该包含密码。
- **数据库模型**可能需要包含哈希密码。
/// 警告
切勿存储用户的明文密码。始终存储一个可以验证的“安全哈希”。
如果你不知道,你将在安全章节中学习什么是“密码哈希”。
///
多个模型¶
以下是一个关于模型如何看起来包含密码字段以及它们在何处使用的通用想法:
//// 标签 | Python 3.10+
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str | None = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
class UserInDB(BaseModel):
username: str
hashed_password: str
email: EmailStr
full_name: str | None = None
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
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
////
//// 标签 | Python 3.8+
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
class UserInDB(BaseModel):
username: str
hashed_password: str
email: EmailStr
full_name: Union[str, None] = None
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
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
////
/// 信息
在 Pydantic v1 中,该方法被称为 .dict()
,在 Pydantic v2 中已被弃用(但仍支持),并重命名为 .model_dump()
。
这里的示例使用 .dict()
以兼容 Pydantic v1,但如果你可以使用 Pydantic v2,则应改用 .model_dump()
。
///
关于 **user_in.dict()
¶
Pydantic 的 .dict()
¶
user_in
是一个 UserIn
类的 Pydantic 模型。
Pydantic 模型有一个 .dict()
方法,该方法返回一个包含模型数据的 dict
。
因此,如果我们创建一个 Pydantic 对象 user_in
如下:
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
然后我们调用:
user_dict = user_in.dict()
我们现在有了一个包含数据的 dict
,存储在变量 user_dict
中(它是一个 dict
而不是 Pydantic 模型对象)。
如果我们调用:
print(user_dict)
我们将得到一个包含以下内容的 Python dict
:
{
'username': 'john',
'password': 'secret',
'email': 'john.doe@example.com',
'full_name': None,
}
解包 dict
¶
如果我们取一个像 user_dict
这样的 dict
并将其传递给一个函数(或类),使用 **user_dict
,Python 将“解包”它。它将直接传递 user_dict
的键和值作为键值参数。
因此,继续使用上面的 user_dict
,写:
UserInDB(**user_dict)
将得到类似于:
UserInDB(
username="john",
password="secret",
email="john.doe@example.com",
full_name=None,
)
或者更准确地说,使用 user_dict
直接,无论其未来内容如何:
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
从另一个 Pydantic 模型的内容创建一个 Pydantic 模型¶
正如我们在上面的示例中从 user_in.dict()
得到 user_dict
,这段代码:
user_dict = user_in.dict()
UserInDB(**user_dict)
将等同于:
UserInDB(**user_in.dict())
...因为 user_in.dict()
是一个 dict
,然后我们通过在 UserInDB
前加上 **
来让 Python “解包”它。
因此,我们从另一个 Pydantic 模型的数据中得到了一个 Pydantic 模型。
解包 dict
和额外关键字¶
然后添加额外的关键字参数 hashed_password=hashed_password
,如下:
UserInDB(**user_in.dict(), hashed_password=hashed_password)
...最终结果类似于:
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
hashed_password = hashed_password,
)
/// 警告
支持的额外函数 fake_password_hasher
和 fake_save_user
只是为了演示数据的可能流程,但它们当然不提供任何实际的安全性。
///
减少重复¶
减少代码重复是 FastAPI 的核心思想之一。
随着代码重复的增加,出现错误、安全问题、代码不同步问题(当你在一个地方更新而在其他地方没有更新)等的几率也会增加。
而这些模型都共享大量数据,并重复属性名称和类型。
我们可以做得更好。
我们可以声明一个 UserBase
模型,作为其他模型的基础。然后我们可以创建该模型的子类,继承其属性(类型声明、验证等)。
所有数据转换、验证、文档等仍将正常工作。
这样,我们只需在模型之间声明差异(带有明文 password
、带有 hashed_password
和不带密码):
//// 标签 | Python 3.10+
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: str | None = None
class UserIn(UserBase):
password: str
class UserOut(UserBase):
pass
class UserInDB(UserBase):
hashed_password: str
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
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
////
//// 标签 | Python 3.8+
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
class UserIn(UserBase):
password: str
class UserOut(UserBase):
pass
class UserInDB(UserBase):
hashed_password: str
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
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
Union
或 anyOf
¶
你可以声明一个响应为两个或更多类型的 Union
,这意味着响应可以是其中任何一种类型。
它将在 OpenAPI 中使用 anyOf
定义。
为此,请使用标准的 Python 类型提示 typing.Union
:
/// 注意
在定义 Union
时,请将最具体的类型放在首位,然后是较不具体的类型。在下面的示例中,更具体的 PlaneItem
在 Union[PlaneItem, CarItem]
中位于 CarItem
之前。
///
//// 标签 | Python 3.10+
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: str = "car"
class PlaneItem(BaseItem):
type: str = "plane"
size: int
items = {
"item1": {"description": "All my friends drive a low rider", "type": "car"},
"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]
////
//// 标签 | Python 3.8+
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: str = "car"
class PlaneItem(BaseItem):
type: str = "plane"
size: int
items = {
"item1": {"description": "All my friends drive a low rider", "type": "car"},
"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]
////
Python 3.10 中的 Union
¶
在这个示例中,我们将 Union[PlaneItem, CarItem]
作为 response_model
参数的值传递。
因为我们将其作为 参数的值 传递,而不是放在 类型注解 中,所以我们即使在 Python 3.10 中也必须使用 Union
。
如果它是在类型注解中,我们可以使用竖线,如下所示:
some_variable: PlaneItem | CarItem
但如果我们将它放在赋值 response_model=PlaneItem | CarItem
中,我们会得到一个错误,因为 Python 会尝试在 PlaneItem
和 CarItem
之间执行 无效操作,而不是将其解释为类型注解。
模型列表¶
同样地,你可以声明对象列表的响应。
为此,请使用标准的 Python typing.List
(或在 Python 3.9 及以上版本中使用 list
):
//// 标签 | Python 3.9+
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
items = [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]
@app.get("/items/", response_model=list[Item])
async def read_items():
return items
////
//// 标签 | Python 3.8+
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
items = [
{"name": "Foo", "description": "There comes my hero"},
{"name": "Red", "description": "It's my aeroplane"},
]
@app.get("/items/", response_model=List[Item])
async def read_items():
return items
////
使用任意 dict
的响应¶
你还可以使用普通的任意 dict
声明响应,只需声明键和值的类型,而不使用 Pydantic 模型。
如果你事先不知道有效的字段/属性名称(这些名称需要用于 Pydantic 模型),这将非常有用。
在这种情况下,你可以使用 typing.Dict
(或在 Python 3.9 及以上版本中使用 dict
):
//// 标签 | Python 3.9+
from fastapi import FastAPI
app = FastAPI()
@app.get("/keyword-weights/", response_model=dict[str, float])
async def read_keyword_weights():
return {"foo": 2.3, "bar": 3.4}
////
//// 标签 | Python 3.8+
from typing import Dict
from fastapi import FastAPI
app = FastAPI()
@app.get("/keyword-weights/", response_model=Dict[str, float])
async def read_keyword_weights():
return {"foo": 2.3, "bar": 3.4}
////
总结¶
为每种情况使用多个 Pydantic 模型并自由继承。
如果实体必须具有不同的“状态”,则不需要为每个实体使用单一的数据模型。例如,用户“实体”具有包括 password
、password_hash
和无密码的状态。