本节内容对应FastAPI手册的三节,分别是请求体-多个参数,请求体-字段和请求体-嵌套模型。
手册: https://fastapi.tiangolo.com/zh/tutorial/body-multiple-params/
源代码示例是python3.10及以上版本。
请求体 - 多个参数¶
既然我们已经知道了如何使用 Path
和 Query
,下面让我们来了解一下请求体声明的更高级用法。
混合使用 Path
、Query
和请求体参数¶
首先,毫无疑问地,你可以随意地混合使用 Path
、Query
和请求体参数声明,FastAPI 会知道该如何处理。你还可以通过将默认值设置为 None
来将请求体参数声明为可选参数:
from typing import Annotated
from fastapi import FastAPI, Path
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
q: str | None = None,
item: Item | None = None,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
if item:
results.update({"item": item})
return results
Note
请注意,在这种情况下,将从请求体获取的
item
是可选的。因为它的默认值为None
。
多个请求体参数¶
在上面的示例中,路径操作将期望一个具有 Item
的属性的 JSON 请求体,就像:
{ "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2 }
但是你也可以声明多个请求体参数,例如 item
和 user
:
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
class User(BaseModel):
username: str
full_name: str | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
results = {"item_id": item_id, "item": item, "user": user}
return results
在这种情况下,FastAPI 将注意到该函数中有多个请求体参数(两个 Pydantic 模型参数)。
因此,它将使用参数名称作为请求体中的键(字段名称),并期望一个类似于以下内容的请求体:
{ "item": { "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2 }, "user": { "username": "dave", "full_name": "Dave Grohl" } }
Note
请注意,即使
item
的声明方式与之前相同,但现在它被期望通过item
键内嵌在请求体中。
FastAPI 将自动对请求中的数据进行转换,因此 item
参数将接收指定的内容,user
参数也是如此。
它将执行对复合数据的校验,并且像现在这样为 OpenAPI 模式和自动化文档对其进行记录。
请求体中的单一值¶
与使用 Query
和 Path
为查询参数和路径参数定义额外数据的方式相同,FastAPI 提供了一个同等的 Body
。
例如,为了扩展先前的模型,你可能决定除了 item
和 user
之外,还想在同一请求体中具有另一个键 importance
。
如果你就按原样声明它,因为它是一个单一值,FastAPI 将假定它是一个查询参数。
但是你可以使用 Body
指示 FastAPI 将其作为请求体的另一个键进行处理。
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results
在这种情况下,FastAPI 将期望像这样的请求体:
{ "item": { "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2 }, "user": { "username": "dave", "full_name": "Dave Grohl" }, "importance": 5 }
同样的,它将转换数据类型,校验,生成文档等。
多个请求体参数和查询参数¶
当然,除了请求体参数外,你还可以在任何需要的时候声明额外的查询参数。
由于默认情况下单一值被解释为查询参数,因此你不必显式地添加 Query
,你可以仅执行以下操作:
q: str = None
比如:
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Item,
user: User,
importance: Annotated[int, Body(gt=0)],
q: str | None = None,
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
if q:
results.update({"q": q})
return results
Info
Body
同样具有与Query
、Path
以及其他后面将看到的类完全相同的额外校验和元数据参数。
嵌入单个请求体参数¶
假设你只有一个来自 Pydantic 模型 Item
的请求体参数 item
。
默认情况下,FastAPI 将直接期望这样的请求体。
但是,如果你希望它期望一个拥有 item
键并在值中包含模型内容的 JSON,就像在声明额外的请求体参数时所做的那样,则可以使用一个特殊的 Body
参数 embed
:
item: Item = Body(embed=True)
比如:
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
在这种情况下,FastAPI 将期望像这样的请求体:
{ "item": { "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2 } }
而不是:
{ "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2 }
总结¶
你可以添加多个请求体参数到路径操作函数中,即使一个请求只能有一个请求体。
但是 FastAPI 会处理它,在函数中为你提供正确的数据,并在路径操作中校验并记录正确的模式。
你还可以声明将作为请求体的一部分所接收的单一值。
你还可以指示 FastAPI 在仅声明了一个请求体参数的情况下,将原本的请求体嵌入到一个键中。
请求体 - 字段¶
与在路径操作函数中使用 Query
、Path
、Body
声明校验与元数据的方式一样,可以使用 Pydantic 的 Field
在 Pydantic 模型内部声明校验和元数据。
导入 Field
¶
首先,从 Pydantic 中导入 Field
:
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
"警告"
注意,与从
fastapi
导入Query
,Path
、Body
不同,要直接从pydantic
导入Field
。
声明模型属性¶
然后,使用 Field
定义模型的属性:
class Item(BaseModel):
name: str
description: str | None = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: float | None = None
Field
的工作方式和 Query
、Path
、Body
相同,参数也相同。
"技术细节"
实际上,
Query
、Path
都是Params
的子类,而Params
类又是 Pydantic 中FieldInfo
的子类。Pydantic 的
Field
返回也是FieldInfo
的类实例。
Body
直接返回的也是FieldInfo
的子类的对象。后文还会介绍一些Body
的子类。注意,从
fastapi
导入的Query
、Path
等对象实际上都是返回特殊类的函数。
"提示"
注意,模型属性的类型、默认值及
Field
的代码结构与路径操作函数的参数相同,只不过是用Field
替换了Path
、Query
、Body
。
添加更多信息¶
Field
、Query
、Body
等对象里可以声明更多信息,并且 JSON Schema 中也会集成这些信息。
声明示例一章中将详细介绍添加更多信息的知识。
小结¶
Pydantic 的 Field
可以为模型属性声明更多校验和元数据。
传递 JSON Schema 元数据还可以使用更多关键字参数。
请求体 - 嵌套模型¶
使用 FastAPI,你可以定义、校验、记录文档并使用任意深度嵌套的模型(归功于Pydantic)。
List 字段¶
你可以将一个属性定义为拥有子元素的类型。例如 Python list
:
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
成为一个由元素组成的列表。不过它没有声明每个元素的类型。
具有子类型的 List 字段¶
但是 Python 有一种特定的方法来声明具有子类型的列表:
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
从 typing 导入 List
¶
首先,从 Python 的标准库 typing
模块中导入 List
:
from typing import List, Union
声明具有子类型的 List¶
要声明具有子类型的类型,例如 list
、dict
、tuple
:
- 从
typing
模块导入它们 - 使用方括号
[
和]
将子类型作为「类型参数」传入
from typing import List
my_list: List[str]
这完全是用于类型声明的标准 Python 语法。
对具有子类型的模型属性也使用相同的标准语法。因此,在我们的示例中,我们可以将 tags
明确地指定为一个「字符串列表」:
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
Set 类型¶
但是随后我们考虑了一下,意识到标签不应该重复,它们很大可能会是唯一的字符串。
Python 具有一种特殊的数据类型来保存一组唯一的元素,即 set
。然后我们可以导入 Set
并将 tag
声明为一个由 str
组成的 set
:
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: set[str] = set()
这样,即使你收到带有重复数据的请求,这些数据也会被转换为一组唯一项。
而且,每当你输出该数据时,即使源数据有重复,它们也将作为一组唯一项输出。
并且还会被相应地标注 / 记录文档。
嵌套模型¶
Pydantic 模型的每个属性都具有类型。
但是这个类型本身可以是另一个 Pydantic 模型。
因此,你可以声明拥有特定属性名称、类型和校验的深度嵌套的 JSON 对象。
上述这些都可以任意的嵌套。
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
模型:
class Image(BaseModel):
url: str
name: str
将子模型用作类型¶
然后我们可以将其用作一个属性的类型:
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
这意味着 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 获得:
- 对被嵌入的模型也适用的编辑器支持(自动补全等)
- 数据转换
- 数据校验
- 自动生成文档
特殊的类型和校验¶
除了普通的单一值类型(如 str
、int
、float
等)外,你还可以使用从 str
继承的更复杂的单一值类型。
要了解所有的可用选项,请查看关于 来自 Pydantic 的外部类型 的文档。你将在下一章节中看到一些示例。例如,在 Image
模型中我们有一个 url
字段,我们可以把它声明为 Pydantic 的 HttpUrl
,而不是 str
:
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
该字符串将被检查是否为有效的 URL,并在 JSON Schema / OpenAPI 文档中进行记录。
带有一组子模型的属性¶
你还可以将 Pydantic 模型用作 list
、set
等的子类型:
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" } ] }
Info
请注意
images
键现在具有一组 image 对象是如何发生的。
深度嵌套模型¶
你可以定义任意深度的嵌套模型:
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
Info
请注意
Offer
拥有一组Item
而反过来Item
又是一个可选的Image
列表是如何发生的。
纯列表请求体¶
如果你期望的 JSON 请求体的最外层是一个 JSON array
(即 Python list
),则可以在路径操作函数的参数中声明此类型,就像声明 Pydantic 模型一样:
images: List[Image]
例如:
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
无处不在的编辑器支持¶
你可以随处获得编辑器支持。
即使是列表中的元素:
如果你直接使用 dict
而不是 Pydantic 模型,那你将无法获得这种编辑器支持。
但是你根本不必担心这两者,传入的字典会自动被转换,你的输出也会自动被转换为 JSON。
任意 dict
构成的请求体¶
你也可以将请求体声明为使用某类型的键和其他类型值的 dict
。
无需事先知道有效的字段/属性(在使用 Pydantic 模型的场景)名称是什么。
如果你想接收一些尚且未知的键,这将很有用。
其他有用的场景是当你想要接收其他类型的键时,例如 int
。
这也是我们在接下来将看到的。在下面的例子中,你将接受任意键为 int
类型并且值为 float
类型的 dict
:
from fastapi import FastAPI
app = FastAPI()
@app.post("/index-weights/")
async def create_index_weights(weights: dict[int, float]):
return weights
Tip
请记住 JSON 仅支持将
str
作为键。但是 Pydantic 具有自动转换数据的功能。
这意味着,即使你的 API 客户端只能将字符串作为键发送,只要这些字符串内容仅包含整数,Pydantic 就会对其进行转换并校验。
然后你接收的名为
weights
的dict
实际上将具有int
类型的键和float
类型的值。
总结¶
使用 FastAPI 你可以拥有 Pydantic 模型提供的极高灵活性,同时保持代码的简单、简短和优雅。
而且还具有下列好处:
- 编辑器支持(处处皆可自动补全!)
- 数据转换(也被称为解析/序列化)
- 数据校验
- 模式文档
- 自动生成的文档
实践
对于无基础的学习者,怎么对本次学习的三节内容进行实践操作是颇费脑筋的,因为根本不知道该怎么从浏览器里向FastAPI传送json数据。
看来还是要使用curl指令来测试了。若没有,可以安装curl,Ubuntu中使用apt install curl ,FreeBSD 中使用pkg install curl 。
嵌入单个请求体参数
代码存储在querypara.py文件
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
启动服务
uvicorn querypara:app --reload
请求
curl -X PUT "http://127.0.0.1:8000/items/5555" -H "Content-Type: application/json" -d '{ "item": { "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2 } }'
输出
curl -X PUT "http://127.0.0.1:8000/items/5555" -H "Content-Type: application/json" -d '{ "item": { "name": "Foo", "description": "The pretender", "price": 42.0, "tax": 3.2 } }'
{"item_id":5555,"item":{"name":"Foo","description":"The pretender","price":42.0,"tax":3.2}}
任意dict请求体
源代码存储在querynest.py文件;
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
启动服务
uvicorn querynest:app --reload
列表中单元素请求
curl -X POST "http://127.0.0.1:8000/images/multiple/" -H "Content-Type: application/json" -d '[{"url": "http://example.com/image1.jpg", "name": "Image1"}]'
注意,列表中元素结尾不能像python里面那样带逗号。
请求输出:
curl -X POST "http://127.0.0.1:8000/images/multiple/" -H "Content-Type: application/json" -d '[{"url": "http://example.com/image1.jpg", "name": "Image1"}]'
[{"url":"http://example.com/image1.jpg","name":"Image1"}]
列表多元素请求
curl -X POST "http://127.0.0.1:8000/images/multiple/" -H "Content-Type: application/json" -d '[{"url": "http://example.com/image1.jpg", "name": "Image 1"}, {"url": "http://example.com/image2.jpg", "name": "Image 2"}]'
请求输出
curl -X POST "http://127.0.0.1:8000/images/multiple/" -H "Content-Type: application/json" -d '[{"url": "http://example.com/image1.jpg", "name": "Image 1"}, {"url": "http://example.com/image2.jpg", "name": "Image 2"}]'
[{"url":"http://example.com/image1.jpg","name":"Image 1"},{"url":"http://example.com/image2.jpg","name":"Image 2"}]
总结
可以直接将json数据传给FastAPI,使用post或put等。在声明参数类型后,系统内部会对参数进行合法验证,一次声明即可,非常简洁方便。
标签:None,name,item,FastAPI,三周,str,id,嵌套 From: https://blog.csdn.net/skywalk8163/article/details/143174104