FastAPI教程 額外的模型

2022-08-20 11:41 更新

我們從前面的示例繼續(xù),擁有多個(gè)相關(guān)的模型是很常見的。

對(duì)用戶模型來說尤其如此,因?yàn)椋?/p>

  • 輸入模型需要擁有密碼屬性。
  • 輸出模型不應(yīng)該包含密碼。
  • 數(shù)據(jù)庫模型很可能需要保存密碼的哈希值。
Danger
永遠(yuǎn)不要存儲(chǔ)用戶的明文密碼。始終存儲(chǔ)一個(gè)可以用于驗(yàn)證的「安全哈希值」。
如果你尚未了解該知識(shí),你可以在安全章節(jié)中學(xué)習(xí)何為「密碼哈希值」。

多個(gè)模型

下面是應(yīng)該如何根據(jù)它們的密碼字段以及使用位置去定義模型的大概思路:

from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: Optional[str] = None


class UserOut(BaseModel):
    username: str
    email: EmailStr
    full_name: Optional[str] = None


class UserInDB(BaseModel):
    username: str
    hashed_password: str
    email: EmailStr
    full_name: Optional[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

關(guān)于 **user_in.dict()

Pydantic 的 .dict()

user_in 是一個(gè) UserIn 類的 Pydantic 模型.

Pydantic 模型具有 .dict() 方法,該方法返回一個(gè)擁有模型數(shù)據(jù)的 dict。

因此,如果我們像下面這樣創(chuàng)建一個(gè) Pydantic 對(duì)象 user_in:

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

然后我們調(diào)用:

user_dict = user_in.dict()

現(xiàn)在我們有了一個(gè)數(shù)據(jù)位于變量 user_dict 中的 dict(它是一個(gè) dict 而不是 Pydantic 模型對(duì)象)。

如果我們調(diào)用:

print(user_dict)

我們將獲得一個(gè)這樣的 Python dict:

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

解包 dict

如果我們將 user_dict 這樣的 dict 以 **user_dict 形式傳遞給一個(gè)函數(shù)(或類),Python將對(duì)其進(jìn)行「解包」。它會(huì)將 user_dict 的鍵和值作為關(guān)鍵字參數(shù)直接傳遞。

因此,從上面的 user_dict 繼續(xù),編寫:

UserInDB(**user_dict)

會(huì)產(chǎn)生類似于以下的結(jié)果:

UserInDB(
    username="john",
    password="secret",
    email="john.doe@example.com",
    full_name=None,
)

或者更確切地,直接使用 user_dict 來表示將來可能包含的任何內(nèi)容:

UserInDB(
    username = user_dict["username"],
    password = user_dict["password"],
    email = user_dict["email"],
    full_name = user_dict["full_name"],
)

來自于其他模型內(nèi)容的 Pydantic 模型

如上例所示,我們從 user_in.dict() 中獲得了 user_dict,此代碼:

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

等同于:

UserInDB(**user_in.dict())

...因?yàn)?nbsp;user_in.dict() 是一個(gè) dict,然后我們通過以**開頭傳遞給 UserInDB 來使 Python「解包」它。

這樣,我們獲得了一個(gè)來自于其他 Pydantic 模型中的數(shù)據(jù)的 Pydantic 模型。

解包 dict 和額外關(guān)鍵字

然后添加額外的關(guān)鍵字參數(shù) hashed_password=hashed_password,例如:

UserInDB(**user_in.dict(), hashed_password=hashed_password)

...最終的結(jié)果如下:

UserInDB(
    username = user_dict["username"],
    password = user_dict["password"],
    email = user_dict["email"],
    full_name = user_dict["full_name"],
    hashed_password = hashed_password,
)

Warning

輔助性的額外函數(shù)只是為了演示可能的數(shù)據(jù)流,但它們顯然不能提供任何真正的安全性。

減少重復(fù)

減少代碼重復(fù)是 FastAPI 的核心思想之一。

因?yàn)榇a重復(fù)會(huì)增加出現(xiàn) bug、安全性問題、代碼失步問題(當(dāng)你在一個(gè)位置更新了代碼但沒有在其他位置更新)等的可能性。

上面的這些模型都共享了大量數(shù)據(jù),并擁有重復(fù)的屬性名稱和類型。

我們可以做得更好。

我們可以聲明一個(gè) UserBase 模型作為其他模型的基類。然后我們可以創(chuàng)建繼承該模型屬性(類型聲明,校驗(yàn)等)的子類。

所有的數(shù)據(jù)轉(zhuǎn)換、校驗(yàn)、文檔生成等仍將正常運(yùn)行。

這樣,我們可以僅聲明模型之間的差異部分(具有明文的 password、具有 hashed_password 以及不包括密碼)。

from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserBase(BaseModel):
    username: str
    email: EmailStr
    full_name: Optional[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

你可以將一個(gè)響應(yīng)聲明為兩種類型的 Union,這意味著該響應(yīng)將是兩種類型中的任何一種。

這將在 OpenAPI 中使用 anyOf 進(jìn)行定義。

為此,請(qǐng)使用標(biāo)準(zhǔn)的 Python 類型提示 typing.Union

Note

定義一個(gè) Union 類型時(shí),首先包括最詳細(xì)的類型,然后是不太詳細(xì)的類型。在下面的示例中,更詳細(xì)的 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])
async def read_item(item_id: str):
    return items[item_id]

模型列表

你可以用同樣的方式聲明由對(duì)象列表構(gòu)成的響應(yīng)。

為此,請(qǐng)使用標(biāo)準(zhǔn)的 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 構(gòu)成的響應(yīng)

你還可以使用一個(gè)任意的普通 dict 聲明響應(yīng),僅聲明鍵和值的類型,而不使用 Pydantic 模型。

如果你事先不知道有效的字段/屬性名稱(對(duì)于 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}

總結(jié)

使用多個(gè) Pydantic 模型,并針對(duì)不同場(chǎng)景自由地繼承。

如果一個(gè)實(shí)體必須能夠具有不同的「狀態(tài)」,你無需為每個(gè)狀態(tài)的實(shí)體定義單獨(dú)的數(shù)據(jù)模型。以用戶「實(shí)體」為例,其狀態(tài)有包含 password、包含 password_hash 以及不含密碼。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)