继续前面的示例,通常会有多个相关模型。
用户模型尤其如此,因为:
input model 需要包含密码字段
output model 不能包含密码字段
database model可能包含一个被hash的密码值
危险⚠️
永远不要存储用户的明文密码,要使用一个安全的hash值去验证密码。
如果您不知道,您将了解什么是“密码哈希” 点击security chapters.
一、多个模型
下面写出了密码的使用地方和几个模型:
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import 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
1. 关于 **user_in.dict()
1.1. Pydantic的 .dict()方法
user_in 是一个 UserIn 类的Pydantic模型的实例.
Pydantic 模型有 .dict() 方法,该方法会将模型中的数据转化为 dict.
我们创建一个 Pydantic 对象 user_in,比如:
user_in = UserIn(username="john", password="secret", email="john.doe@example.com")
然后调用.dict()方法:
user_dict = user_in.dict()
我们现在有了一个dict,数据在变量user_dict中(这是dict而不是Pydantic模型对象)。
查看一下user_dict:
print(user_dict)
# 输出
{
'username': 'john',
'password': 'secret',
'email': 'john.doe@example.com',
'full_name': None,
}
1.2. dict解包
如果我们采用user_dict之类的dict并将其传递给带有** user_dict的函数(或类),Python将对其“解包”。 它将直接传递user_dict的键和值作为键值参数。
因此, 使用上面的 user_dict :
UserInDB(**user_dict)
上面的**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"],
)
1.3. 来自另一个内容的Pydantic模型
上面的例子中, 我们从 user_in.dict()得到了 user_dict ,如下:
user_dict = user_in.dict()
UserInDB(**user_dict)
等价于:
UserInDB(**user_in.dict())
因为 user_in.dict() 是一个 dict, 然后我们Python的 "解包**语法" 将参数传递到UserInDB
因此我们得到了一个Pydantic模型,但是数据是从另一个Pydantic模型.
1.4. 解包dict 和添加额外的keyword参数
添加额外关键字参数, 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,
)
警告⚠️:
支持的附加功能仅仅是演示数据的可能流,但是它们当然并不能提供任何真正的安全性。
二、减少重复代码
减少重复代码是 FastAPI 的核心思想之一。
随着代码重复的增加,错误,安全性问题,代码不同步问题(当您在一个地方进行更新而不是在另一个地方进行更新)等问题的机会也将增加。
这些模型都共享大量数据,并复制属性名称和类型。
我们可以做的更好.
我们可以定义UserBase模型,为我们其他的模型提供一个基类。然后我们其他的类可以继承这个UserBase模型,继承它的所有的属性。
这样我们只需要在每个子类中定义出其他的不同的属性即可:
from fastapi import FastAPI
from pydantic import BaseModel
from pydantic.types import 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 和 anyOf
您可以将响应声明为两种类型的Union,这意味着响应将是两种类型中的任何一种。
它会在 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
同样的方式,你可以定义响应类型是模型组成的列表。
同样,你也需要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
四、用任意dict响应
您还可以使用简单的任意dict声明响应,仅声明键和值的类型,而无需使用Pydantic模型。
如果您事先不知道有效的字段/属性名称(Pydantic模型需要此名称),这将很有用。
这里你需要使用 typing.Dict:
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}