当前位置:网站首页>15_额外的模型
15_额外的模型
2022-07-22 04:53:00 【空巢青年_rui】
15_额外的模型
我们继续之前的例子,可见,代码中拥有多个相关的模型是很常见的。
对用户模型来说尤其如此,因为:
- 输入模型需要拥有密码属性
- 输出模型不应该包含密码
- 数据库模型很可能需要保存密码的哈希值
安全提示:
永远不要存储用户的明文密码。始终存储一个可以用于验证的「安全哈希值」。
如果你尚未了解该知识,你可以在安全章节中学习何为「密码哈希值」。
1. 多个模型:
下面是应该如何根据它们的密码字段以及使用位置去定义模型的大概思路:
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
关于 **user_in.dict()
:
1). Pydantic 的 .dict()
。
user_in
是一个 UserIn
类的 Pydantic 模型。Pydantic 模型有一个 .dict()
方法用于返回一个带有模型数据的 dict
。
因此,我们可以像下面这样创建一个 Pydantic 对象 user_in
:
user_in = UserIn(username="john", password="secret", email="[email protected]")
然后我们调用:
user_dict = user_in.dict()
我们现在有了一个带有数据的字典变量 user_dict
(他是一个dict
而不是Pydantic模型对象)。
如果我们调用
print(user_dict)
将会获得一个这样的Python dict:
{
'username': 'john',
'password': 'secret',
'email': '[email protected]',
'full_name': None,
}
2). 解包 dict:
如果我们将 user_dict
这样的 dict
以 **user_dict
形式传递给一个函数(或类),Python将对其进行「解包」。它会将 user_dict
的键和值作为关键字参数直接传递。
因此,从上面的user_dict继续,执行
UserInDB(**user_dict)
会产生类似于下面的结果:
UserInDB(
username="john",
password="secret",
email="[email protected]",
full_name=None,
)
或者,更准确的说,直接使用 user_idct
来表示将来可能包含的任何内容:
UserInDB(
username = user_dict["username"],
password = user_dict["password"],
email = user_dict["email"],
full_name = user_dict["full_name"],
)
3). 来自于其他模型内容的 Pydantic 模型:
在上面,我们从 user_in.dict()
中获得了 user_dict
:
user_dict = user_in.dict()
UserInDB(**user_dict)
这两行代码等同于:
UserInDB(**user_in.dict())
因为 user_in.dict()
是一个 dict
,然后我们通过 **
开头传递给UserInDB
类来解包它。
这样,我们获得了一个来自于其他 Pydantic 模型中数据内容的 Pydantic 模型。
4). 解包 dict 和额外关键字:
然后, 我们在UserInDB
类的实例化时添加了额外的关键字参数 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_*()
只是演示可能的数据流,但它们当然并没有提供任何真正的安全性。
2. 减少重复:
减少代码重复是FastAPI的核心思想之一。
因为代码重复会增加出现bug、安全问题、代码不同步(当你在一个位置更新了代码但没有在其他位置更新)等问题的可能性。
上面的模型中包含大量的共享数据,并拥有重复的属性名称和类型。我们可以对其进行改进。
我们可以声明一个 UserBase 模型作为其他模型的基类。然后创建继承该模型属性(类型声明、校验等)的子类。
所有的数据转换、校验、文档生成等仍能正常运行。
这样,我们可以仅声明模型之间有差异的部分(具有明文密码、具有密码哈希值以及不包含密码):
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
3. Union
或者anyOf
:
我们可以将响应声明为一个可能为两种类型的 Union
,这意味着该响应将是两种类型中的任何一种。
这将在 OpenAPI 中使用 anyOf
进行定义。
为此,请使用Python标准的类型提示 typing.Union
(即使使用了python 3.10,也要调用 typing 模块的 Union
,因为这里是作为参数的值传递的,不是作为变量的类型声明使用的)。
注意:
当定义一个 Union 时,首先包括最详细的类型,然后是不太详细的类型。在下面的示例中,更详细的
PlaneItem
位于Union[PlaneItem,CarItem]
中的CarItem
之前。
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]) # 使用Union指定响应模型
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
和 CartItem
之间执行一个无效的操作,而不是将其解释为类型注释。
4. 模型列表:
可以用同样的方式声明由对象列表构成的响应。
为此,请使用标准的 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
python3.6+:
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
5. 任意dict构成的响应:
我们还可以使用一个任意的普通 dict
声明响应,仅声明键和值的类型,而不使用 Pydantic 模型。
如果你事先不知道有效的 字段/属性 名称(对于 Pydantic 模型是必需的),这将很有用。
在这种情况下,你可以使用 typing.Dict
, 或者在Python 3.9 及之后版本中直接使用 dict
:
python3.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}
python3.6+:
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 模型,并针对不同场景自由地继承。
如果一个实体(entity)必须能够具有不同的「状态 states」,你无需为每个状态的实体定义单独的数据模型。以用户「实体」为例,其状态有包含 password、包含 password_hash 以及不含密码。
边栏推荐
- oracle存储过程参数理解
- Kingbasees Security Guide for Jincang database -- 2.1. about database security threats
- Jincang database kmonitor user guide --3. deployment
- 信息学奥赛一本通 1977:【08NOIP普及组】立体图 | 洛谷 P1058 [NOIP2008 普及组] 立体图
- Mecol Studio - the third assignment of harmonyos application development training
- AlterNet Studio 8.1 Crack
- Win10系统打开什么都是反应比平时慢,转圈等待1分钟如何解决?
- Mecol Studio - harmonyos second assignment
- UE4 进入指定区域实现触发加速功能
- AcWing_ 11. Number of solutions for knapsack problem_ dp
猜你喜欢
Switch and router technology: static NAT, dynamic NAT, pat and port mirroring
把握机器人教育朝AI智能化发展的趋势
MySQL查询计划key_len如何计算
Switch and router technology: OSPF route redistribution, OSPF NSSA area and OSPF virtual link
UE4 将画刷制作的物体合并成一个整体
UE4 设置碰撞体
UE4 设置夜晚(根据DirectionalLight方向更新SkySphere)
Simulation Implementation of string
UE4 创建一个工程
Fastjson code execution cve-2022-25845
随机推荐
Gold warehouse database kmonitor usage guide --2. monitoring indicators
模型压缩、加速及移动端部署
Inventory of e-mail security incidents in China in the first half of 2022
微信支付项目实战、创建订单到支付退款代码详解
A Mobile Telematics Pattern Recognition Framework for Driving Behavior Extraction
STM32控制电机简易教程
理解递归与迭代
高数_第2章多元函数微分学_隐函数的偏导数
ECCV 2022 | fix the performance damage of large targets caused by FPN: you should look at all objects
UE4 将画刷制作的物体合并成一个整体
What is SCM? What are the components of SCM?
UE4 通过按键升降电梯
把握机器人教育朝AI智能化发展的趋势
CF464E The Classic Problem
UE4 将蓝图写在Actor类里面 实现复用
AlterNet Studio 8.1 Crack
【Unity】 UI跟随3D物体,世界坐标转UI坐标
Large file slice upload and breakpoint continuation
Informatics Olympiad all in one 1977: [08noip popularization group] stereogram | Luogu p1058 [noip2008 popularization group] stereogram
How to install a pagoda on IBM's free machine