FastAPI | 教程 | 请求体-嵌套模型

https://fastapi.tiangolo.com/tutorial/body-nested-models/

请求体-嵌套模型

使用FastAPI,您可以定义,验证,记录和使用任意深度嵌套的模型(这要归功于Pydantic)。

List fields

您可以将属性定义为子类型。例如,Python list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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: Optional[float] = 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被定义为item里面的一个列表。

具有类型参数的List fields

Python使用一种特定的方式来声明具有内部类型或“类型参数”的列表:

typing导入 List

首先,List从标准Python typing模块导入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results

声明一个有类型参数的List

声明一个有类型参数(子类型)的types,如listdicttuple

  • typing模块导入它们
  • 使用方括号将内部类型作为“类型参数”传递:[]
1
2
3
from typing import List

my_list: List[str]

这就是Python下面所有类型声明的标准语法。

所有具有内部类型的模型属性都使用相同的标准语法。

因此,在我们的示例中,tags就是一个“字符串列表”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results

集合类型

但是,理论上"tags"不应该重复,它们可能是独一无二的。

这个时候set出现了,它是Python用于一组唯一项的特殊数据类型。

然后我们可以导入Set并声明tags为具有str类型的set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from typing import Optional, Set

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: 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

这样,即使收到带有重复数据的请求,该请求也将被转换为一组唯一的item。

即使源重复,每当输出该数据时,它们也将作为一组唯一项输出。

它也将相应地被注释/记录。

嵌套模型

Pydantic model 的每个属性都有一个类型。

但是该类型本身可以是另一种Pydantic model 。

因此,您可以使用特定的属性名、类型和验证来声明深度嵌套的JSON“对象”。

这些都是可以任意嵌套的。

定义一个子模型

例如,我们可以定义一个Image模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from typing import Optional, Set

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
url: str
name: str


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


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results

使用子模型作为类型

然后我们可以将其用作属性的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from typing import Optional, Set

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Image(BaseModel):
url: str
name: str


class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
image: Optional[Image] = 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将期望类似于以下内容的请求体:

1
2
3
4
5
6
7
8
9
10
11
{
"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进行声明即可:

  • 编辑器支持(completion, etc),哪怕是嵌套模型
  • 数据转换
  • 数据验证
  • 自动文档

特殊类型和验证

除了str、int、float等普通的单数类型外,还可以使用从str继承的更复杂的单数类型。

要查看所有选项,请查看Pydantic’s exotic types。您将在下一章中看到一些示例。

例如,在Image模型中,我们有一个url字段,我们可以将其声明为一个不同的str,Pydantic的HttpUrl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from typing import Optional, 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: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
image: Optional[Image] = 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等的子类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from typing import List, Optional, 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: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
images: Optional[List[Image]] = 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请求体,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"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对象的列表

深层嵌套模型

您可以定义任意深度嵌套的模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from typing import List, Optional, 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: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = []
images: Optional[List[Image]] = None


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


@app.post("/offers/")
async def create_offer(offer: Offer):
return offer

offer有一个item的列表,而item也有一个可以为空的image列表

Bodies of pure lists

如果您期望JSON请求体的根节点JSON array(Python list),则可以在函数的参数中声明类型,与Pydantic模型相同:

1
images: List[Image]

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

无处不在的编辑器支持

你可以在任何地方得到编译器的支持。

即使是列表中的子类型中:

img

如果直接使用dict而不是Pydantic模型,则无法获得这种编辑器支持。

而且,传入的dict会自动转换,输出也会自动转换为JSON。

任意dictS的实体

您还可以将一个主体声明为具有某种类型的键和其他类型的值的dict

不必事先知道什么是有效的字段/属性名(Pydantic模型就是这样)。

如果您想接收未定义的keys,这将非常有用。


当你想要其他类型的keys,例如int的时候。

我们需要看一下下面这段代码。

在这种情况下,你可以接受任何key为int,value是float类型的dict

1
2
3
4
5
6
7
8
9
10
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

请记住,JSON仅支持str作为keys

但是Pydantic具有自动数据转换功能。

这意味着,即使API客户机只能将字符串作为keys发送,但是只要这些字符串包含纯整数,Pydantic就可以转换并验证它们。

但是你接收到的字典,必须包含int类型的key和float类型的value。

总结

使用**FastAPI,**您将拥有Pydantic模型提供的最大灵活性,同时可以使您的代码简单,简短,美观。

可以得到这些好处:

  • 编辑器支持(无处不在!)
  • 数据转换(又名解析/序列化)
  • 数据验证
  • Schema文档
  • 自动文档