FastAPI基礎之 額外的響應模型

ckxllf發表於2021-04-27

  我們從前面的示例繼續,擁有多個相關的模型是很常見的。

  對使用者模型來說尤其如此,因為:

  輸入模型需要擁有密碼屬性。

  輸出模型不應該包含密碼。

  資料庫模型很可能需要儲存密碼的雜湊值。

  Danger

  永遠不要儲存使用者的明文密碼。始終儲存一個可以用於驗證的「安全雜湊值」。

  如果你尚未了解該知識,你可以在安全章節中學習何為「密碼雜湊值」。

  1、多個模型

  下面是應該如何根據它們的密碼欄位以及使用位置去定義模型的大概思路:

  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

  2、關於 **user_in.dict()

  2.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")

  然後我們呼叫:

  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,

  }

  2.2 解包 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"],

  )

  2.3 來自於其他模型內容的 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 模型。

  2.4 解包 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,

  )

  Warning

  輔助性的額外函式只是為了演示可能的資料流,但它們顯然不能提供任何真正的安全性。

  3、減少重複

  減少程式碼重複是 FastAPI 的核心思想之一。

  因為程式碼重複會增加出現 bug、安全性問題、程式碼失步問題(當你在一個位置更新了程式碼但沒有在其他位置更新)等的可能性。

  上面的這些模型都共享了大量資料,並擁有重複的屬性名稱和型別。

  我們可以做得更好。

  我們可以宣告一個 UserBase 模型作為其他模型的基類。然後我們可以建立繼承該模型屬性(型別宣告,校驗等)的子類。

  所有的資料轉換、校驗、文件生成等仍將正常執行。

  這樣,我們可以僅宣告模型之間的差異部分(具有明文的 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

  4、Union 或者 anyOf

  你可以將一個響應宣告為兩種型別的 Union,這意味著該響應將是兩種型別中的任何一種。

  這將在 OpenAPI 中使用 anyOf 進行定義。

  為此,請使用標準的 Python 型別提示 typing.Union:

  Note 大連做人流多少錢

  定義一個 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])

  async def read_item(item_id: str):

  return items[item_id]

  5、模型列表

  你可以用同樣的方式宣告由物件列表構成的響應。為此,請使用標準的 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

  6、任意 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}


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69945560/viewspace-2770176/,如需轉載,請註明出處,否則將追究法律責任。

相關文章