fastapi ---- a Python Web Framework
文章目录
教程
- 官方文档: FastAPI
类似项目对比
与 flask 对比
Why Choose Flask Over FastAPI - Python kitchen
- flask 和 starlette 是 counterpart
- flask-restx 和 fastapi 是 counterpart
兄弟项目
Typer, 命令行工具
相关工具
uvicorn
- 用于运行 fastapi 程序
- ASGI, python 异步 web 服务框架
- 数据验证和设置工具
- 通过 python annotations (typing) 实现
- 在 runtime 时期强制执行 typing(type hint)
类型不一致,给出错误提示
异常:
pydantic.ValidationError- 作用:记录所有的字段错误情况
strawberry
- A Python library for GraphQL | 🍓 Strawberry GraphQL
- 在 python3 中的 GraphQL 实现库
性能测试 Benchmark
依赖包
特点
支持
async def和def并用- 不同的路由,可以分别使用 def 或者 async def, 并不冲突
路由冲突问题
- 先定义,先使用(和 python 默认的方式不一样),不是后面的覆盖前面的
- 后面重复定义的路由,无效
demo
| |
启动命令:
| |
直接在代码中调用 uvicorn
| |
概念
OpenAPI 中概念
Schema 模式
- 定义:一个定义或描述
url path
- 通用名称: endpoint 或 route(路由)
operation
即,请求方法, httpd methods
- POST
- GET
- PUT
- DELETE
- …
Model 数据类型
枚举类型
普通类型
- str, int, float, bool 等
额外类型
参考:
类型:
- UUID
时间:
- datetime.datetime
- datetime.date
- datetime.time
- datetime.timedelta
frozenset
- 类似 set
- bytes
Decimal
- 类似 float
更多类型
- pydantic 类型: Field Types - pydantic
路由 route
路由参数
特点:
允许路由参数是文件路径
参数 parameters
路由参
参考:
定义方法:
- 在 url path(路由)中的
{}包裹起来的参数
验证
参考:
使用工具:
fastapi.Path类
定义方法:
- 路由函数的参数表中,除掉
路由参数以外的参数
验证设置:
- le: <=
- ge: >=
- lt: <
- gt: >
query 参数
参考:
在 url 使用 query 参数:
| |
bool 类型,在 url 中允许的输入值:
- 1/0
- true/false
- on/off
- yes/no
可选参数
- 通过 python 的默认关键字参数实现
必选参数
- 通过 python 的位置关键字参数实现
参数验证 validation
str 类型验证
参考:
应用工具:
fastapi.Query 类
验证类型:
长度
例子
1q: str | None = Query(default=None, min_length=3, max_length=50, regex="^fixedquery$"min_lengthmax_length
正则
regex
- 可选 optional
- 必选 required
- 默认值 default
OpenAPI 文档相关:
- title
- description
- alias
- deprecated
请求主体参数 Request Body
参考:
定义方法:
- 在函数的非 route 参数(路由参数)获取
数据类型是 pydantic BaseModel 子类
注意:
- 普通类型(非 BaseModel 子类)会被作为 query 参数(PUT 请求中)
客户端请求传参位置:
- 不在 url 中体现
- 在请求的 body 中说明
body 参数 + path 参数 + query 参数共存
原则:
- path 参数在
路由中存在,无歧义 普通类型(str, int, float 等)
- 默认是 query 参数
- 强制作为 body 参数, 通过
fastapi.Body类声明
多个 body 参数
参考:
fastapi 处理方式:
- 把整个 request body 当做一个 json -> python dict
- 在这个 python dict 内部查找多个参数
单个 body 参数
嵌套 model 参数
单个属性的验证
工具:
fastapi.Fields
Cookie 参数
Header 参数
OpenAPI 文档支持
model 例子数据
参考:
实现方法:
在 BaseModel 子类的 class Config 中说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15class Item(BaseModel): name: str description: Union[str, None] = None price: float tax: Union[float, None] = None class Config: schema_extra = { "example": { "name": "Foo", "description": "A very nice Item", "price": 35.4, "tax": 3.2, } }在 Body, Field, Query 等的 example 字段中说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14@app.put("/items/{item_id}") async def update_item( item_id: int, item: Item = Body( example={ "name": "Foo", "description": "A very nice Item", "price": 35.4, "tax": 3.2, }, ), ): results = {"item_id": item_id, "item": item} return results- 多个例子,通过
examples字段说明
禁用文档
参考:
方法:
- 通过设置
FastAPI(openapi_url=...)设置
例子:
| |
响应 Response
参考:
demo:
| |
Response Model
指定方法:
- 如上面的例子,通过
response_model字段
model 的作用:
- 数据类型转换
- 验证数据 validate
- OpenAPI 的 json schema
- 文档生成
- 重要: 限制暴露的数据量(没有声明的,会被在返回时过滤掉)
model 字段过滤
解释: 是否在响应中返回 model 中的所有字段
根据默认值和用户设定过滤
解释:如何不使用 model 中的默认值
方法:
通过下述路由装饰器参数 descrator parameter
response_model_exclude_unset=True- 排除 model 定义时,没有设置默认值的字段
response_model_exclude_defaults=True- 排除 model 定义时,设定和默认值的字段
response_model_exclude_none=True- 排除 model 定义时,设定了默认为 None 的字段
| |
手动指定
解释: 手动指定哪些 model 字段在响应中出现
方法:
通过下述路由装饰器参数 descrator parameter
response_model_include=["name", "description"]- 包含 model 的哪些字段
response_model_exclude=["tax"]- 排除 model 的哪些字段
Response Code (http 状态码)
参考:
demo:
| |
方法:
- 通过 route decorator 中
status_code参数指定
工具:
fastapi.status存储所有的状态码
错误处理 handling errors
参考:
注意:
- fastapi 的 exception_handler 能够处理的 ValueError 等或者自定义的异常类,不能处理
Exception异常
demo:
| |
方法:
- 使用
HTTPException类 HTTPException(status_code=404, detail="Item not found")- 把 status_code 错误码(状态码)和 detail 错误原因一起抛出
detail参数- 可以接收 str, dict, list 等数据
headers 参数
- 用于设定 response Header
自定义异常处理
参考:
解释:
- 用于自定义异常的捕获和处理
demo:
| |
默认异常处理器(default exception handlers)
解释:
- fastapi 自己提供用于捕获 HTTPException
Exception 所有异常捕获
fastapi exception_handler 无法正常捕获 Exception 类型
注意:
fastapi 的
@app.exception_handler(Exception)会发生失效问题- 能够进入 exception_handler 但是无法回到 middleware
- 测试版本:v0.108.0
测试代码:
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 33 34 35 36 37 38from fastapi import FastAPI, Request, JSONResponse app = FastAPI() @app.middleware("http") async def add_request_response_log(req: Request, call_next): print(f"before calling req: {id(req)}") resp: Response = await call_next(req) print(f"after calling req: {id(req)}") return resp @app.exception_handler(Exception) def default_exception_handler(req: Request, e: Exception): print(f"error: {e} of Exception") return JSONResponse( status_code=500, content={"msg": str(e), "type": e.__class__.__name__} ) @app.post("/hello") def extract_batch(): # generate task id task_id = generate_task_id() logger.info(f"generated {task_id = } for request id = {request_id}") raise Exception("ERR") raise ValueError("ERR") def main(): import uvicorn uvicorn.run(app) if __name__ == '__main__': main()
正确的 Exception 捕获方法
参考:
python 3.x - Catch `Exception` globally in FastAPI - Stack Overflow
- 使用 middleware + try 包裹 call_next() 内容
| |
测试
后台任务
参考:
解释:
- 接收到请求后,立刻返回,server 在后台处理耗时处理任务
方法:
- 工具:
fastapi.BackgroudTasks - fastapi 会自己创建一个 BackgroudTasks 实例
- 负责后台任务管理
tricks 小技巧
路由参数 route parameter 与 query parameter 任意排序
解释:
- 即,不强制要求 route parameter 在前
参考:
实现方法:
- 通过
*,other-kwargs语法实现
举例:
| |
request body logging
参考:
- kubernetes - How to log raw HTTP request/response in Python FastAPI - Stack O…
- Custom Request and APIRoute class - FastAPI
解决方法:
- 使用 APIRoute
例子:
| |
form 和 file
只是 form 使用 Content-Type: application/www-form-urlencoded, 如果包含 file 使用 Content-Type: multipart/form-data
form 使用和测试
| |
file 上传,客户端发送文件到服务器
- File 类用来上传小文件
- UploadFile 类用来上传大文件
| |
file 上传,大文件上传
参考:
tiangolo/fastapi#58 Streaming Large Files
使用 UploadFile
- 它会优先把文件放到内存中,如果过大,会放到磁盘上
- UploadFile.file 是一个类似 tempfile.SpooledTemporaryFile 的对象
使用 Request.stream() 对象
1 2async for chunk in request.stream(): ...
file 下载,发送文件到客户端
参考:
直接返回 bytes
适合小文件
通过 FileResponse
适合 file-like object
特点:
- 可以收受给定路径的文件
通过 StreamingResponse
适合给中内容作为流式内容传输
参考:
特点:
- 可以接受
generator, 作为数据源 - 可以在 generator 中返回一个 open 的 文件 io 对象
Response
后台任务 BackgroundTasks
无报错,任务不执行错误
原因分析:
- 这实际是一种异步任务的错误追踪问题
- BackgroundTasks.add_task(job_fun) 添加的任务是不会自动抛出异常的
- 如果在发生了 job_fun 执行过程中发生了错误,就会表面上什么也看不到,不会执行后续任务
解决办法:
- 在 job_fun 的最最外层包裹一层 try except Exception 异常捕获,再打印异常到 log 文件
解决例子:
| |
文件下载
向客户端传递文件名
使用 Content-Disposition: attachment;filename=your_filename.txt
| |
文件名包含非 ascii 编码字符处理方法(如:中文名文件名)
参考:
| |
multipart response
参考:
How can I return from a FastAPI service a multipart/form-data response consis…
- 实现逻辑: 使用 requests_toolbelt.MultipartEncoder
Uploading Data — requests_toolbelt 1.0.0 documentation
要点:
- streaming 使用 data=encoder
- 不需要 streaming 使用 data=encoder.to_string()
例子:
fastapi + MultipartEncoder 实现
1 2 3 4 5 6 7 8 9 10 11 12from fastapi.responses import Response from requests_toolbelt import MultipartEncoder ... @app.get("/images_and_metadata/") async def image_and_metadata(): m = MultipartEncoder( fields={'field0': 'value', 'field1': 'value', 'field2': ('filename', open('image1.jpg', 'rb'), 'image/jpeg'), 'field3': ('filename2', open('image2.jpg', 'rb'), 'image/jpeg')} ) return Response(m.to_string(), media_type=m.content_type) ...response 的输出
--500a94d71de847c2b7a6df169732e807 Content-Disposition: form-data; name="field0" value --500a94d71de847c2b7a6df169732e807 Content-Disposition: form-data; name="field1" value --500a94d71de847c2b7a6df169732e807 Content-Disposition: form-data; name="field2"; filename="filename" Content-Type: image/jpg <image bytes> --500a94d71de847c2b7a6df169732e807 Content-Disposition: form-data; name="field3"; filename="filename2" Content-Type: image/jpg <image bytes> --500a94d71de847c2b7a6df169732e807--
requests 请求发送 multipart
streaming:
data=encoder1 2 3 4 5 6 7 8 9 10 11import requests from requests_toolbelt import MultipartEncoder encoder = MultipartEncoder({ 'field': ('file_name', b'{"a": "b"}', 'application/json', {'X-My-Header': 'my-value'}) }) encoder = MultipartEncoder({'field': 'value', 'other_field': 'other_value'}) r = requests.post('https://httpbin.org/post', data=encoder, headers={'Content-Type': encoder.content_type})不需要 streaming:
data=encoder.to_string()1 2 3 4 5 6 7 8 9import requests from requests_toolbelt import MultipartEncoder encoder = MultipartEncoder({'field': 'value', 'other_field': 'other_value'}) r = requests.post('https://httpbin.org/post', data=encoder.to_string(), headers={'Content-Type': encoder.content_type})
Websocket + FastAPI
参考:
使用
| |
UploadFile 如何作为 stream 使用
方法: 直接使用 UploadFile.file 即可, UploadFile.file 本身就是一个 stream 对象
Form 中如何传递 json
方法:
Form 接收一个参数,收到后转换成 BaseModel
- json_str –> json loads –> dict | BaseModel
- 可以使用 Depends 把调用 json.loads 的过程隐藏起来
| |
文章作者
上次更新 2025-06-20 (811ee6f)