首页 > 其他分享 >三周精通FastAPI:8 请求体 - 多个参数、字段、嵌套模型

三周精通FastAPI:8 请求体 - 多个参数、字段、嵌套模型

时间:2024-10-26 14:20:00浏览次数:3  
标签:None name item FastAPI 三周 str id 嵌套

本节内容对应FastAPI手册的三节,分别是请求体-多个参数,请求体-字段和请求体-嵌套模型。

手册: https://fastapi.tiangolo.com/zh/tutorial/body-multiple-params/

源代码示例是python3.10及以上版本。

请求体 - 多个参数

既然我们已经知道了如何使用 Path 和 Query,下面让我们来了解一下请求体声明的更高级用法。

混合使用 PathQuery 和请求体参数

首先,毫无疑问地,你可以随意地混合使用 PathQuery 和请求体参数声明,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 同样具有与 QueryPath 以及其他后面将看到的类完全相同的额外校验和元数据参数。

嵌入单个请求体参数

假设你只有一个来自 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 在仅声明了一个请求体参数的情况下,将原本的请求体嵌入到一个键中。

请求体 - 字段

与在路径操作函数中使用 QueryPath 、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 导入 QueryPathBody 不同,要直接从 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 的工作方式和 QueryPathBody 相同,参数也相同。

"技术细节"

实际上,QueryPath 都是 Params 的子类,而 Params 类又是 Pydantic 中 FieldInfo 的子类。

Pydantic 的 Field 返回也是 FieldInfo 的类实例。

Body 直接返回的也是 FieldInfo 的子类的对象。后文还会介绍一些 Body 的子类。

注意,从 fastapi 导入的 QueryPath 等对象实际上都是返回特殊类的函数。

"提示"

注意,模型属性的类型、默认值及 Field 的代码结构与路径操作函数的参数相同,只不过是用 Field 替换了PathQueryBody

添加更多信息

FieldQueryBody 等对象里可以声明更多信息,并且 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

要声明具有子类型的类型,例如 listdicttuple

  • 从 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 获得:

  • 对被嵌入的模型也适用的编辑器支持(自动补全等)
  • 数据转换
  • 数据校验
  • 自动生成文档

特殊的类型和校验

除了普通的单一值类型(如 strintfloat 等)外,你还可以使用从 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 模型用作 listset 等的子类型:

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

相关文章

  • 三周精通FastAPI:10 Cookie 参数 和Cookie 参数模型
    官方文档:Cookie参数-FastAPICookie参数¶定义 Cookie 参数与定义 Query 和 Path 参数一样。源码:fromtypingimportAnnotatedfromfastapiimportCookie,FastAPIapp=FastAPI()@app.get("/items/")asyncdefread_items(ads_id:Annotated[str|Non......
  • 浅拷贝与深拷贝 以及嵌套和递归使用模板类
     1.浅拷贝和深拷贝——对象和类浅拷贝浅拷贝只复制对象的基本属性,而不复制引用类型的属性指向的对象,即只复制引用而不复制引用指向的对象在浅拷贝中,新对象与原始对象共享引用类型的属性指向的对象,即它们指向同一个对象。编译器提供的拷贝构造函数是浅拷贝,当一个对象修......
  • Day 11 函数对象 + 函数的嵌套 + 名称空间与作用域
    目录0昨日复习0.1函数0.2定义0.3三种形式的函数0.3.1无参函数0.3.2有参函数0.3.3空函数0.4函数的返回值0.5函数的调用0.6函数参数的应用0.6.1形参0.6.2实参0.6.3位置形参0.6.4位置实参0.6.5默认形参0.6.6关键字实参0.7可变长参数0.7.1*形参0.7.2*实参0.7.3**......
  • 使用yield压平嵌套字典有多简单?
    我们经常遇到各种字典套字典的数据,例如:nest_dict = {    'a': 1,    'b': {        'c': 2,        'd': 3,        'e': {'f': 4}    },    'g': {'h': 5},    'i': 6,    'j�......
  • 【FastAPI】线上部署
    1.编写代码并且上传到线上仓库https://gitee.com/xiao-wenliang/fastapi_demo.git2.根目录下,data文件夹下克隆项目3.创建虚拟环境并且下载第三方模块3.1:创建虚拟环境3.2:下载第三方模块3.3:安装pipinstallgunicornvirtualenv/envs/fastapi_demo--pyt......
  • 嵌套元素的“事件”冒泡?!——WEB开发系列52
    事件处理是创建交互式用户界面的关键部分,浏览器通过事件系统让我们能够捕获和响应用户的输入,比如点击、鼠标移动、键盘输入等。什么是事件冒泡?事件冒泡是指在嵌套的HTML元素中,一个事件从最具体的元素开始,然后向上传播到更高层级的父元素。例如,如果用户点击一个嵌套的按钮,事件首先......
  • 三周精通FastAPI:6 路径参数和数值校验
    路径参数和数值校验¶与使用 Query 为查询参数声明更多的校验和元数据的方式相同,你也可以使用 Path 为路径参数声明相同类型的校验和元数据。导入路径Path¶首先,从 fastapi 导入 Path:fromtypingimportAnnotatedfromfastapiimportFastAPI,Path,Querya......
  • 三周精通FastAPI:7 查询参数模型
    查询参数模型如果你有一组相关的查询参数,你可以创建一个Pydantic模型来声明它们。这将允许您在多个地方重用模型,并一次声明所有参数的验证和元数据。......
  • C语言使用指针作为函数参数,并利用函数嵌套求输入三个整数,将它们按大到小的顺序输出。(
    输入三个整数,要求从大到小的顺序向他们输出,用函数实现。   本代码使用到了指针和函数嵌套。   调用指针做函数ex,并嵌套调用指针函数exx在函数ex中。(代码在下面哦!)一、关于函数 ex  1. 这个函数接受三个指针参数 int*p1 、 int*p2 和 int*p3 ,分别指......
  • 数据库系统-07-SQL查询语句5-嵌套子查询
    一、嵌套子查询1.概念:子查询是嵌套在另一个查询中的select-from-where表达式。子查询通常被用来对集合成员资格、集合的比较以及集合的基数进行检查2.集合的成员资格(1)概念:SQL允许测试元组在关系中的成员资格。连接词in测试元组是否是集合中的成员,集合是由select子句产生的......