FastAPI 是一个现代、快速(高性能)的 Web 框架.
官方文档 -> 🚪 项目源码 -> 🚪 Uvicorn -> 🚪
环境准备 pip install fastapi uvicorn[standard]
基础例子 创建main.py
文件
from typing import Optional from fastapi import FastAPIapp = FastAPI() @app.get("/" ) def read_root (): return {"Hello" : "World" } @app.get("/items/{item_id}" ) def read_item (item_id: int , q: Optional [str ] = None ): return {"item_id" : item_id, "q" : q}
关于函数是否使用async
?
文档参考 -> 🚪
如果不清楚函数内调用是否异步, 那就定义为普通函数, Fastapi
会放到thread pool
里执行; 如果有使用异步并且是在高并发情景下, 使用async
速度会更快.
运行启动 命令行启动 uvicorn main:app --reload
main
: main.py
文件(一个 Python "模块")。
app
: 在 main.py
文件中通过 app = FastAPI()
创建的对象。
--reload
: 让服务器在更新代码后重新启动。仅在开发时使用该选项。
--host
: 设置映射IP, 默认127.0.0.1
--port
: 设置映射端口, 默认8000
访问 http://127.0.0.1:8000/items/5?q=somequery 会返回 {"item_id": 5, "q": "somequery"}
程序内启动 可以用于调试
import uvicornif __name__ == '__main__' : uvicorn.run(app, host="127.0.0.1" , port=8000 )
自带文档
访问 http://127.0.0.1:8000/docs 是由Swagger UI 自动生成的交互式文档
访问 http://127.0.0.1:8000/redoc 是由ReDoc 生成自动生成的交互式文档
设置枚举类型 导入Enum
继承并声明枚举类
class ModelName (str, Enum ): alexnet = "alexnet " resnet = "resnet " lenet = "lenet "
将其声明为参数
@app.get("/models/{model_name}" ) async def get_model (model_name: ModelName ): if model_name == ModelName.alexnet: return {"model_name" : model_name, "message" : "Deep Learning FTW!" } if model_name.value == "lenet" : return {"model_name" : model_name, "message" : "LeCNN all the images" } return {"model_name" : model_name, "message" : "Have some residuals" }
设置请求体 基础应用 导入BaseModel
from pydantic import BaseModel
继承并声明数据模型类
class Item (BaseModel ): name: str description: Optional [str ] = None price: float tax: Optional [float ] = None
将其声明为参数
@app.post("/items/" ) def create_item (item: Item ): return item
请求格式应为
{ "name" : "Foo" , "description" : "An optional description" , "price" : 45.2 , "tax" : 3.5 }
多个请求体参数 class User (BaseModel ): username: str full_name: Optional [str ] = 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
请求格式应为
{ "item" : { "name" : "Foo" , "description" : "The pretender" , "price" : 42.0 , "tax" : 3.2 }, "user" : { "username" : "dave" , "full_name" : "Dave Grohl" } }
单个请求体参数 导入Body
使用Body()
设置
@app.post("/items/{item_id}" ) async def update_item (item_id: int , item: Item = Body(..., embed=True ) ): results = {"item_id" : item_id, "item" : item} return results
请求格式应为
{ "item" : { "name" : "Foo" , "description" : "The pretender" , "price" : 42.0 , "tax" : 3.2 } }
嵌套格式请求体 class Image (BaseModel ): url: HttpUrl name: str class Item (BaseModel ): 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
请求格式应为
{ "images" : [ { "url" : "http://example.com/baz.jpg" , "name" : "The Foo live" }, { "url" : "http://example.com/dave.jpg" , "name" : "The Baz" } ] }
为参数添加校验 查询参数校验 导入Query
from fastapi import Query
将 Query
用作查询参数的默认值,并设置min_length
为3,max_length
为9
@app.get("/items/" ) async def read_items (q: Optional [str ] = Query(None , min_length=3 , max_length=9 ) ): results = {"items" : [{"item_id" : "Foo" }, {"item_id" : "Bar" }]} if q: results.update({"q" : q}) return results
其它校验参数(对应类型为int或float)
gt
:大于(g
reater t
han)
ge
:大于等于(g
reater than or e
qual)
lt
:小于(l
ess t
han)
le
:小于等于(l
ess than or e
qual)
也可以用正则表达式进行判断regex
Query(None , regex=r".{3,9}" )
测试一下
http://127.0.0.1:8000/items/?q=12
http://127.0.0.1:8000/items/?q=123456
http://127.0.0.1:8000/items/?q=1234567890
使用参数别名 在Query中使用alias
, 设置q
别名为item-query
@app.get('/items/' ) async def read_items (q: Optional [str ] = Query(None , alias="item-query" ) ): return {'q' : q}
测试一下
http://127.0.0.1:8000/items?q=hello
http://127.0.0.1:8000/items?item-query=hello
路径参数校验 导入Path
使用方法和Query
一样, 第一个参数为默认参数, 使用...
表示该参数为必需参数
@app.get("/items/{item_id}" ) async def read_items ( *, item_id: int = Path(..., title="The ID of the item to get" , ge=0 , le=1000 ), size: float = Query(..., gt=0 , lt=10.5 ) ): return {"item_id" : item_id}
测试一下
http://127.0.0.1:8000/items/1000?size=1.2
http://127.0.0.1:8000/items/100
请求体字段校验
官方参考 -> 🚪
注: Body、Query、Path的大部分参数是一样的
导入
from fastapi import Header, Cookie
声明
@app.get("/items/" ) def read_items (user_agent: Optional [str ] = Header(None ), ads_id: Optional [str ] = Cookie(None ): return { "User-Agent" : user_agent, "ads_id" : ads_id }
响应和输出 设置输出模型 @app
设置response_model
参数,
from typing import Optional from fastapi import FastAPIfrom pydantic import BaseModel, EmailStrapp = FastAPI() class UserIn (BaseModel ): username: str password: str email: EmailStr full_name: Optional [str ] = None class UserOut (BaseModel ): username: str email: EmailStr full_name: Optional [str ] = None @app.post("/user/" , response_model=UserOut ) async def create_user (user: UserIn ): return user
设置响应不包含默认值 @app
设置response_model_exclude_unset
参数为True
, 不想获取过多Json可以用的上.
from typing import List , Optional from fastapi import FastAPIfrom pydantic import BaseModelapp = FastAPI() class Item (BaseModel ): name: str description: Optional [str ] = None price: float tax: float = 10.5 tags: List [str ] = [] items = { "foo" : {"name" : "Foo" , "price" : 50.2 } } @app.get("/items/{item_id}" , response_model=Item, response_model_exclude_unset=True ) async def read_item (item_id: str ): return items[item_id]
响应为
{ "name" : "Foo" , "price" : 50.2 }
而不是
{ "name" : "Foo" , "description" : null , "price" : 50.2 , "tax" : 10.5 , "tags" : [ ] }
设置响应状态码 @app
设置参数status_code
, 响应时会返回对应状态码, 不想暴露真实状态码时会很有用.
from fastapi import FastAPI, statusapp = FastAPI() @app.post("/items/" , status_code=status.HTTP_201_CREATED ) async def create_item (name: str ): return {"name" : name}
PS: 到这里, 简单的API应用就没问题了, 接下来是一些进阶技巧
中间件
"中间件"是一个函数,它在每个请求 被特定的路径操作 处理之前,以及在每个响应 返回之前工作.
使用装饰器 @app.middleware("http")
来添加中间件
第一个参数: request
第二个参数: 一个函数 call_next
(函数名可自定义)
它将接收 request
作为参数
将 request
传递给相应的 路径操作
然后它将返回由相应的 路径操作 生成的 response
.
import timefrom fastapi import FastAPI, Requestapp = FastAPI() @app.get("/" ) async def test (num: int ): return {"num" : num} @app.middleware("http" ) async def add_process_time_header (request: Request, call_next ): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time response.headers["X-Process-Time" ] = str (process_time) return response @app.middleware("http" ) async def set_status_code_eq_200 (request: Request, call_next ): print ('set code eq 200' ) response = await call_next(request) response.status_code = 200 return response if __name__ == '__main__' : uvicorn.run(app, host="127.0.0.1" , port=8000 )
使用调试模式测试一下, 顺便观察下中间件的执行顺序
http://127.0.0.1:8000/?num=22
http://127.0.0.1:8000/?num=str
执行顺序是从下往上先执行call_next
前的代码,然后执行call_next
后, 再从上往下执行call_next
后的部分, 可以看成栈的操作顺序, 先入后出, 后入先出.
跨域资源共享
指浏览器中运行的前端拥有与后端通信的 JavaScript 代码,而后端处于与前端不同的「源」的情况.
源是协议(http
,https
)、域(myapp.com
,localhost
,localhost.tiangolo.com
)以及端口(80
、443
、8080
)的组合。
因此,这些都是不同的源:
http://localhost
https://localhost
http://localhost:8080
即使它们都在 localhost
中,但是它们使用不同的协议或者端口,所以它们都是不同的「源」
from fastapi import FastAPIfrom fastapi.middleware.cors import CORSMiddlewareapp = FastAPI() origins = [ "http://localhost.tiangolo.com" , "https://localhost.tiangolo.com" , "http://localhost" , "http://localhost:8080" , ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True , allow_methods=["*" ], allow_headers=["*" ], ) @app.get("/" ) async def main (): return {"message" : "Hello World" }
其它应用 -> 🚪
创建后台任务
使用场景: 邮件发送, 图片渲染下载...
使用BackgroundTasks
from fastapi import BackgroundTasks, FastAPIapp = FastAPI() def write_notification (email: str , message="" ): with open ("log.txt" , mode="w" ) as email_file: content = f"notification for {email} : {message} " email_file.write(content) @app.post("/send-notification/{email}" ) async def send_notification (email: str , background_tasks: BackgroundTasks ): background_tasks.add_task(write_notification, email, message="some notification" ) return {"message" : "Notification sent in the background" }
其它功能
以下部分属于比较少用到或者是知识量较多需要深入学习的, 故只提供官方链接以供参考
使用其它数据类型
设置请求文件
错误异常处理
请求体更新数据
使用关系数据库