目录
简介
官网教程(中文版):FastAPI
python语言下的Web框架有很多,比如:
-
从大而全的Django。
-
小而且美的Flask。
-
很早就支持异步的 Tornado。
-
性能更进一步的异步框架 sanic。
-
......
fastapi在如何把后端api做好的这件事情上,做的比sanic更全面,更彻底。fastapi的安装也非常简单,如下:
安装FastAPI
pip install fastapi
安装uvicorn来作为服务器
pip install uvicorn
Ps:若要一次性安装所有的可选依赖及对应功能:
pip install fastapi[all]
fastapi的优点很多,比如:性能、api文档、高效的类型检查等
性能
先做个简单的性能对比:
-
flask
# flask==2.0.1
from flask import Flask
from flask import jsonify
app = Flask(__name__)
@app.route("/")
def hello_world():
return jsonify({"hello": "world"})
-
fastapi
# fastapi==0.65.1
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
虽然两个框架都支持异步(flask 2.0 支持异步),但我们使用的都是同步代码。
JMeter配置:并发(500)* 循环(100)* 启动时间(1s) = 总请求数(50000)
结果 | flask | fastapi | gin |
---|---|---|---|
运行时长 | 67s | 28s | 5s |
最大值 | 37197ms | 641ms | 270ms |
平均值 | 582ms | 266ms | 40ms |
吞吐量 | 751.s/sec | 1798.3/sec | 9817.4/sec |
虽然是简单的对比,fastapi 在各项性能指标,都有非常明显的性能优势。
Ps:
在相同的环境上开启了gin(go语言) 服务又跑了一遍。gin 在go 主流的几款 web框架下性能表现都是很强的,碾压python Web框架简直太容易了。最后一列是gin的数据。
api 文档
fastapi直接支持OpenAPI(前身是Swagger) 和redoc 两种文档格式。
fastapi_demo.py
# -*- coding: UTF-8 -*-
# fastapi==0.65.1
import time
import uvicorn
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Optional[bool] = None
@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}
@app.put("/items/{item_id}")
async def read_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
if __name__ == '__main__':
# 使用uvicorn启动服务,指定端口8099
uvicorn.run(app='fastapi_demo:app', host="0.0.0.0", port=8099, reload=True, debug=True)
- 启动服务:python3 fastapi_demo.py
- 访问服务(API文档):http://127.0.0.1:8099/docs
- 访问服务(API备用文档):http://127.0.0.1:8000/redoc
代码即文档,压根不用写接口文档。试问:是不是很爽?
类型检查
python是弱类型的语言,直到python 3.5 才加入类型系统。而我们在做接口参数校验的时候,必定要写大量代码验证参数是否为空,类型是否正确。
- flask
import json
from flask import Flask
from flask import jsonify
from flask import request
app = Flask(__name__)
@app.route('/items/<int:item_id>', methods=['GET', 'POST', "PUT", "DELETE"])
def update_item(item_id):
if request.method == "PUT":
try:
data = json.loads(request.get_data())
except json.decoder.JSONDecodeError:
return jsonify({"code":10101, "msg": "format error"})
try:
name = data["name"]
price = data["price"]
is_offer = data["is_offer"]
except KeyError:
return jsonify({"code": 10102, "msg": "key null"})
if not isinstance(name, str):
return jsonify({"code": 10103, "msg": "name not is str"})
if not isinstance(price, float):
return jsonify({"code": 10104, "msg": "price not is float"})
if not isinstance(is_offer, bool):
return jsonify({"code": 10105, "msg": "is_offer not is bool"})
return jsonify({"item_name": name, "item_id": item_id})
在flask中为了验证参数是否为空,以及参数的类型,必须要写大量的异常和类型判断的代码。
-
fastapi
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: Optional[bool] = None
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
fastapi 通过 pydantic 检查参数类型,有一点像go的结构体,简直不要太简单。
Ps:Pydantic库简介:Python笔记:Pydantic库简介_codename_cys的博客-CSDN博客_pydantic
蓝图APIRouter
我们都知道在大型的应用程序或者 web api 中, 我们很少在一个文件中写入多个路由,将所有的请求方法写在同一个处理文件下面的话,会导致我们的代码显得很没有逻辑性,这样既不利于程序的扩展,也不利于程序日后的维护。在 Flask 中,我们一般用蓝图 Blueprint 来处理,那么在FastApi 中如何处理呢?
当然可以,在 FastApi 中使用 APIRouter 处理这种多程序分类,即类似 Flask 中的蓝图。可以直接参考:更大的应用 - 多个文件 - FastAPI
假设我们的系统有2个模块:users和items
- users模块
假设专门用于处理用户的文件是的子模块/app/routers/users.py
您希望将与用户相关的路径操作与其余代码分开,使其看起来简洁明了。
可以使用来为该模块创建路径操作 APIRouter。
./route/users.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
@router.get("/users/me", tags=["users"])
async def read_user_me():
return {"username": "fakecurrentuser"}
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
return {"username": username}
web 服务中还有另外一个应用模块,items。
同样的通过APIRouter来对其路由进行注册,代码如下:./router/items.py
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
router = APIRouter(
prefix="/items",
tags=["items"],
responses={403: {"description": "Operation forbidden"}}
)
@router.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
if item_id != 123:
raise HTTPException(status_code=403)
return {"item_id": item_id, "q": q}
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id != "123":
raise HTTPException(status_code=404, detail="Item not found")
return {"name": "rongsong", "item_id": item_id}
这样就将两个功能模块 item, user
给分离开来了,后期我们想更新或扩展 user 模块的功能,并不会对 item 造成影响!
上面便是 APIRouter
最最基础也是最强大之处,还有其他功能吗?当然有!
自定义的tags与responses
细心的朋友应该发现了,在上述 item.py
中实例化 router
的时候,传了好几个参数
一起来看看分别代表什么含义!
prefix
参数,路由的前缀
tags
将应用于特定路径操作的内容
responses
指特定于该路径下的响应内容,如上述便指定 404
的返回信息
注册 APIRouter
最后一个步骤就是要将我们的 APIRouter 注册到核心对象上去
和之前我们创建主文件一样导入 FastApi,以及声明的 APIRouter 实例。
main.py
import uvicorn
from fastapi import Depends, FastAPI
from router import items, users
app = FastAPI()
app.include_router(users.router)
app.include_router(items.router)
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
if __name__ == '__main__':
# 使用uvicorn启动服务,端口8099
uvicorn.run(app='main:app', host="0.0.0.0", port=8099, reload=True, debug=True)
其中 include_router() 函数就是上面说的注册。这时候就完成了,使用该 app 来启动服务即可
- 启动命令:python3 main.py
- 访问服务:http://127.0.0.1:8099/docs,正常情况下显示如下所示:
更多的内容可以参考官网教程(中文版):FastAPI