額外模型¶
延續前面的例子,通常會有多個相關的模型。
尤其是使用者模型,因為
- 輸入模型 需要能夠包含密碼。
- 輸出模型 不應該包含密碼。
- 資料庫模型 可能需要包含雜湊後的密碼。
多個模型¶
以下是如何使用密碼欄位以及它們使用位置的模型的大致概念
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
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()
我們現在在變數 user_dict
中有一個包含資料的 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 模型¶
如上例所示,我們從 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
和沒有密碼)。
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
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
的前面。
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]
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
)
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
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
)
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}
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
和無密碼。