面向 Flask 用户的 FastAPI
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
虽然 Flask 已成为机器学习项目中 API 开发的实际选择,但一个名为 FastAPI 的新框架也获得了社区的广泛关注。
我最近决定尝试一下 FastAPI,方法是将一个生产环境的 Flask 项目移植到 FastAPI 上。由于之前使用过 Flask,所以上手 FastAPI 非常容易,我只用了几个小时就完成了部署和运行。
自动数据验证、文档生成以及内置的最佳实践(如 pydantic schemas 和 python 类型)等附加优势,使其成为未来项目的有力选择。
在这篇文章中,我将通过对比 Flask 和 FastAPI 在各种常见用例中的实现方式来介绍 FastAPI。
版本信息:
截至撰写本文时,Flask 版本为 1.1.2,FastAPI 版本为 0.58.1。
安装
Flask 和 FastAPI 都可以在 PyPI 上找到。对于 conda,你需要使用conda-forge指定的通道来安装 FastAPI,而 Flask 则在默认通道中即可安装。
烧瓶:
pip install flask
conda install flask
FastAPI:
pip install fastapi uvicorn
conda install fastapi uvicorn -c conda-forge
运行“Hello World”
烧瓶:
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
app.run()
现在您可以使用以下命令运行开发服务器。它默认运行在 5000 端口。
python app.py
FastAPI
# app.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
uvicorn.run(app)
FastAPI 将服务交付给一个名为 的生产就绪服务器uvicorn。我们可以在开发模式下运行它,默认端口为 8000。
python app.py
生产服务器
烧瓶:
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
app.run()
对于生产服务器而言,gunicorn这是 Flask 中的常见选择。
gunicorn app:app
FastAPI
# app.py
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
def home():
return {'hello': 'world'}
if __name__ == '__main__':
uvicorn.run(app)
FastAPI 将服务交付给一个名为uvicorn的生产就绪服务器。我们可以这样启动服务器:
uvicorn app:app
您也可以通过运行以下命令以热重载模式启动它:
uvicorn app:app --reload
此外,您还可以更改端口。
uvicorn app:app --port 5000
工人数量也可以加以控制。
uvicorn app:app --workers 2
您可以使用gunicorn以下命令来管理 uvicorn。所有常规的 gunicorn 参数,例如-w工作进程数()都适用。
gunicorn -k uvicorn.workers.UvicornWorker app:app
HTTP 方法
烧瓶:
@app.route('/', methods=['POST'])
def example():
...
FastAPI:
@app.post('/')
def example():
...
每个HTTP方法都有单独的装饰器方法。
@app.get('/')
@app.put('/')
@app.patch('/')
@app.delete('/')
URL变量
我们想从 URL 中获取用户 ID /users/1,然后将用户 ID 返回给用户。
烧瓶:
@app.route('/users/<int:user_id>')
def get_user_details(user_id):
return {'user_id': user_id}
FastAPI:
在 FastAPI 中,我们使用 Python 的类型提示来指定所有数据类型。例如,这里我们指定该值user_id应为整数。URL 路径中的变量也以类似于 f-string 的方式进行指定。
@app.get('/users/{user_id}')
def get_user_details(user_id: int):
return {'user_id': user_id}
查询字符串
我们希望允许用户通过?q=abc在 URL 中使用查询字符串来指定搜索词。
烧瓶:
from flask import request
@app.route('/search')
def search():
query = request.args.get('q')
return {'query': query}
FastAPI:
@app.get('/search')
def search(q: str):
return {'query': q}
JSON POST 请求
我们来看一个简单的例子,我们想要发送一个带有text键的 JSON POST 请求,并获取返回的小写版本。
# Request
{"text": "HELLO"}
# Response
{"text": "hello"}
烧瓶:
from flask import request
@app.route('/lowercase', methods=['POST'])
def lower_case():
text = request.json.get('text')
return {'text': text.lower()}
FastAPI:
如果您只是想复制 Flask 的功能,可以在 FastAPI 中按如下方式操作。
from typing import Dict
@app.post('/lowercase')
def lower_case(json_data: Dict):
text = json_data.get('text')
return {'text': text.lower()}
但是,FastAPI 引入了一个新概念,即创建 Pydantic schema,该 schema 可以映射到接收到的 JSON 数据。我们可以使用 Pydantic 重构上面的示例,如下所示:
from pydantic import BaseModel
class Sentence(BaseModel):
text: str
@app.post('/lowercase')
def lower_case(sentence: Sentence):
return {'text': sentence.text.lower()}
如您所见,JSON 数据并非以字典形式返回,而是被转换为模式对象Sentence。因此,我们可以使用诸如 `type` 之类的数据属性来访问数据sentence.text。这还实现了数据类型的自动验证。如果用户尝试发送字符串以外的任何数据,系统将自动生成验证错误。
无效请求示例
{"text": null}
自动回复
{
"detail": [
{
"loc": [
"body",
"text"
],
"msg": "none is not an allowed value",
"type": "type_error.none.not_allowed"
}
]
}
文件上传
让我们创建一个 API 来返回上传的文件名。上传文件时使用的键是file.
Flask
允许通过请求对象访问已上传的文件。
# app.py
from flask import Flask, request
app = Flask(__name__)
@app.route('/upload', methods=['POST'])
def upload_file():
file = request.files.get('file')
return {'name': file.filename}
FastAPI:
FastAPI 使用函数参数来指定文件键。
# app.py
from fastapi import FastAPI, UploadFile, File
app = FastAPI()
@app.post('/upload')
def upload_file(file: UploadFile = File(...)):
return {'name': file.filename}
表格提交
我们想要访问如下所示定义的文本表单字段,并输出其值。
<input name='city' type='text'>
Flask
允许通过请求对象访问表单字段。
# app.py
from flask import Flask, request
app = Flask(__name__)
@app.route('/submit', methods=['POST'])
def echo():
city = request.form.get('city')
return {'city': city}
FastAPI:
我们使用函数参数来定义表单字段的键和数据类型。
# app.py
from fastapi import FastAPI, Form
app = FastAPI()
@app.post('/submit')
def echo(city: str = Form(...)):
return {'city': city}
我们还可以像下面这样将表单字段设为可选字段。
from typing import Optional
@app.post('/submit')
def echo(city: Optional[str] = Form(None)):
return {'city': city}
同样,我们可以为表单字段设置默认值,如下所示。
@app.post('/submit')
def echo(city: Optional[str] = Form('Paris')):
return {'city': city}
曲奇饼
我们希望访问name请求中名为“cookie”的 cookie。
Flask
允许通过请求对象访问 cookie。
# app.py
from flask import Flask, request
app = Flask(__name__)
@app.route('/profile')
def profile():
name = request.cookies.get('name')
return {'name': name}
FastAPI:
我们使用参数来定义 cookie 的键。
# app.py
from fastapi import FastAPI, Cookie
app = FastAPI()
@app.get('/profile')
def profile(name = Cookie(None)):
return {'name': name}
模块化视图
我们希望将单个 app.py 文件中的视图分解成单独的文件。
- app.py
- views
- user.py
Flask:
在 Flask 中,我们使用称为蓝图的概念来管理这一切。我们首先会创建一个用户视图蓝图,如下所示:
# views/user.py
from flask import Blueprint
user_blueprint = Blueprint('user', __name__)
@user_blueprint.route('/users')
def list_users():
return {'users': ['a', 'b', 'c']}
然后,将此视图注册到主app.py文件中。
# app.py
from flask import Flask
from views.user import user_blueprint
app = Flask(__name__)
app.register_blueprint(user_blueprint)
FastAPI:
在 FastAPI 中,相当于蓝图的东西称为路由器。首先,我们创建一个用户路由器,如下所示:
# routers/user.py
from fastapi import APIRouter
router = APIRouter()
@router.get('/users')
def list_users():
return {'users': ['a', 'b', 'c']}
然后,我们将此路由器附加到主应用程序对象,如下所示:
# app.py
from fastapi import FastAPI
from routers import user
app = FastAPI()
app.include_router(user.router)
数据验证
Flask
本身并不提供任何开箱即用的输入数据验证功能。通常的做法是编写自定义验证逻辑,或者使用像marshmalllow或pydantic这样的库。
FastAPI:
FastAPI 将 pydantic 封装到其框架中,并允许通过简单地结合使用 pydantic schema 和 python 类型提示来进行数据验证。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
age: int
@app.post('/users')
def save_user(user: User):
return {'name': user.name,
'age': user.age}
这段代码会自动进行验证,确保name参数是字符串或age整数。如果发送了其他数据类型,它会自动生成验证错误信息。
以下是一些常见用例的 pydantic schema 示例。
示例 1:键值对
{
"name": "Isaac",
"age": 60
}
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
例2:物品集合
{
"series": ["GOT", "Dark", "Mr. Robot"]
}
from pydantic import BaseModel
from typing import List
class Metadata(BaseModel):
series: List[str]
示例 3:嵌套对象
{
"users": [
{
"name": "xyz",
"age": 25
},
{
"name": "abc",
"age": 30
}
],
"group": "Group A"
}
from pydantic import BaseModel
from typing import List
class User(BaseModel):
name: str
age: int
class UserGroup(BaseModel):
users: List[User]
group: str
您可以点击此处了解更多关于Python类型提示的信息。
自动文档
Flask
本身并不提供任何内置的文档生成功能。虽然有flask-swagger或flask-restful等扩展可以弥补这一不足,但工作流程相对复杂。
FastAPI:
FastAPI 会自动生成一个交互式 Swagger 文档端点,/docs以及一个参考文档/redoc。
例如,假设我们有一个简单的视图,如下所示,它反映了用户搜索的内容。
# app.py
from fastapi import FastAPI
app = FastAPI()
@app.get('/search')
def search(q: str):
return {'query': q}
Swagger 文档
如果运行服务器并访问该端点http://127.0.0.1:8000/docs,您将获得自动生成的 Swagger 文档。
您可以直接通过浏览器以交互方式试用 API。
ReDoc 文档
除了 Swagger 之外,如果您访问该端点http://127.0.0.01:8000/redoc,还会获得一份自动生成的参考文档。其中包含有关参数、请求格式、响应格式和状态代码的信息。
跨域资源共享(CORS)
Flask
本身并不提供 CORS 支持。我们需要使用诸如flask-cors之类的扩展来配置 CORS,如下所示。
# app.py
from flask import Flask
from flask_cors import CORS
app_ = Flask(__name__)
CORS(app_)
FastAPI:
FastAPI 提供了一个内置中间件来处理 CORS。下面我们展示一个 CORS 示例,其中我们允许任何来源访问我们的 API。
# app.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=['*'],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
结论
因此,FastAPI 是 Flask 的绝佳替代方案,它能够构建健壮且内置最佳实践的 API。您可以参考文档了解更多信息。
连接
如果您喜欢这篇博文,欢迎在Twitter上关注我,我每周都会在那里分享新的博文。
文章来源:https://dev.to/amitness/fastapi-for-flask-users-1bg4


