第二十九章:FastAPI
一:FastAPI
1.FastAPI介绍
和 Flask 类似,FastAPI 也是一个 web 框架,对于实现 Restful API 非常简单友好,那么就会有人问了,既然都有了 Flask 为什么还需要使用 FastAPI 呢?
首先,我们来了解一下什么是FastAPI:
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于基于标准 Python 类型提示使用 Python 3.6+ 构建 API。
主要特点:
- 快速:非常高的性能,与NodeJS和Go相当(感谢 Starlette 和 Pydantic)。可用的最快的 Python 框架之一。
- 快速编码:将开发功能的速度提高约 200% 到 300%。*
- 更少的错误:减少约 40% 的人为(开发人员)导致的错误。*
- 直观:出色的编辑器支持。到处完成。更少的调试时间。
- 简单:旨在易于使用和学习。减少阅读文档的时间。
- Short : 尽量减少代码重复。每个参数声明的多个功能。更少的错误。
- 健壮:获取生产就绪的代码。具有自动交互式文档。
- 基于标准:基于(并完全兼容)API 的开放标准:OpenAPI(以前称为 Swagger)和JSON Schema。
2.FastAPI安装
# 安装 FastAPI
pip install fastapi
# 作为服务器
pip install uvicorn
# 运行
uvicorn main:app --reload
3.快速使用
3.1 使用流程
# 1.导入 FastAPI。
# 2.创建一个 app 实例。
# 3.编写一个路径操作装饰器(如 @app.get("/"))。
# 4.编写一个路径操作函数(如上面的 def root(): ...)。
# 5.运行开发服务器,如: uvicorn main:app --reload。
3.2 代码示例
将以下代码复制到 main.py
文件中
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
async def root(): # async def root() 与 def root() 都可以
return {'code': 100, 'msg': 'hello world'}
3.3 查看网址
打开浏览器访问 http://127.0.0.1:8000。
你将看到如下的 JSON 响应:
{"code":100,"msg":"hello world"}
3.4 交互式 API 文档
这是 FastAPI 自带的文档
跳转到 http://127.0.0.1:8000/docs
二:使用指南
1.路由
1.1 参数路由
1.1.1 设置参数
from fastapi import FastAPI
app = FastAPI()
@app.get('/item_id/{item_id}')
async def item(item_id):
return {'code': 100, 'msg': 'hello world', 'item_id': item_id}
# http://127.0.0.1:8000/item_id/15/
# {"code":100,"msg":"hello world","item_id":"15"}
# http://127.0.0.1:8000/item_id/pic
# {"code":100,"msg":"hello world","item_id":"pic"}
1.1.2 路由传值设置参数
# 路由传值设置参数
# 可以使用标准的 Python 类型标注为函数中的路径参数声明类型。
# 类型不正确时,会报错
# 当时设置为 item_id: str 时,路径中无论传入 字符串、数字、小数 都会通过并返回字符串
# 路径顺序:/users/me 要放在 /users/{user_id} 之前,否则会被 /users/{user_id} 拦截
from fastapi import FastAPI
app = FastAPI()
@app.get('/item_id/{item_id}')
async def item(item_id: int):
return {'code': 100, 'msg': 'hello world', 'item_id': item_id}
# 报错如下:
# 字符串
# http://127.0.0.1:8000/item_id/pic
# {"detail":[{"loc":["path","item_id"],"msg":"value is not a valid integer","type":"type_error.integer"}]}
# 小数
# http://127.0.0.1:8000/item_id/5.6
# {"detail":[{"loc":["path","item_id"],"msg":"value is not a valid integer","type":"type_error.integer"}]}
1.1.3 自定义类型
如果你有一个接收路径参数的路径操作,但你希望预先设定可能的有效参数值,则可以使用标准的 Python Enum
类型。
创建一个 Enum 类
导入 Enum 并创建一个继承自 str 和 Enum 的子类。
通过从 str 继承,API 文档将能够知道这些值必须为 string 类型并且能够正确地展示出来。
然后创建具有固定值的类属性,这些固定值将是可用的有效值:
from enum import Enum
from fastapi import FastAPI
app = FastAPI()
class ModelName(str, Enum):
ysg = 'ysg'
ysging = 'ysging'
czx = 'czx'
@app.get('/model/{model_name}')
def models(model_name: ModelName): # 声明路径参数 model_name: ModelName
if model_name is ModelName.ysg:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "ysging":
return {"model_name": model_name, "message": "LeCNN all the images"}
return {"model_name": model_name, "message": "Have some residuals"}
# 输入不存在 ModelName 中的类型,报错
# {"detail":[{"loc":["path","model_name"],"msg":"value is not a valid enumeration member; permitted: 'ysg', 'ysging', 'czx'","type":"type_error.enum","ctx":{"enum_values":["ysg","ysging","czx"]}}]}
1.1.4 路径转换器
你可以使用直接来自 Starlette 的选项来声明一个包含路径的路径参数:
/files/{file_path:path}
在这种情况下,参数的名称为 file_path,结尾部分的 :path 说明该参数应匹配任意的路径。
因此,你可以这样使用它:
from fastapi import FastAPI
app = FastAPI()
@app.get('/file/{file_path:path}')
async def read_file(file_path):
return {"file_path": file_path}
# http://127.0.0.1:8000/file/home/ysg/
# {"file_path":"home/ysg/"}
你可能会需要参数包含 /home/johndoe/myfile.txt,以斜杠(/)开头。
在这种情况下,URL 将会是 /files//home/johndoe/myfile.txt,在files 和 home 之间有一个双斜杠(//)。
1.2 路由查询参数
from fastapi import FastAPI
app = FastAPI()
# 例一
@app.get("/items/{item_id}")
async def read_user_item(item_id: str, needy: str):
# 例二
@app.get("/items/{item_id}")
async def read_user_item(
item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None
):
# 例一:needy: str 虽然不是路径中必填项,但是 needy 必填
# 例二:
needy,一个必需的 str 类型参数。
skip,一个默认值为 0 的 int 类型参数。
limit,一个可选的 int 类型参数。
1.2.1 声明查询参数
@app.get('/itmes/')
async def read_item(page: int = 0, size: int = 10):
lit = [{'name': 'ysg'}, {'name': 'ysging'}, {'name': 'ysg3'}]
@app.get('/itmes/')
async def read_item(page: int = 0, size: int = 10): # 设置默认值
return lit[page: page + size]
# http://127.0.0.1:8000/itmes/?page=1&size=2
# [{"name":"ysging"},{"name":"ysg3"}]
# 不传值时,使用默认值
# http://127.0.0.1:8000/itmes/
# [{"name":"ysg"},{"name":"ysging"},{"name":"ysg3"}]
1.2.2 可选参数
如果 q 不输入值,则为空
from typing import Union
@app.get('/items/{items_id}')
async def items(items_id:str, q:Union[str, None]=None):
if q:
return {'items_id': items_id, 'q':q}
return {'items_id': items_id}
# http://127.0.0.1:8000/items/id9
# {"items_id":"id9"}
# http://127.0.0.1:8000/items/id9?q=123
# {"items_id":"id9","q":"123"}
1.2.3 参数的类型转换
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get('/home/{home_id}/')
async def home(home_id: str, q: Union[str, None] = None, short: bool = False):
item = {'home_id': home_id}
if q:
item.update({'q': q})
if short:
item.update({'short': '这是一个信息描述'})
return item
# http://127.0.0.1:8000/home/avc/
# {"home_id":"avc"}
# http://127.0.0.1:8000/home/avc/?q=123
# {"home_id":"avc","q":"123"}
# http://127.0.0.1:8000/home/avc/?short=ysg
# {"detail":[{"loc":["query","short"],"msg":"value could not be parsed to a boolean","type":"type_error.bool"}]}
# http://127.0.0.1:8000/home/avc/?q=123&short=ysg
# {"detail":[{"loc":["query","short"],"msg":"value could not be parsed to a boolean","type":"type_error.bool"}]}
1.2.4 多个路径和查询参数
你可以同时声明多个路径参数和查询参数,FastAPI 能够识别它们。
而且你不需要以任何特定的顺序来声明。
它们将通过名称被检测到:
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get('/home/{home_id}/user/{user_id}')
async def home(home_id: str, user_id: str, q: Union[str, None] = None, short: bool = False):
item = {'home_id': home_id, 'user_id': user_id}
if q:
item.update({'q': q})
if short:
item.update({'short': '这是一个信息描述'})
return item
# http://127.0.0.1:8000/home/ave/user/ysg
# {"home_id":"ave","user_id":"ysg"}
1.3 路由中的请求体
1.3.1 声明请求体
查看接口文档可访问:http://127.0.0.1:8000/docs
仅仅使用了 Python 类型声明,FastAPI 将会:
- 将请求体作为 JSON 读取。
- 转换为相应的类型(在需要时)。
- 校验数据。
- 如果数据无效,将返回一条清晰易读的错误信息,指出不正确数据的确切位置和内容。
- 将接收的数据赋值到参数
item
中。- 由于你已经在函数中将它声明为
Item
类型,你还将获得对于所有属性及其类型的一切编辑器支持(代码补全等)。
- 由于你已经在函数中将它声明为
- 为你的模型生成 JSON 模式 定义,你还可以在其他任何对你的项目有意义的地方使用它们。
- 这些模式将成为生成的 OpenAPI 模式的一部分,并且被自动化文档 UI 所使用。
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
info: Union[float, None] = None
@app.post('/items/')
async def items(item: Item):
return item
# 查看接口文档可访问:http://127.0.0.1:8000/docs
# post 请求
http://127.0.0.1:8000/items/
# 请求体为空
# 返回值报错
{
"detail": [
{
"loc": [
"body"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
# 输入请求体
{
"name":"ysg",
"price":15.3
}
# 返回值
{
"name": "ysg",
"price": 15.3,
"info": null
}
1.3.2 请求体 + 路径参数
class Item(BaseModel):
name: str
price: float
info: Union[float, None] = None
@app.post('/items/{item_id}')
async def items(item_id: int, item: Item):
return {'item_id': item_id, **item.dict()}
# post 请求
http://127.0.0.1:8000/items/12/
# 请求体
{
"name":"ysg",
"price":15
}
# 返回值
{
"item_id": 12,
"name": "ysg",
"price": 15.0,
"info": null
}
1.3.3 请求体 + 路径参数 + 查询参数
class Item(BaseModel):
name: str
price: float
info: Union[float, None] = None
@app.post('/items/{item_id}')
async def items(item_id: int, item: Item, q:Union[str, None]=None):
return {'item_id': item_id, **item.dict()}
# post 请求
http://127.0.0.1:8000/items/12/?q=qwer
# 请求体
{
"name":"ysg",
"price":15
}
# 返回值
{
"item_id": 12,
"name": "ysg",
"price": 15.0,
"info": null,
"q": "qwer"
}
1.4 查询参数字符串校验
1.4.1 添加校验参数
from typing import Union
from fastapi import FastAPI, Query
app = FastAPI()
@app.get('/item/')
async def item(q:Union[str, None] = Query(default=None, max_length=20, min_length=3, regex="^fixedquery$")):
results = {'items': [{'name': 'ysg', 'age': 16}]}
if q:
results.update({'q': q})
return results
# 不符合校验类型会抛出错误
# http://127.0.0.1:8000/item/?q=12345678
# {"detail":[{"loc":["query","q"],"msg":"ensure this value has at most 7 characters","type":"value_error.any_str.max_length","ctx":{"limit_value":7}}]}
1.4.2 声明必填参数
# 方式一:声明参数,不设置默认值
async def item(q: str = Query(min_length=3))
# 方式二:Query 中默认值使用省略号(...)声明必需参数
async def read_items(q: str = Query(default=..., min_length=3))
# 方式三:使用None声明必需参数
async def read_items(q: Union[str, None] = Query(default=..., min_length=3))
1.4.3 查询参数列表
声明方式
from typing import Union, List
from fastapi import FastAPI, Query
app = FastAPI()
@app.get('/user/')
# 方法一:使用 List[str] 声明
# async def user(q: Union[List[str], None] = Query(default=None)):
# 方法二:使用 list 声明时,无法对列表内容进行类型控制
# async def user(q: Union[list, None] = Query(default=None)):
# 方法三:设置默认值
async def user(q: Union[list, None] = Query(default=['user1', 'user2'])):
return {'q': q}
# 方法一、二
# http://127.0.0.1:8000/user/?q=ysg&q=ysging
# {"q":["ysg","ysging"]}
# 方法三
# http://127.0.0.1:8000/user/
# {"q":["user1","user2"]}
设置别名
下面将查询参数 q
设置别名 item_query
,在访问时,直接访问 q
将无效,只能通过访问别名才可以。
from typing import Union, List
from fastapi import FastAPI, Query
app = FastAPI()
@app.get('/user/')
async def user(q: Union[list, None] = Query(default=['user1', 'user2'], alias='item_query')):
res = {'age': 15}
if q:
res.update({'q': q})
else:
res.update({'item_query': q})
return res
1.5 路径参数和数值校验
路径参数总是必需的,因为它必须是路径的一部分。
1.5.1 导入 Path
from fastapi import FastAPI, Path, Query
from typing import Union
app = FastAPI()
@app.get('/item/{item_id}')
# item_id 与 q 的循序可以调整。将通过参数的名称、类型和默认值声明(Query、Path 等)来检测参数,而不在乎参数的顺序。
async def item(item_id: int = Path(title='这个是项目ID'), q: Union[str, None] = Query(default='这个是 q 的默认值')):
ret = {'item_id': item_id}
if q:
ret.update({'q': q})
return ret
# http://127.0.0.1:8000/item/212?item-query=sdf
# {"item_id":212,"q":"sdf"}
如果你想不使用 Query
声明没有默认值的查询参数 q
,同时使用 Path
声明路径参数 item_id
,并使它们的顺序与上面不同,Python 对此有一些特殊的语法。
传递 *
作为函数的第一个参数。
Python 不会对该 *
做任何事情,但是它将知道之后的所有参数都应作为关键字参数(键值对),也被称为 kwargs
,来调用。即使它们没有默认值。
@app.get('/item/{item_id}')
async def item(*, q, item_id: int = Path(title='这个是项目ID', gt=100,le=200)):
1.5.2 数值校验
使用 Query
和 Path
(以及你将在后面看到的其他类)可以声明字符串约束,但也可以声明数值约束。
像下面这样,添加 ge=1
后,item_id
将必须是一个大于(g
reater than)或等于(e
qual)1
的整数。
否则报错:
{"detail":[{"loc":["path","item_id"],"msg":"ensure this value is greater than or equal to 6","type":"value_error.number.not_ge","ctx":{"limit_value":6}}]}
大于等于:ge
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get('/item/{item_id}')
async def item(*, q, item_id: int = Path(title='这个是项目ID', ge=6)):
ret = {'item_id': item_id}
if q:
ret.update({'q': q})
return ret
大于:gt
小于等于:le
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get('/item/{item_id}')
async def item(*, q, item_id: int = Path(title='这个是项目ID', gt=100,le=200)):
ret = {'item_id': item_id}
if q:
ret.update({'q': q})
return ret
浮点数和小于:lt
不符合的报错信息
{"detail":[{"loc":["query","size"],"msg":"ensure this value is greater than 2.5","type":"value_error.number.not_gt","ctx":{"limit_value":2.5}}]}
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get('/item/{item_id}')
async def item(*, size: float = Query(lt=9.7, gt=2.5), q, item_id: int = Path(title='这个是项目ID', gt=100, le=200)):
ret = {'item_id': item_id}
if q:
ret.update({'q': q})
if size:
ret.update({'size': size})
return ret
# http://127.0.0.1:8000/item/150?q=qweq&size=6.5
# {"item_id":150,"q":"qweq","size":6.5}
2.请求体
2.1 多个请求体参数
你可以添加多个请求体参数到路径操作函数中,即使一个请求只能有一个请求体。
但是 FastAPI 会处理它,在函数中为你提供正确的数据,并在路径操作中校验并记录正确的模式。
你还可以声明将作为请求体的一部分所接收的单一值。
你还可以指示 FastAPI 在仅声明了一个请求体参数的情况下,将原本的请求体嵌入到一个键中。
2.2.1 多个请求体参数
请注意,即使 item
的声明方式与之前相同,但现在它被期望通过 item
键内嵌在请求体中。
FastAPI 将自动对请求中的数据进行转换,因此 item
参数将接收指定的内容,user
参数也是如此。
它将执行对复合数据的校验,并且像现在这样为 OpenAPI 模式和自动化文档对其进行记录。
from fastapi import FastAPI, Path, Query
from typing import Union
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
title: str
price: float
sumber: Union[int, None] = None
info: Union[str, None] = '这是一个默认值'
class User(BaseModel):
name: str
age: float
sex: Union[str, None] = '保密'
@app.put("/items/{item_id}")
# 混合使用 Path、Query 和请求体参数
def updata_item(*,
q: Union[str, None],
item: Item,
user: User,
item_id: int = Path(gt=3, lt=9)):
ret = {'q': q, 'item': item, 'item_id': item_id, 'user': user}
return ret
# http://127.0.0.1:8000/items/6?q=qwer
# 请求体
{
"item":{
"title":"python",
"price":15
},
"user":{
"name":"ysg",
"age":15
}
}
# 返回值
# 在这种情况下,FastAPI 将注意到该函数中有多个请求体参数(两个 Pydantic 模型参数)。
# 因此,它将使用参数名称作为请求体中的键(字段名称),并期望一个类似于以下内容的请求体:
{
"q": "qwer",
"item": {
"title": "python",
"price": 15.0,
"sumber": null,
"info": "这是一个默认值"
},
"item_id": 6,
"user": {
"name": "ysg",
"age": 15.0,
"sex": "保密"
}
}
# 报错信息如:
{
"detail": [
{
"loc": [
"body",
"item",
"title"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
2.2.2 Body() 的使用
例如,为了扩展先前的模型,你可能决定除了 item
和 user
之外,还想在同一请求体中具有另一个键 mybody
。
如果你就按原样声明它,因为它是一个单一值,FastAPI 将假定它是一个查询参数。
但是你可以使用 Body
指示 FastAPI 将其作为请求体的另一个键进行处理。
from fastapi import FastAPI, Path, Query, Body
from typing import Union
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
title: str
price: float
sumber: Union[int, None] = None
info: Union[str, None] = '这是一个默认值'
class User(BaseModel):
name: str
age: float
sex: Union[str, None] = '保密'
@app.put("/items/{item_id}")
def updata_item(*,
q: Union[str, None] = None,
mybody: str = Body(),
item: Item,
user: User,
item_id: int = Path(gt=3, lt=9)):
ret = {'q': q, 'item': item, 'item_id': item_id, 'user': user, 'mybody': mybody}
return ret
# http://127.0.0.1:8000/items/6?q=qwer
# 请求体
{
"mybody":"被 Body() 装饰的参数写入请求体重",
"item":{
"title":"python",
"price":15
},
"user":{
"name":"ysg",
"age":15
}
}
# 返回值
{
"q": "qwer",
"item": {
"title": "python",
"price": 15.0,
"sumber": null,
"info": "这是一个默认值"
},
"item_id": 6,
"user": {
"name": "ysg",
"age": 15.0,
"sex": "保密"
},
"mybody": "被 Body() 装饰的参数写入请求体重"
}
如果单个请求体也希望像以下方式请求:
{
"item":{
"title":"python",
"price":15
}
}
则可以使用一个特殊的 Body
参数 embed=Ture
@app.put("/items/{item_id}")
def updata_item(*,
item: Item = Body(embed=True),
item_id: int
):
ret = {'item': item, 'item_id': item_id}
return ret
请求与返回值如下:
# http://127.0.0.1:8000/items/6
{
"item": {
"title": "python",
"price": 15.0,
"sumber": null,
"info": "这是一个默认值"
},
"item_id": 6
}
与 item: Item = Body()
的请求体区别如下:
{
"title":"python",
"price":15
}
2.2 字段
与使用 Query
、Path
和 Body
在路径操作函数中声明额外的校验和元数据的方式相同,你可以使用 Pydantic 的 Field
在 Pydantic 模型内部声明校验和元数据。
2.2.1 导入 Field
注意,Field
是直接从 pydantic
导入的,而不是像其他的(Query
,Path
,Body
等)都从 fastapi
导入。
Field
的工作方式和 Query
、Path
和 Body
相同,包括它们的参数等等也完全相同
# 导入 Field
from pydantic import BaseModel, Field
2.2.2 声明模型属性
实际上,Query
、Path
和其他你将在之后看到的类,创建的是由一个共同的 Params
类派生的子类的对象,该共同类本身又是 Pydantic 的 FieldInfo
类的子类。
Pydantic 的 Field
也会返回一个 FieldInfo
的实例。
Body
也直接返回 FieldInfo
的一个子类的对象。还有其他一些你之后会看到的类是 Body
类的子类。
请记住当你从 fastapi
导入 Query
、Path
等对象时,他们实际上是返回特殊类的函数。
from fastapi import FastAPI, Body
from typing import Union
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
title: str
price: float = Field(gt=0, description='价格必须大于0')
sumber: Union[int, None] = None
info: Union[str, None] = Field(default='这是一个默认值', title='产品描述', max_length=300)
@app.put("/items/{item_id}")
def updata_item(*,
item: Item = Body(embed=True),
item_id: int
):
ret = {'item': item, 'item_id': item_id}
return ret
# http://127.0.0.1:8000/items/6
# 请求体
{
"item":{
"title":"python",
"price":1
}
}
# 返回值
{
"item": {
"title": "python",
"price": 1.0,
"sumber": null,
"info": "这是一个默认值"
},
"item_id": 6
}
2.3 嵌套模型
2.3.1 内部类型
list 类型:保存一组列表
set 类型:保存一组唯一的元素
from fastapi import FastAPI, Query, Body
from typing import List, Set, Union
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str
age: int
info: Union[str, None] = Field(default='这个是一个人物介绍', max_length=300)
# hobby: List[str] = []
hobby: Set[str] = set()
@app.post('/user/{user_id}')
def user(*, user_id: Union[int, None] = Query(lt=3, gt=10), item: Item = Body(embed=True)):
ret = {'user_id': user_id, 'item': item}
return ret
List 与 Set 的区别
# http://127.0.0.1:8000/user/6
# 请求体
{
"item":{
"name":"ysg",
"age":1,
"hobby":["射箭", "爬山", "爬山"]
}
}
# 返回值
{
"user_id": 6,
"item": {
"name": "ysg",
"age": 1,
"info": "这个是一个人物介绍",
"hobby": # list 与 set 不同
}
}
# list 返回值
[
"射箭",
"爬山",
"爬山",
]
# set 返回值
[
"射箭",
"爬山"
]
2.3.2 请求体
将子模型用作类型
然后我们可以将其用作一个属性的类型
注意:你可以定义任意深度的嵌套模型,下面例子中只嵌套的一层。
from fastapi import FastAPI, Query, Body
from typing import List, Set, Union
from pydantic import BaseModel, Field
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
age: int
info: Union[str, None] = Field(default='这个是一个人物介绍', max_length=300)
hobby: List[str] = []
# hobby: Set[str] = set()
image: Union[Image, None] = None
@app.post('/user/{user_id}')
def user(*, user_id: Union[int, None] = Query(lt=3, gt=10), item: Item = Body(embed=True)):
ret = {'user_id': user_id, 'item': item}
return ret
再一次,仅仅进行这样的声明,你将通过 FastAPI 获得:
- 对被嵌入的模型也适用的编辑器支持(自动补全等)
- 数据转换
- 数据校验
- 自动生成文档:http://127.0.0.1:8000/docs
# http://127.0.0.1:8000/user/6
# 请求体
{
"item":{
"name":"ysg",
"age":1,
"hobby":["射箭", "爬山", "爬山"],
"image": {
"url": "www.baidu.com",
"name":"百度"
}
}
}
# 返回值
{
"user_id": 6,
"item": {
"name": "ysg",
"age": 1,
"info": "这个是一个人物介绍",
"hobby": [
"射箭",
"爬山",
"爬山"
],
"image": {
"url": "www.baidu.com",
"name": "百度"
}
}
}
2.3.3 特殊的类型和校验
除了普通的单一值类型(如 str
、int
、float
等)外,你还可以使用从 str
继承的更复杂的单一值类型。
要了解所有的可用选项,请查看关于 来自 Pydantic 的外部类型 的文档。你将在下一章节中看到一些示例。
例如,在 Image
模型中我们有一个 url
字段,我们可以把它声明为 Pydantic 的 HttpUrl
,而不是 str
把 2.3.2 请求体
中的类 Image
改为:
class Image(BaseModel):
url: HttpUrl
name: str
便会对 url
进行校验,报错信息如下:
{
"detail": [
{
"loc": [
"body",
"item",
"image",
"url"
],
"msg": "invalid or missing URL scheme",
"type": "value_error.url.scheme"
}
]
}
2.3.4 请求体的 list 类型
你还可以将 Pydantic 模型用作 list
、set
等的子类型
把 2.3.2 请求体
中的类 Item
改为:
class Item(BaseModel):
name: str
age: int
info: Union[str, None] = Field(default='这个是一个人物介绍', max_length=300)
hobby: List[str] = []
# hobby: Set[str] = set()
image: Union[List[Image], None] = None
请求体如下:
{
"item":{
"name":"ysg",
"age":16,
"hobby":["射箭", "爬山", "爬山"],
"image": [{
"url": "https://www.baidu.com",
"name":"百度"
},
{
"url": "https://www.mgtv.com",
"name":"芒果TV"
}
]
}
}
返回值:
{
"detail": [
{
"loc": [
"body",
"item",
"image"
],
"msg": "value is not a valid list",
"type": "type_error.list"
}
]
}
2.3.5 纯列表请求体
如果你期望的 JSON 请求体的最外层是一个 JSON array
(即 Python list
),则可以在路径操作函数的参数中声明此类型,就像声明 Pydantic 模型一样:
images: List[Image]
from fastapi import FastAPI, Query, Body
from typing import List, Union
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
@app.post('/user/{user_id}')
def user(*, user_id: Union[int, None] = Query(lt=3, gt=10), image: List[Image] = Body(embed=True)):
ret = {'user_id': user_id, 'image': image}
return ret
http://127.0.0.1:8000/user/6
# 请求体
{
"image": [
{
"url": "https://www.baidu.com",
"name": "百度"
},
{
"url": "https://www.mgtv.com",
"name": "芒果TV"
}
]
}
# 返回值
{
"user_id": 6,
"image": [
{
"url": "https://www.baidu.com",
"name": "百度"
},
{
"url": "https://www.mgtv.com",
"name": "芒果TV"
}
]
}
2.3.6 dict 构成的请求体
你也可以将请求体声明为使用某类型的键和其他类型值的 dict
。
无需事先知道有效的字段/属性(在使用 Pydantic 模型的场景)名称是什么。
如果你想接收一些尚且未知的键,这将很有用。
其他有用的场景是当你想要接收其他类型的键时,例如 int
。
这也是我们在接下来将看到的。
在下面的例子中,你将接受任意键为 int
类型并且值为 float
类型的 dict
:
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
作为键。
但是 Pydantic 具有自动转换数据的功能。
这意味着,即使你的 API 客户端只能将字符串作为键发送,只要这些字符串内容仅包含整数,Pydantic 就会对其进行转换并校验。
然后你接收的名为 weights
的 dict
实际上将具有 int
类型的键和 float
类型的值。
3.API文档注释
3.1 请求体注释
把以下内容放于请求体函数中,或使用 Body() 放到函数参数中
# 方法一【常用】:
class Config:
schema_extra = {
"example":{
"name": "ysg",
"age": 25,
"info": "这个是一个人物介绍",
"hobby": ['爱好1', '爱好2', '爱好3'],
"image": [
{
"url": "https://www.baidu.com",
"name": "百度"
},
{
"url": "https://www.mgtv.com",
"name": "芒果TV"
}
]
}
}
# 方法二【不常用】:
# 注意:Body 不能再加其它参数,如:embed=True,否则无效。
@app.post('/user/{user_id}')
def user(*,
user_id: Union[int, None] = Query(lt=3, gt=10),
item: Item = Body(example={
"name": "ysg",
"age": 25,
"info": "这个是一个人物介绍",
"hobby": ['爱好1', '爱好2', '爱好3'],
"image": [
{
"url": "https://www.baidu.com",
"name": "百度"
},
{
"url": "https://www.mgtv.com",
"name": "芒果TV"
}
]
})):
完整代码如下:
from fastapi import FastAPI, Query, Body
from typing import List, Set, Union
from pydantic import BaseModel, Field, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
age: int
info: Union[str, None] = Field(default='这个是一个人物介绍', max_length=300)
# hobby: List[str] = []
hobby: Set[str] = set()
image: Union[List[Image], None] = None
class Config:
schema_extra = {
"example":{
"name": "ysg",
"age": 25,
"info": "这个是一个人物介绍",
"hobby": ['爱好1', '爱好2', '爱好3'],
"image": [
{
"url": "https://www.baidu.com",
"name": "百度"
},
{
"url": "https://www.mgtv.com",
"name": "芒果TV"
}
]
}
}
@app.post('/user/{user_id}')
def user(*, user_id: Union[int, None] = Query(lt=3, gt=10), item: Item = Body(embed=True)):
ret = {'user_id': user_id, 'item': item}
return ret
效果即可在 API文档
中查看:
3.2 field() 注释
在 Field
, Path
, Query
, Body
和其他你之后将会看到的工厂函数,你可以为JSON 模式声明额外信息,你也可以通过给工厂函数传递其他的任意参数来给JSON 模式声明额外信息,比如增加 example
:
Field(default='人物介绍', example='这是一个的介绍', max_length=300)
from fastapi import FastAPI, Body
from typing import Union
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str
age: int
info: Union[str, None] = Field(default='人物介绍', example='这是一个的介绍', max_length=300)
@app.post('/user/{user_id}/')
async def user(user_id: int, item: Item = Body(embed=True)):
ret = {'user_id': user_id, 'item': item}
return ret
3.3 Query() 注释
q: Union[str, None] = Query(example='这个是一个查询参数', max_length=30, default=None)
from fastapi import FastAPI, Query
from typing import Union
app = FastAPI()
@app.post('/user/{user_id}/')
async def user(*, user_id: int, q: Union[str, None] = Query(example='这个是一个查询参数', max_length=30, default=None)):
ret = {'user_id': user_id, 'q': q}
return ret
4.Cookie参数
4.1 设置 cookie
通过浏览器放置 cookie
> document.cookie="name=ysg"
<.'name=ysg'
4.2 取出 cookie
声明 Cookie
参数的结构与声明 Query
参数和 Path
参数时相同。
第一个值是参数的默认值,同时也可以传递所有验证参数或注释参数,来校验参数:
from fastapi import FastAPI, Cookie
from typing import Union
app = FastAPI()
@app.get('/items/')
async def cookie_item(name: Union[str, None] = Cookie(default=None)):
ret = {'name': name}
return ret
5.Header参数
声明 header 参数,并取出 user_agent
from fastapi import FastAPI, Header
from typing import Union
app = FastAPI()
@app.get('/items/')
async def cookie_item(user_agent: Union[str, None] = Header(default=None)):
ret = {'user_agent': user_agent}
return ret
# {"user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"}
5.1自动转换
Header
在 Path
, Query
和 Cookie
提供的功能之上有一点额外的功能。
大多数标准的headers用 "连字符" 分隔,也称为 "减号" (-
)。
但是像 user-agent
这样的变量在Python中是无效的。
因此, 默认情况下, Header
将把参数名称的字符从下划线 (_
) 转换为连字符 (-
) 来提取并记录 headers.
同时,HTTP headers 是大小写不敏感的,因此,因此可以使用标准Python样式(也称为 "snake_case")声明它们。
因此,您可以像通常在Python代码中那样使用 user_agent
,而不需要将首字母大写为 User_Agent
或类似的东西。
如果出于某些原因,你需要禁用下划线到连字符的自动转换,设置Header
的参数 convert_underscores
为 False
:
from typing import Union
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(
strange_header: Union[str, None] = Header(default=None, convert_underscores=False)
):
return {"strange_header": strange_header}
5.2 重复的 headers
有可能收到重复的headers。这意味着,相同的header具有多个值。
您可以在类型声明中使用一个list来定义这些情况。
你可以通过一个Python list
的形式获得重复header的所有值。
比如, 为了声明一个 X-Token
header 可以出现多次,你可以这样写:
from typing import List, Union
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(x_token: Union[List[str], None] = Header(default=None)):
return {"X-Token values": x_token}
如果你与路径操作通信时发送两个HTTP headers,就像:
X-Token: foo
X-Token: bar
相应如下:
{
"X-Token values": [
"bar",
"foo"
]
}
6.相应模型
你可以在任意的路径操作中使用 response_model
参数来声明用于响应的模型:
@app.get()
@app.post()
@app.put()
@app.delete()
- 等等。
注意,response_model
是「装饰器」方法(get,post 等)的一个参数。不像之前的所有参数和请求体,它不属于路径操作函数。
6.1 添加输出模型
注意:永远不要存储用户的明文密码,也不要在响应中发送密码。
我们可以创建一个有明文密码的输入模型和一个没有明文密码的输出模型:
# 输入模型
class UserIn(BaseModel):
user: str
pawd: str
email: EmailStr
# 输出模型
class UserOut(BaseModel):
user: str
email: EmailStr
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn):
return user
# http://127.0.0.1:8000/user/
# 请求体
{
"user": "ysg",
"pawd": "123.56",
"email": "123@qq.com"
}
# 返回值
{
"user": "ysg",
"email": "123@qq.com"
}
因此,FastAPI 将会负责过滤掉未在输出模型中声明的所有数据(使用 Pydantic)。
当你查看自动化文档时,你可以检查输入模型和输出模型是否都具有自己的JSON Schema:
6.2 响应模型编码参数
你的响应模型可以具有默认值,FastAPI 通过 Pydantic 模型的 .dict()
配合 该方法的 exclude_unset
参数 来实现此功能。例如:
from fastapi import FastAPI, File
from typing import Union
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
title: str
price: float
tags: List[str] = []
description: Union[str, None] = File(max_length=300)
info = {
'bowl': {"title": "碗", "price": 63.2, "description": "一打碗", "tags": ['物美价廉', '小巧可爱']},
'cup': {"title": "杯", "price": 25.6, "description": "这是一个杯子", "tags": []},
'pot': {"title": "壶", "price": 254, "description": None}, # 当 "description": "", FastAPI 认为值是 ""。
}
@app.get("/shop/{shop_id}", response_model=Item, response_model_exclude_none=True)
async def shop(shop_id: str):
return info[shop_id]
# 请求体
{
"user": "ysg",
"pawd": "123.56",
"tags": ["实惠", "美观"],
"description": "这是一个杯子"
}
# http://127.0.0.1:8000/shop/bowl
{
"title": "碗",
"price": 63.2,
"tags": [
"物美价廉",
"小巧可爱"
],
"description": "一打碗"
}
# http://127.0.0.1:8000/shop/cup
{
"title": "杯",
"price": 25.6,
"tags": [],
"description": "这是一个杯子"
}
# http://127.0.0.1:8000/shop/pot
{
"title": "壶",
"price": 254.0,
"tags": []
}
description: Union[str, None] = None
具有默认值None
。tags: List[str] = []
具有一个空列表作为默认值:[]
.response_model_exclude_none=Ture
请求体中没有传该值时,忽略默认值
6.2.1 默认值字段没有实际值
但如果它们并没有存储实际的值,你可能想从结果中忽略它们的默认值。
举个例子,当你在 NoSQL 数据库中保存了具有许多可选属性的模型,但你又不想发送充满默认值的很长的 JSON 响应。例如 ID 为 pot
的项:
# http://127.0.0.1:8000/shop/pot
{
"title": "壶",
"price": 254.0,
"tags": []
}
其中忽略了 description
的默认值。
6.2.2 默认值字段有实际值的数据
但是,如果你的数据在具有默认值的模型字段中有实际的值,例如 ID 为 bowl
的项:
# http://127.0.0.1:8000/shop/bowl
{
"title": "碗",
"price": 63.2,
"tags": [
"物美价廉",
"小巧可爱"
],
"description": "一打碗"
}
6.2.3 具有与默认值相同值的数据
如果数据具有与默认值相同的值,例如 ID 为 baz
的项:
#
# 请求体
{
"title": "杯",
"price": 25.6,
"tags": [],
"description": "这是一个杯子"
}
# 返回值
即使 description
和 tags
具有与默认值相同的值,FastAPI 足够聪明 (实际上是 Pydantic 足够聪明) 去认识到这一点,它们的值被显式地所设定(而不是取自默认值)。
6.3 额外的模型
6.3.1 多模型继承
6.3.2 两种类型的 Union
6.3.3 模型列表
6.3.4 任意 dict 构成的响应
1.额外数据类型
到目前为止,您一直在使用常见的数据类型,如:
int
float
str
bool
但是您也可以使用更复杂的数据类型。
您仍然会拥有现在已经看到的相同的特性:
- 很棒的编辑器支持。
- 传入请求的数据转换。
- 响应数据转换。
- 数据验证。
- 自动补全和文档。
1.1其他数据类型地址
下面是一些你可以使用的其他数据类型:
-
UUID
:
- 一种标准的 "通用唯一标识符" ,在许多数据库和系统中用作ID。
- 在请求和响应中将以
str
表示。
-
datetime.datetime
:
- 一个 Python
datetime.datetime
. - 在请求和响应中将表示为 ISO 8601 格式的
str
,比如:2008-09-15T15:53:00+05:00
.
- 一个 Python
-
datetime.date
:
- Python
datetime.date
. - 在请求和响应中将表示为 ISO 8601 格式的
str
,比如:2008-09-15
.
- Python
-
datetime.time
:
- 一个 Python
datetime.time
. - 在请求和响应中将表示为 ISO 8601 格式的
str
,比如:14:23:55.003
.
- 一个 Python
-
datetime.timedelta
:
- 一个 Python
datetime.timedelta
. - 在请求和响应中将表示为
float
代表总秒数。 - Pydantic 也允许将其表示为 "ISO 8601 时间差异编码", 查看文档了解更多信息。
- 一个 Python
-
frozenset
:
-
在请求和响应中,作为
set
对待:
- 在请求中,列表将被读取,消除重复,并将其转换为一个
set
。 - 在响应中
set
将被转换为list
。 - 产生的模式将指定那些
set
的值是唯一的 (使用 JSON 模式的uniqueItems
)。
- 在请求中,列表将被读取,消除重复,并将其转换为一个
-
-
bytes
:
- 标准的 Python
bytes
。 - 在请求和相应中被当作
str
处理。 - 生成的模式将指定这个
str
是binary
"格式"。
- 标准的 Python
-
Decimal
:
- 标准的 Python
Decimal
。 - 在请求和相应中被当做
float
一样处理。
- 标准的 Python
-
您可以在这里检查所有有效的pydantic数据类型: Pydantic data types.
1.2 例子
下面是一个路径操作的示例,其中的参数使用了上面的一些类型。
from datetime import datetime, time, timedelta
from typing import Union
from uuid import UUID
from fastapi import Body, FastAPI
app = FastAPI()
@app.put("/items/{item_id}")
async def read_items(
item_id: UUID,
start_datetime: Union[datetime, None] = Body(default=None),
end_datetime: Union[datetime, None] = Body(default=None),
repeat_at: Union[time, None] = Body(default=None),
process_after: Union[timedelta, None] = Body(default=None),
):
start_process = start_datetime + process_after
duration = end_datetime - start_process
return {
"item_id": item_id,
"start_datetime": start_datetime,
"end_datetime": end_datetime,
"repeat_at": repeat_at,
"process_after": process_after,
"start_process": start_process,
"duration": duration,
}
注意,函数内的参数有原生的数据类型,你可以,例如,执行正常的日期操作,如:
from datetime import datetime, time, timedelta
from typing import Union
from uuid import UUID
from fastapi import Body, FastAPI
app = FastAPI()
@app.put("/items/{item_id}")
async def read_items(
item_id: UUID,
start_datetime: Union[datetime, None] = Body(default=None),
end_datetime: Union[datetime, None] = Body(default=None),
repeat_at: Union[time, None] = Body(default=None),
process_after: Union[timedelta, None] = Body(default=None),
):
start_process = start_datetime + process_after
duration = end_datetime - start_process
return {
"item_id": item_id,
"start_datetime": start_datetime,
"end_datetime": end_datetime,
"repeat_at": repeat_at,
"process_after": process_after,
"start_process": start_process,
"duration": duration,
}