请求体 - 嵌套模型¶
基于 Pydantic,FastAPI 能够定义、校验、存档、使用任意深度的嵌套模型。
List 字段¶
模型属性可以定义为子类型。例如,Python 列表:
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: list = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
tags
是由多个元素组成的列表。但上例未声明列表内元素的类型。
带类型参数的列表字段¶
Python 可以声明包含内部类型(类型参数)的列表:
导入 typing 的 List
¶
Python 3.9 以上版本中,可以使用标准的 list
声明下述类型注释。
但在 Python 3.6 版之后及 3.9 版之前,首先要从 Python 的 typing
模块中导入 List
:
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
声明带类型参数的 list
¶
声明包含 list
、dict
、tuple
等类型参数(内部类型)的类型:
- 如果 Python 的版本低于 3.9,则要从
typing
模块导入等效的版本。 - 使用方括号
[]
传递类型参数,即内部类型。
Python 3.9 中的代码如下所示:
my_list: list[str]
Python 3.9 之前的版本中,如下所示:
from typing import List
my_list: List[str]
这些都是标准的 Python 类型声明语法。
包含内部类型的模型属性也使用这些标准语法。
本例把 tags
声明为字符串列表:
from typing import List, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: List[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: list[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
集合类型 ~ Set¶
标签(tags)不能重复,每个标签字符串都应该是唯一的。
Python 提供了专门保存一组唯一元素的数据类型,集合(set
)。
导入 Set
,并把 tags
声明为由 str
组成的 set
:
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
收到的请求中包含重复数据时,会被转换为只包含唯一元素的集合。
而且,每次输出数据时,即使源数据中有重复项,输出的也是只包含唯一元素的集合。
并且还会在文档中进行相应地注释/存档。
嵌套模型¶
Pydantic 模型的每个属性都有自己的类型。
而且,这些属性的类型也可以是 Pydantic 模型。
因此,Pydantic 模型可以声明拥有特定属性名、类型和校验的深度嵌套 JSON 对象。
所有这些对象,都可以任意嵌套。
定义子模型¶
例如,定义一个 Image
模型:
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
用子模型作为类型¶
然后,把 Image
模型声明为属性的类型:
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
FastAPI 返回如下请求体:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": ["rock", "metal", "bar"],
"image": {
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
}
}
再次强调一下,声明之后,就可以通过 FastAPI 获得:
- 编辑器对嵌套模型的自动补全等支持
- 数据转换
- 数据校验
- API 文档
特殊类型与校验¶
除了 str
、int
、float
等普通单值类型外,还可以使用从 str
继承的复杂单值类型。
所有选项详见 Pydantic 官档 - 外部类型。下一章介绍一些示例。
例如,把 Image
模型的 url
字段声明为 Pydantic 的 HttpUrl
,而不是 str
:
from typing import Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
image: Union[Image, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
image: Image | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
FastAPI 校验该字符串是否为有效的 URL,并在 JSON Schema / OpenAPI 中存档。
包含子模型列表的属性¶
list
、set
的子类型也可以是 Pydantic 模型:
from typing import List, Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
images: Union[List[Image], None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
images: Union[list[Image], None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
JSON 请求体以如下方式转换、校验并存档:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2,
"tags": [
"rock",
"metal",
"bar"
],
"images": [
{
"url": "http://example.com/baz.jpg",
"name": "The Foo live"
},
{
"url": "http://example.com/dave.jpg",
"name": "The Baz"
}
]
}
说明
注意,images
键中包含了 image 对象列表。
深度嵌套模型¶
定义任意深度的嵌套模型:
from typing import List, Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
images: Union[List[Image], None] = None
class Offer(BaseModel):
name: str
description: Union[str, None] = None
price: float
items: List[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: set[str] = set()
images: Union[list[Image], None] = None
class Offer(BaseModel):
name: str
description: Union[str, None] = None
price: float
items: list[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
images: list[Image] | None = None
class Offer(BaseModel):
name: str
description: str | None = None
price: float
items: list[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
说明
注意,Offer
中嵌套了 Item
列表,而 Item
又嵌套了可选的 Image
列表。
纯列表请求体¶
JSON 请求体的最外层是 JSON array
( Python 列表)时,在路径操作函数的参数中,可以使用与声明 Pydantic 模型相同的方式声明该参数的类型:
images: List[Image]
在 Python 3.9 及以上版本中为:
images: list[Image]
具体如以下代码所示:
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
@app.post("/images/multiple/")
async def create_multiple_images(images: List[Image]):
return images
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
@app.post("/images/multiple/")
async def create_multiple_images(images: list[Image]):
return images
无处不在的编辑器支持¶
编辑器的支持无处不在。
即使是列表中的元素:
直接使用字典,不使用 Pydantic 模型时,无法获得这种编辑器支持。
但不必担心,传入的字典会被自动转换,输出数据也会被自动转换为 JSON。
由字典构成的请求体¶
请求体可以声明为字典,并且字典的键与值可以声明为不同类型。
因为使用了 Pydantic 模型,不必事先知道可用的字段 / 属性名。
接收未知的键时,这种方式很有用。
其他用例包括使用 int
作为键的类型。
示例如下。
本例中,路径操作函数可以接收键的类型是 int
,值的类型是 float
的字典:
from typing import Dict
from fastapi import FastAPI
app = FastAPI()
@app.post("/index-weights/")
async def create_index_weights(weights: Dict[int, float]):
return weights
from fastapi import FastAPI
app = FastAPI()
@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
return weights
提示
注意,JSON 的键只支持字符串。
但 Pydantic 可以自动转换数据类型。
也就是说,即使 API 客户端只能发送字符串类型的键,但只要这些字符串只包含整数,Pydantic 就能转换并校验该键。
这样一来,字典 weights
的键的类型就是 int
,而值的类型则为 float
。
小结¶
FastAPI 拥有 Pydantic 模型的高度灵活性,还能让代码更加简短、优雅。
并且支持以下功能:
- 编辑器支持(无处不在的自动补全!)
- 数据转换(即解析/序列化)
- 数据校验
- 概图存档
- API 文档