FastAPI教程 響應(yīng)模型

2022-08-20 11:39 更新

你可以在任意的路徑操作中使用 response_model 參數(shù)來聲明用于響應(yīng)的模型:

  • @app.get()
  • @app.post()
  • @app.put()
  • @app.delete()
  • 等等。
from typing import List, Optional

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None
    tags: List[str] = []


@app.post("/items/", response_model=Item)
async def create_item(item: Item):
    return item

Note

注意,response_model是「裝飾器」方法(get,post 等)的一個(gè)參數(shù)。不像之前的所有參數(shù)和請(qǐng)求體,它不屬于路徑操作函數(shù)。

它接收的類型與你將為 Pydantic 模型屬性所聲明的類型相同,因此它可以是一個(gè) Pydantic 模型,但也可以是一個(gè)由 Pydantic 模型組成的 list,例如 List[Item]。

FastAPI 將使用此 response_model 來:

  • 將輸出數(shù)據(jù)轉(zhuǎn)換為其聲明的類型。
  • 校驗(yàn)數(shù)據(jù)。
  • 在 OpenAPI 的路徑操作中為響應(yīng)添加一個(gè) JSON Schema。
  • 并在自動(dòng)生成文檔系統(tǒng)中使用。

但最重要的是:

  • 會(huì)將輸出數(shù)據(jù)限制在該模型定義內(nèi)。下面我們會(huì)看到這一點(diǎn)有多重要。

技術(shù)細(xì)節(jié)

響應(yīng)模型在參數(shù)中被聲明,而不是作為函數(shù)返回類型的注解,這是因?yàn)槁窂胶瘮?shù)可能不會(huì)真正返回該響應(yīng)模型,而是返回一個(gè) dict、數(shù)據(jù)庫對(duì)象或其他模型,然后再使用 response_model 來執(zhí)行字段約束和序列化。

返回與輸入相同的數(shù)據(jù)

現(xiàn)在我們聲明一個(gè) UserIn 模型,它將包含一個(gè)明文密碼屬性。

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


# Don't do this in production!
@app.post("/user/", response_model=UserIn)
async def create_user(user: UserIn):
    return user

我們正在使用此模型聲明輸入數(shù)據(jù),并使用同一模型聲明輸出數(shù)據(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


# Don't do this in production!
@app.post("/user/", response_model=UserIn)
async def create_user(user: UserIn):
    return user

現(xiàn)在,每當(dāng)瀏覽器使用一個(gè)密碼創(chuàng)建用戶時(shí),API 都會(huì)在響應(yīng)中返回相同的密碼。

在這個(gè)案例中,這可能不算是問題,因?yàn)橛脩糇约赫诎l(fā)送密碼。

但是,如果我們?cè)谄渌穆窂讲僮髦惺褂孟嗤哪P?,則可能會(huì)將用戶的密碼發(fā)送給每個(gè)客戶端。

Danger

永遠(yuǎn)不要存儲(chǔ)用戶的明文密碼,也不要在響應(yīng)中發(fā)送密碼。

添加輸出模型

相反,我們可以創(chuàng)建一個(gè)有明文密碼的輸入模型和一個(gè)沒有明文密碼的輸出模型:

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


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

這樣,即便我們的路徑操作函數(shù)將會(huì)返回包含密碼的相同輸入用戶:

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


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

...我們已經(jīng)將 response_model 聲明為了不包含密碼的 UserOut 模型:

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


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

因此,F(xiàn)astAPI 將會(huì)負(fù)責(zé)過濾掉未在輸出模型中聲明的所有數(shù)據(jù)(使用 Pydantic)。

在文檔中查看

當(dāng)你查看自動(dòng)化文檔時(shí),你可以檢查輸入模型和輸出模型是否都具有自己的 JSON Schema:

并且兩種模型都將在交互式 API 文檔中使用:

響應(yīng)模型編碼參數(shù)

你的響應(yīng)模型可以具有默認(rèn)值,例如:

from typing import List, Optional

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: float = 10.5
    tags: List[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]
  • description: Optional[str] = None 具有默認(rèn)值 None。
  • tax: float = 10.5 具有默認(rèn)值 10.5.
  • tags: List[str] = [] 具有一個(gè)空列表作為默認(rèn)值: [].

但如果它們并沒有存儲(chǔ)實(shí)際的值,你可能想從結(jié)果中忽略它們的默認(rèn)值。

舉個(gè)例子,當(dāng)你在 NoSQL 數(shù)據(jù)庫中保存了具有許多可選屬性的模型,但你又不想發(fā)送充滿默認(rèn)值的很長的 JSON 響應(yīng)。

使用 response_model_exclude_unset 參數(shù)

你可以設(shè)置路徑操作裝飾器的 response_model_exclude_unset=True 參數(shù):

from typing import List, Optional

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: float = 10.5
    tags: List[str] = []


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
    "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}


@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
    return items[item_id]

然后響應(yīng)中將不會(huì)包含那些默認(rèn)值,而是僅有實(shí)際設(shè)置的值。

因此,如果你向路徑操作發(fā)送 ID 為 foo 的商品的請(qǐng)求,則響應(yīng)(不包括默認(rèn)值)將為:

{
    "name": "Foo",
    "price": 50.2
}

Info

FastAPI 通過 Pydantic 模型的 .dict() 配合 該方法的 exclude_unset 參數(shù) 來實(shí)現(xiàn)此功能。

Info

你還可以使用:

  • response_model_exclude_defaults=True
  • response_model_exclude_none=True

參考 Pydantic 文檔 中對(duì) exclude_defaults 和 exclude_none 的描述。

默認(rèn)值字段有實(shí)際值的數(shù)據(jù)

但是,如果你的數(shù)據(jù)在具有默認(rèn)值的模型字段中有實(shí)際的值,例如 ID 為 bar 的項(xiàng):

{
    "name": "Bar",
    "description": "The bartenders",
    "price": 62,
    "tax": 20.2
}

這些值將包含在響應(yīng)中。

具有與默認(rèn)值相同值的數(shù)據(jù)

如果數(shù)據(jù)具有與默認(rèn)值相同的值,例如 ID 為 baz 的項(xiàng):

{
    "name": "Baz",
    "description": None,
    "price": 50.2,
    "tax": 10.5,
    "tags": []
}

即使 description、tax 和 tags 具有與默認(rèn)值相同的值,F(xiàn)astAPI 足夠聰明 (實(shí)際上是 Pydantic 足夠聰明) 去認(rèn)識(shí)到這一點(diǎn),它們的值被顯式地所設(shè)定(而不是取自默認(rèn)值)。

因此,它們將包含在 JSON 響應(yīng)中。

Tip

請(qǐng)注意默認(rèn)值可以是任何值,而不僅是None。

它們可以是一個(gè)列表([]),一個(gè)值為 10.5的 float,等等。

response_model_include 和 response_model_exclude

你還可以使用路徑操作裝飾器的 response_model_include 和 response_model_exclude 參數(shù)。

它們接收一個(gè)由屬性名稱 str 組成的 set 來包含(忽略其他的)或者排除(包含其他的)這些屬性。

如果你只有一個(gè) Pydantic 模型,并且想要從輸出中移除一些數(shù)據(jù),則可以使用這種快捷方法。

Tip

但是依然建議你使用上面提到的主意,使用多個(gè)類而不是這些參數(shù)。

這是因?yàn)榧词故褂?nbsp;response_model_include 或 response_model_exclude 來省略某些屬性,在應(yīng)用程序的 OpenAPI 定義(和文檔)中生成的 JSON Schema 仍將是完整的模型。

這也適用于作用類似的 response_model_by_alias。

from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: float = 10.5


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


@app.get(
    "/items/{item_id}/name",
    response_model=Item,
    response_model_include={"name", "description"},
)
async def read_item_name(item_id: str):
    return items[item_id]


@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude={"tax"})
async def read_item_public_data(item_id: str):
    return items[item_id]

Tip

{"name", "description"} 語法創(chuàng)建一個(gè)具有這兩個(gè)值的 set。

等同于 set(["name", "description"])。

使用 list 而不是 set

如果你忘記使用 set 而是使用 list 或 tuple,F(xiàn)astAPI 仍會(huì)將其轉(zhuǎn)換為 set 并且正常工作:

from typing import Optional

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: float = 10.5


items = {
    "foo": {"name": "Foo", "price": 50.2},
    "bar": {"name": "Bar", "description": "The Bar fighters", "price": 62, "tax": 20.2},
    "baz": {
        "name": "Baz",
        "description": "There goes my baz",
        "price": 50.2,
        "tax": 10.5,
    },
}


@app.get(
    "/items/{item_id}/name",
    response_model=Item,
    response_model_include=["name", "description"],
)
async def read_item_name(item_id: str):
    return items[item_id]


@app.get("/items/{item_id}/public", response_model=Item, response_model_exclude=["tax"])
async def read_item_public_data(item_id: str):
    return items[item_id]

總結(jié)

使用路徑操作裝飾器的 response_model 參數(shù)來定義響應(yīng)模型,特別是確保私有數(shù)據(jù)被過濾掉。

使用 response_model_exclude_unset 來僅返回顯式設(shè)定的值。


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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)