Movatterモバイル変換


[0]ホーム

URL:


跳转至
Join theFastAPI Cloud waiting list 🚀
Follow@fastapi onX (Twitter) to stay updated
FollowFastAPI onLinkedIn to stay updated
Subscribe to theFastAPI and friends newsletter 🎉
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor
sponsor

更多模型

🌐 由 AI 与人类协作翻译

本翻译由人类引导的 AI 生成。🤝

可能存在误解原意或不够自然等问题。🤖

你可以通过帮助我们更好地引导 AI LLM来改进此翻译。

英文版本

书接上文,多个关联模型这种情况很常见。

特别是用户模型,因为:

  • 输入模型应该含密码
  • 输出模型不应含密码
  • 数据库模型可能需要包含哈希后的密码

危险

不要存储用户的明文密码。始终只存储之后可用于校验的“安全哈希”。

如果你还不了解,可以在安全性章节中学习什么是“密码哈希”。

多个模型

下面的代码展示了不同模型处理密码字段的方式,及使用位置的大致思路:

fromfastapiimportFastAPIfrompydanticimportBaseModel,EmailStrapp=FastAPI()classUserIn(BaseModel):username:strpassword:stremail:EmailStrfull_name:str|None=NoneclassUserOut(BaseModel):username:stremail:EmailStrfull_name:str|None=NoneclassUserInDB(BaseModel):username:strhashed_password:stremail:EmailStrfull_name:str|None=Nonedeffake_password_hasher(raw_password:str):return"supersecret"+raw_passworddeffake_save_user(user_in:UserIn):hashed_password=fake_password_hasher(user_in.password)user_in_db=UserInDB(**user_in.model_dump(),hashed_password=hashed_password)print("User saved! ..not really")returnuser_in_db@app.post("/user/",response_model=UserOut)asyncdefcreate_user(user_in:UserIn):user_saved=fake_save_user(user_in)returnuser_saved

关于**user_in.model_dump()

Pydantic 的.model_dump()

user_in 是类UserIn 的 Pydantic 模型。

Pydantic 模型有.model_dump() 方法,会返回包含模型数据的dict

因此,如果使用如下方式创建 Pydantic 对象user_in

user_in=UserIn(username="john",password="secret",email="john.doe@example.com")

就能以如下方式调用:

user_dict=user_in.model_dump()

现在,变量user_dict 中的是包含数据的dict(它是dict,不是 Pydantic 模型对象)。

以如下方式调用:

print(user_dict)

输出的就是 Pythondict

{'username':'john','password':'secret','email':'john.doe@example.com','full_name':None,}

解包dict

dict(如user_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 模型

上例中 ,从user_in.model_dump() 中得到了user_dict,下面的代码:

user_dict=user_in.model_dump()UserInDB(**user_dict)

等效于:

UserInDB(**user_in.model_dump())

……因为user_in.model_dump()dict,在传递给UserInDB 时,把** 加在user_in.model_dump() 前,可以让 Python 进行解包。

这样,就可以用其它 Pydantic 模型中的数据生成 Pydantic 模型。

解包dict 并添加额外关键字参数

接下来,继续添加关键字参数hashed_password=hashed_password,例如:

UserInDB(**user_in.model_dump(),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_hasherfake_save_user 仅用于演示可能的数据流,当然并不提供真实的安全性。

减少重复

减少代码重复是FastAPI 的核心思想之一。

代码重复会导致 bug、安全问题、代码失步等问题(更新了某个位置的代码,但没有同步更新其它位置的代码)。

上面的这些模型共享了大量数据,拥有重复的属性名和类型。

我们可以做得更好。

声明UserBase 模型作为其它模型的基类。然后,用该类衍生出继承其属性(类型声明、校验等)的子类。

所有数据转换、校验、文档等功能仍将正常运行。

这样,就可以仅声明模型之间的差异部分(具有明文的password、具有hashed_password 以及不包括密码):

fromfastapiimportFastAPIfrompydanticimportBaseModel,EmailStrapp=FastAPI()classUserBase(BaseModel):username:stremail:EmailStrfull_name:str|None=NoneclassUserIn(UserBase):password:strclassUserOut(UserBase):passclassUserInDB(UserBase):hashed_password:strdeffake_password_hasher(raw_password:str):return"supersecret"+raw_passworddeffake_save_user(user_in:UserIn):hashed_password=fake_password_hasher(user_in.password)user_in_db=UserInDB(**user_in.model_dump(),hashed_password=hashed_password)print("User saved! ..not really")returnuser_in_db@app.post("/user/",response_model=UserOut)asyncdefcreate_user(user_in:UserIn):user_saved=fake_save_user(user_in)returnuser_saved

UnionanyOf

响应可以声明为两个或多个类型的Union,即该响应可以是这些类型中的任意一种。

在 OpenAPI 中会用anyOf 表示。

为此,请使用 Python 标准类型提示typing.Union

注意

定义Union 类型时,要把更具体的类型写在前面,然后是不太具体的类型。下例中,更具体的PlaneItem 位于Union[PlaneItem, CarItem] 中的CarItem 之前。

fromfastapiimportFastAPIfrompydanticimportBaseModelapp=FastAPI()classBaseItem(BaseModel):description:strtype:strclassCarItem(BaseItem):type:str="car"classPlaneItem(BaseItem):type:str="plane"size:intitems={"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=PlaneItem|CarItem)asyncdefread_item(item_id:str):returnitems[item_id]

Python 3.10 中的Union

在这个示例中,我们把Union[PlaneItem, CarItem] 作为参数response_model 的值传入。

因为这是作为“参数的值”而不是放在“类型注解”中,所以即使在 Python 3.10 也必须使用Union

如果是在类型注解中,我们就可以使用竖线:

some_variable:PlaneItem|CarItem

但如果把它写成赋值response_model=PlaneItem | CarItem,就会报错,因为 Python 会尝试在PlaneItemCarItem 之间执行一个“无效的运算”,而不是把它当作类型注解来解析。

模型列表

同样地,你可以声明由对象列表构成的响应。

为此,请使用标准的 Pythonlist

fromfastapiimportFastAPIfrompydanticimportBaseModelapp=FastAPI()classItem(BaseModel):name:strdescription:stritems=[{"name":"Foo","description":"There comes my hero"},{"name":"Red","description":"It's my aeroplane"},]@app.get("/items/",response_model=list[Item])asyncdefread_items():returnitems

任意dict 的响应

你也可以使用普通的任意dict 来声明响应,只需声明键和值的类型,无需使用 Pydantic 模型。

如果你事先不知道有效的字段/属性名(Pydantic 模型需要预先知道字段)时,这很有用。

此时,可以使用dict

fromfastapiimportFastAPIapp=FastAPI()@app.get("/keyword-weights/",response_model=dict[str,float])asyncdefread_keyword_weights():return{"foo":2.3,"bar":3.4}

小结

针对不同场景,可以随意使用不同的 Pydantic 模型并通过继承复用。

当一个实体需要具备不同的“状态”时,无需只为该实体定义一个数据模型。例如,用户“实体”就可能有包含password、包含password_hash 以及不含密码等多种状态。


[8]ページ先頭

©2009-2026 Movatter.jp