FastAPI響應系列(一) 響應模型


一、基礎

  響應模型與請求體模型類似,請求體就是通過Pydantic創建請求體模型,可用於對請求內容進行校驗,響應模型就是對響應體進行校驗。可以在任意的路徑操作中使用response_model參數來聲明響應的模型:

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
    address: str = None
    full_name: Optional[str] = None


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


@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
    return user

  上面定義了兩個Pydantic模型,一個為請求體模型,另一個則是響應體模型。響應體模型在路徑操作中進行聲明,所以響應體數據會經過這個模型校驗,返回的數據是符合這個模型的數據。顯然響應體模型過濾掉了password字段。

2、response_model_exclude_unset

該參數的作用是表示默認值不包含在響應中,僅包含實際給的值,如果實際給的值與默認值相同也會包含在響應中。

...
@app.post("/user/", response_model=UserOut,response_model_exclude_unset=True)
async def create_user(user: UserIn):
    return user
...

此時如果發送的請求體內容是:

{
  "username": "zhangsan",
  "password": "123456",
  "email": "user@example.com"
}

顯然address和full_name的字段值都是默認 的None,這樣響應體不會返回這些默認值,實際響應內容為:

{
  "username": "zhangsan",
  "email": "user@example.com"
}

3、response_model_exclude

 顧名思義,該參數是排除響應模型中返回數據的字段:

...
@app.post("/user/", response_model=UserOut, response_model_exclude=["address"])
async def create_user(user: UserIn):
    return user
...

請求體:

{
  "username": "zhangsan",
  "password": "123456",
  "email": "user@example.com",
  "address": "張三村",
  "full_name": "張三"
}

響應體中已經排除了address字段:

{
  "username": "zhangsan",
  "email": "user@example.com",
  "full_name": "張三"
}

4、response_model_include

 該字段表示響應模型應該包含的字段:

...
@app.post("/user/", response_model=UserOut, response_model_include=["address"])
async def create_user(user: UserIn):
    return user
...

請求體:

{
  "username": "zhangsan",
  "password": "123456",
  "email": "user@example.com",
  "address": "張三村",
  "full_name": "張三"
}

響應體中只包含address字段:

{
  "address": "張三村"
}

注意:response_model_exclude和response_model_include的值本質是將列表轉成了set,所以如果使用這樣response_model_include={"address"}的格式完全沒問題。

二、進階

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
    address: str = None
    full_name: Optional[str] = None


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


class UserInDB(BaseModel):
    username: str
    hashed_password: str
    email: EmailStr
    address: str = None
    fullname: Optional[str] = None


def fake_password_hasher(raw_password: str):
    return "secret" + 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)
    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

在這個過程中:

  • 接受客戶端的用戶請求體信息
  • 對請求體內容進行校驗
  • 對請求體的密碼進行hash加密
  • 對數據庫用戶模型進行校驗
  • 獲取符合數據庫用戶模型的數據,並且存入數據庫
  • 向客戶端返回這個已經創建的用戶信息

 2、**user_in.dict()

 如果創建一個Pydantic模型,那么怎么創建一個模型對象呢?

...
user_in = UserIn(username="zhang san",
                     password="123456",
                     email="user@example.com",
                     address="zhang san street",
                     full_name="zhangsan")
...

  這樣就創建了一個UserIn模型的對象,顯然FastAPI中聲明UserIn類型,這樣會將請求體轉為Pydantic定義的UserIn的模型對象,也就是實現了上述過程。那么通過user_in.dict()又做了什么呢?顧名思義就是將上述user_in對象轉成key-value的字典格式,就是一個dict數據類型。

{
    'username': 'zhangsan',
    'password': '123456',
    'email': 'user@example.com',
    'address': 'zhangsanstreet',
    'full_name': 'zhangsan'
}

如果現在有這樣一個字典類型的數據,如何傳遞給Pydantic的模型類,使其創建一個模型類對象,Python使用了字典(dict)解包的方式。所以對於:

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

通過**來對字典解包,形成的結果就是:

UserInDB(
    username='zhangsan',
    password='123456',
    email='user@example.com',
    address='zhangsanstreet',
    full_name='zhangsan',
    hashed_password=hashed_password
)

但是通過UserInDB模型類的校驗后生成的最終結果就是:

UserInDB(
    username='zhangsan',
    hashed_password=hashed_password,
    email='user@example.com',
    address='zhangsanstreet',
    full_name='zhangsan'
)

注意:上述通過dict()方法可以將Pydantic模型對象轉成字典方式,另外也可以通過jsonable_encoder()方法來實現:

from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
from fastapi.encoders import jsonable_encoder

app = FastAPI()


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


@app.post("/user/")
async def create_user(user: UserIn):
    json_compatible_data = jsonable_encoder(user)
    print(type(json_compatible_data))  # <class 'dict'>
    return user
View Code

3、Union/List

  • Union
from typing import Optional, Union, List
...

@app.post("/response/model", response_model=Union[UserIn, UserOut])
async def response_model(user: UserIn):
    return user

...

Union的作用就是將Union中模型的字段取並集,顯然,如果只是UserOut模型,結果是不會返回password字段,但是把UserIn模型也放入Union中,取兩個模型並集后的字段作為響應模型進行校驗響應的user數據。

  • List
...
@app.post("/response/model", response_model=List[UserOut])
async def response_model():
    user_list = [
        {
            'username': 'zhangsan',
            'password': '123456',
            'email': 'user@example.com',
            'address': 'zhangsanstreet',
            'full_name': 'zhangsan'
        },
        {
            'username': 'lisi',
            'password': '123456',
            'email': 'user@example.com',
            'address': 'lisistreet',
            'full_name': 'lisi'
        }
    ]
    return user_list
...

這樣響應的內容是由對象構成的列表。其響應的內容:

[
  {
    "username": "zhangsan",
    "email": "user@example.com",
    "address": "zhangsanstreet",
    "full_name": "zhangsan"
  },
  {
    "username": "lisi",
    "email": "user@example.com",
    "address": "lisistreet",
    "full_name": "lisi"
  }
]

4、dict構成響應

 當不聲明任何響應的Pydantic模型時,此時並不知道有效的字段,那么可以通過聲明普通的鍵值對dict類型:

from typing import Dict
from fastapi import FastAPI

app = FastAPI()


@app.get("/apple/dict/", response_model=Dict[str, float])
async def apple_dict():
    return {"apple": 3.14, "pear": 5.12}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM