从Flask到FastAPI的平滑迁移
本文最初发布于 BetterProgramming,经原作者授权由 InfoQ 中文站翻译并分享。
本文将介绍 FastAPI 背后的基本概念,以及将 Flask 服务器过渡到 FastAPI 服务器所涉及的步骤和代码对比以供参考。根据 官方文档,FastAPI 的框架是:
...... 一个现代、快速(高性能)的 Web 框架,基于标准 Python 类型提示,使用 Python 3.6+ 构建 API。
众所周知,Flask 是百分百 WSGI(Web Server Gateway Interface,Web 服务器网关接口)的微型 web 框架。随着发展,ASGI(Asynchronous Server Gateway Interface,异步服务器网关接口)作为 WSGI 精神的继承者,实现了在 I/O 绑定的语境下的高吞吐量,支持 HTTP/2 以及 WebSockets,这些都是 WSGI 所不能及的。
随着科技的发展,快如闪电的 ASGI 服务器 Uvicorn 诞生了。然而,Uvicorn 也仅仅只是一个不具备任何路由功能的 web 服务器。Starlette 的出现则是在 ASGI 的服务器(Uvicorn、Daphne,以及 Hypercorn)的基础上提供了一套完整的 ASGI 工具箱。如果要说这二者有什么直接的区别,Starlette 是 ASGI 的 web 框架,而 Flask 则是 WSGI 的 web 框架。
FastAPI 框架充分利用 Starlette 的功能和 Flask 的编程风格,打造出了一款类 Flask 的 ASGIweb 框架。除此之外,作为创建 RESTful API 的理想 web 框架,FastAPI 还包含以下功能:
数据校验。使用 Pydantic 确保运行时强制执行类型提示,数据无效时会有用户友好的错误提示。
文档生成。支持 JSON Scheme 自动生成的数据模型文档,自带 Swagger UI 和 ReDoc 两个交互式 API 文档。
尽管 Flask 和 FastAPI 这两种框架在代码编写上所需时间基本一致,但 FastAPI 自带的 Web 服务器数据校验和数据模型文档生成可以让团队的开发流程更加顺利。
下面让我们动起手来,开始安装必要的模块。
强烈建议在开始安装前先搭建一个虚拟环境。如果你只想试水 FastAPI,那么安装 FastAPI 和 Uvicorn 即可,其余的安装包都是可选项,本篇教程中也会有所讲解。
pip install fastapi
pip install uvicorn
pip install jinja2
pip install aiofiles
pip install python-multipart
pip install flask
下面,让我们进入正题,开始应用阶段。
这一部分将展示 Flask 服务器与 FastAPI 服务器在相同 API 和功能中的代码对比。
from flask import Flask, request, jsonify, render_template, send_from_directory
import random
from fastapi import FastAPI, Form, Request
from fastapi.responses import PlainTextResponse, HTMLResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
import random
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
import random # needed for generating a random number for an API
import uvicorn # optional if you run it directly from terminal
app = Flask(__name__)
app = FastAPI()
# 可选,用于渲染静态文件
app.mount("/static", StaticFiles(directory="static"), name="static")
# 可选,用于模板引擎渲染网页
templates = Jinja2Templates(directory="templates")
# 视使用情况
class Item(BaseModel):
language = 'english'
@app.route('/')
def hello():
return "Hello World!"
FastAPI 版本的“Hello World”如下。因为默认返回类型为 JSON,所以需要修改 response_class 到 PlainTextResponse 来返回字符串。
@app.get("/", response_class=PlainTextResponse)
async def hello():
return "Hello World!"
@app.route('/random-number')
def random_number():
return str(random.randrange(100))
@app.get('/random-number', response_class=PlainTextResponse)
async def random_number():
return str(random.randrange(100))
@app.route('/alpha', methods=['GET'])
def alpha():
text = request.args.get('text', '')
result = {'text': text, 'is_alpha' : text.isalpha()}
return jsonify(result)
首先定义 HTTP 请求方法和装饰器,这在 FastAPI 中被称作操作(operation)。GET 操作需要调用 app.get 来完成;而对于多个功能相同的 HTTP 请求方法则需要将其逻辑打包到一个函数中,然后在其各自的操作中独立调用。
app.get('/alpha')
async def alpha(text: str):
result = {'text': text, 'is_alpha' : text.isalpha()}
return result
@app.route('/create-user', methods=['POST'])
def create_user():
id = request.form.get('id', '0001')
name = request.form.get('name', 'Anonymous')
# 用于认证、校验、更新数据库
data = {'id': id, 'name': name}
result = {'status_code': '0', 'status_message' : 'Success', 'data': data}
return jsonify(result)
@app.post('/create-user')
async def create_user(id: str = Form(...), name: str = Form(...)):
# 用于认证、校验、更新数据库
data = {'id': id, 'name': name}
result = {'status_code': '0', 'status_message' : 'Success', 'data': data}
return result
@app.route('/update-language', methods=['POST', 'PUT', 'GET', 'DELETE'])
def update_language():
language = 'english'
if request.method == 'PUT':
json_data = request.get_json()
language = json_data['language']
return "Successfully updated language to %s" % (language)
class Item(BaseModel):
language = 'english'
@app.put('/update-language', response_class=PlainTextResponse)
async def update_language(item: Item):
language = item.language
return "Successfully updated language to %s" % (language)
在 Flask 中服务网页相对比较简单,使用 Jinja2 模板引擎即可完成。我们只需要在 templates 的文件夹中声明 HTML 文件,如果你需要提供静态文件,则需要将其放入名为 static 的文件夹中。
@app.route('/get-webpage', methods=['GET'])
def get_webpage():
return render_template('index.html', message="Contact Us")
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
这些都是用于渲染 HTML 模板的。为提供 HTML 网页,我们需要将 response_class 改为 HTMLResponse。如果你用的不是模板引擎,那么可以直接将结果返回为字符串。
@app.get('/get-webpage', response_class=HTMLResponse)
async def get_webpage(request: Request):
return templates.TemplateResponse("index.html", {"request": request, "message": "Contact Us"})
若要将文件返回给用户,最佳的处理方式是通过内置函数 send_from_directory,如果路径或文件是通过用户输入获得,那么则更应如此。该内置函数接受两个主要输入:
文件路径
文件名
另外,你也可以额外声明其他参数,诸如:as_attachment,通过修改 Content-Disposition 头来指定其为附件。
<type:variable_name>
语法来指定。在本例中,我们用
<string:language>
。
@app.route('/get-language-file/<string:language>', methods=['GET'])
def get_language_file(language):
return send_from_directory('./static/language', language + '.json', as_attachment=True)
FastAPI 根据要求和需要,提供了相当多的响应类型。如果需要返回文件,可以用 FileResponse 或 StreamingResponse。在本文中我们将展示 FileResponse 的使用案例,它接受以下输入:
path:需要流式传输的文件路径
headers:任何自定义头,以字典形式输入
media_type:给定媒体类型的字符串。默认通过文件名或路径推断媒体类型。
filename:设置后,会被响应的 Content-Disposition 引用。
@app.get('/get-language-file/{language}')
async def get_language_file(language: str):
file_name = "%s.json" % (language)
file_path = "./static/language/" + file_name
return FileResponse(path=file_path, headers={"Content-Disposition": "attachment; filename=" + file_name})
if __name__ == '__main__':
app.run('0.0.0.0',port=8000)
python myapp.py
import uvicorn
if __name__ == '__main__':
uvicorn.run('myapp:app', host='0.0.0.0', port=8000)
python myapp.py
uvicorn myapp:app
还可以再额外指定一些参数,诸如:
reload:启用自动加载功能,修改文件后会刷新服务器。对本地开发非常有用。
port:服务器端口,默认为 8000。
uvicorn myapp:app --reload --port 5000
# 导入声明
from flask import Flask, request, jsonify, render_template, send_from_directory
import random
# 初始化
app = Flask(__name__)
# hello world,GET 方法,返回字符串
@app.route('/')
def hello():
return "Hello World!"
# 随机数,GET 方法,返回字符串
@app.route('/random-number')
def random_number():
return str(random.randrange(100))
# 检查 isAlpha,GET 方法,查询参数,返回 JSON
@app.route('/alpha', methods=['GET'])
def alpha():
text = request.args.get('text', '')
result = {'text': text, 'is_alpha' : text.isalpha()}
return jsonify(result)
# 创建新 user,POST 方法,表单字段,返回 JSON
@app.route('/create-user', methods=['POST'])
def create_user():
id = request.form.get('id', '0001')
name = request.form.get('name', 'Anonymous')
# 用于认证、校验、更新数据库
data = {'id': id, 'name': name}
result = {'status_code': '0', 'status_message' : 'Success', 'data': data}
return jsonify(result)
# 更新 language,PUT 方法,JSON 输入,返回字符串
@app.route('/update-language', methods=['POST', 'PUT', 'GET', 'DELETE'])
def update_language():
language = 'english'
if request.method == 'PUT':
json_data = request.get_json()
language = json_data['language']
return "Successfully updated language to %s" % (language)
# 服务网页,GET 方法,返回 HTML
@app.route('/get-webpage', methods=['GET'])
def get_webpage():
return render_template('index.html', message="Contact Us")
# 文件响应,GET 方法,返回文件为附件
@app.route('/get-language-file/<string:language>', methods=['GET'])
def get_language_file(language):
return send_from_directory('./static/language', language + '.json', as_attachment=True)
# main
if __name__ == '__main__':
app.run('0.0.0.0',port=8000)
# 导入声明
from fastapi import FastAPI, Form, Request
from fastapi.responses import PlainTextResponse, HTMLResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
import random
import uvicorn
# 初始化
app = FastAPI()
# “挂载”静态文件夹,用于渲染静态文件
app.mount("/static", StaticFiles(directory="static"), name="static")
# Jinja2 模板,用于利用模板引擎返回网页
templates = Jinja2Templates(directory="templates")
# Pydantic 数据模型 class
class Item(BaseModel):
#language: str
language = 'english'
# hello world,GET 方法,返回字符串
@app.get("/", response_class=PlainTextResponse)
async def hello():
return "Hello World!"
# 随机数,GET 方法,返回字符串
@app.get('/random-number', response_class=PlainTextResponse)
async def random_number():
return str(random.randrange(100))
# 检查 isAlpha,GET 方法,查询参数,返回 JSON
@app.get('/alpha')
async def alpha(text: str):
result = {'text': text, 'is_alpha' : text.isalpha()}
return result
# 创建新 user,POST 方法,表单字段,返回 JSON
@app.post('/create-user')
async def create_user(id: str = Form(...), name: str = Form(...)):
# 用于认证、校验、更新数据库
data = {'id': id, 'name': name}
result = {'status_code': '0', 'status_message' : 'Success', 'data': data}
return result
# 更新 language,PUT 方法,JSON 输入,返回字符串
@app.put('/update-language', response_class=PlainTextResponse)
async def update_language(item: Item):
language = item.language
return "Successfully updated language to %s" % (language)
# 服务网页,GET 方法,返回 HTML
@app.get('/get-webpage', response_class=HTMLResponse)
async def get_webpage(request: Request):
return templates.TemplateResponse("index.html", {"request": request, "message": "Contact Us"})
# 文件响应,GET 方法,返回文件为附件
@app.get('/get-language-file/{language}')
async def get_language_file(language: str):
file_name = "%s.json" % (language)
file_path = "./static/language/" + file_name
return FileResponse(path=file_path, headers={"Content-Disposition": "attachment; filename=" + file_name})
# main
if __name__ == '__main__':
uvicorn.run('myapp:app', host='0.0.0.0', port=8000)
在成功运行 FastAPI 服务器后,你将得到两个用于文档的额外路由。
http://localhost:8000/docs
进入之后你会看到以下界面:
图源:Ng Wai Foong
这是一个交互式的文档,你可以在其中单独测试 API。点击 /alpha 路由时,你应该能看到以下界面:
图源:Ng Wai Foong
在 text 字段输入字符串后,点击“Try it out”按钮。接着再点击“Execute”按钮,会得到以下结果:
图源:Ng Wai Foong
http://localhost:8000/redoc
你会看到以下文档界面。
图源:Ng Wai Foong
总结时间:
本文开篇先是 FastAPI 核心概念的背景介绍,然后是 Flask 和 FastAPI 运行所需模块的安装。安装结束后,我们测试了不同 HTTP 请求方法、输入请求、输出响应下的几种 API,并分别对比了这些功能在 Flask 和 FastAPI 下代码的区别。
本文概括介绍了如何将 Flask 服务器迁移到 FastAPI 服务器的基本过程,并列举了使用实例。
https://medium.com/better-programming/migrate-from-flask-to-fastapi-smoothly-cc4c6c255397](https://medium.com/better-programming/migrate-from-flask-to-fastapi-smoothly-cc4c6c255397
Uvicorn 的 Github 页面:
https://github.com/encode/uvicorn
Uvicorn 文档:
https://www.uvicorn.org/
FastAPI 的 Github 页面:
https://github.com/tiangolo/fastapi
FastAPI 文档:
https://fastapi.tiangolo.com/
官方文档翻译:
https://github.com/apachecn/fastapi-docs-cn