Extra Models
有时候我们常常会有不止一个关系模型
用户模型尤其如此,因为:
- input model 需要有密码字段
- output model 不应该有密码字段
- database model 需要有哈希密码字段
警告:永远不要存储用户的明文密码,应该总是存储可以验证的安全哈希
Multiple models
下面的例子是关于在用户模型中如何放置密码字段的通用思想
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: str = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: str = None
class UserInDB(BaseModel):
username: str
hashed_password: str
email: EmailStr
full_name: str = 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
关于 **user_in.dict()
user_in
是 类 UserIn
的Pydantic
模型实例
Pydantic
模型的 .dict()
方法返回一个含有模型数据的字典
例如,如果我们创建一个 Pydantic
对象 user_in
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
接着调用
user_dict = user_in.dict()
那么 user_dict
实际上是一个 Python dict
,其内容为
{
'username': 'john',
'password': 'secret',
'email': 'john.doe@example.com',
'full_name': None,
}
Unwrapping a dict
如果我们在函数调用或者类实例化时,用 **user_dict
的方法传递像 user_dict
这样的字典参数,Python 将会对其解包,即以关键字参数的方式传递 user_dict
的所有键和值
在上面的例子中,使用
UserInDB(**user_dict)
相当于
UserInDB(
username="john",
password="secret",
email="john.doe@example.com",
full_name=None,
)
更准确的说,是
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
Unwrapping a dict
and extra keywords
在上面的例子中,我们除了进行解包 **user_in.dict()
,还添加了额外的关键字参数
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,
)
Reduce duplication
FastAPI 的核心宗旨之一是减少代码冗余
因为代码冗余会增加出现 bug,安全问题,代码不同步问题(在某一处更新代码,而其他的地方没有)的几率
在上面的例子中,模型共享了具有大量重复属性名字和类型的数据
我们可以做得更好
我们可以声明一个 UserBase
模型作为其他模型的基本模型。然后,我们可以使该模型的子类继承其属性(类型声明,验证等)
所有的数据转换、验证、自动文档等都会正常工作
这样的话,我们只需要声明这些模型不同的部分(password
,hashed_password
以及没有密码)
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: str = 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
or anyOf
我们可以声明相应为两种类型的联合,即,相应为两种类型之一
在 OpenAPI 中将会使用 anyOf
来定义
要做到这一点,可以使用 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"
class PlaneItem(BaseItem):
type = "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]
List of models
同样,我们可以声明返回一组对象
要做到这一点,使用 Python 的 typing.List
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
Response with arbitrary dict
我们也可以使用简单任意 dict
声明返回响应,在 dict
中,只需要声明键和值的类型,而不需要使用 Pydantic
模型
如果事先不知道有效的字段/属性名称(Pydantic模型需要此名称),这将很有用。
例如
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}