本文最初發布于 BetterProgramming,經原作者授權由 YouMeLive翻譯并分享。
本文將介紹 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 即可,其余的安裝包都是可選項,本篇教程中也會有所講解。
FastAPI
安裝過程非常簡單,運行 pip install 即可。
Uvicorn
FastAPI 的 ASGI 服務器建議使用 Uvicorn。同樣使用 pip install 安裝。
Jinja2(可選)
任何模板引擎都可以用于 FastAPI 的網頁渲染,簡單起見,這里使用 Flask 里通用的模板引擎 Jinja2。
Aiofiles(可選)
如果想要渲染靜態文件,那么你還需要安裝 aiofiles。
Python-multipart(可選)
默認下,FastAPI 會將輸入請求標準化為 JSON,如果需要接收 form 字段,那么你還需要安裝 python-multipart。
pip install python-multipart
復制代碼
Flask(可選)
本篇教程主要為 Flask 服務器與 FastAPI 在功能性上的對比演示,如果你想一步一步跟著做,那么請安裝 Flask 以供對比。否則,請直接無視本段,直接運行 FastAPI 服務器即可。
下面,讓我們進入正題,開始應用階段。
第二步:對比
這一部分將展示 Flask 服務器與 FastAPI 服務器在相同 API 和功能中的代碼對比。
導入(Flask)
Flask 的所有東西都捆綁在它的安裝包中,所以導入聲明非常直接。random 模塊是用于在后續 API 中生成隨機數的。
from flask import Flask, request, jsonify, render_template, send_from_directoryimport random
復制代碼
導入(FastAPI)
FastAPI 的導入聲明則被歸類到不同的包中,所以看起來很復雜。下面的代碼塊導入了對表單字段輸入的支持、對返回不同類型返回的支持,以及通過模板引擎渲染靜態文件和 HTML 文件的支持。
from fastapi import FastAPI, Form, Requestfrom fastapi.responses import PlainTextResponse, HTMLResponse, FileResponsefrom fastapi.staticfiles import StaticFilesfrom fastapi.templating import Jinja2Templatesfrom pydantic import BaseModelimport randomimport uvicorn
復制代碼
最最基礎的導入應該長這個樣子:
from fastapi import FastAPIfrom pydantic import BaseModelimport random
復制代碼
初始化(Flask)
Flask 可以處理靜態文件及模板引擎,初始化代碼如下:
初始化(FastAPI)
除了標準初始化流程,還需“掛載”靜態文件路徑。同樣,模板引擎渲染也需要聲明一個變量。大部分的初始化代碼都是在用基于 Pydantic 的語法創建數據模型類。
app = FastAPI()# 可選,用于渲染靜態文件app.mount("/static", StaticFiles(directory="static"), name="static")# 可選,用于模板引擎渲染網頁templates = Jinja2Templates(directory="templates")# 視使用情況class Item(BaseModel): language = 'english'
復制代碼
Hello World(Flask)
創建一個可以返回字符串的路由。
@app.route('/')def hello(): return "Hello World!"
復制代碼
Hello World(FastAPI)
FastAPI 版本的“Hello World”如下。因為默認返回類型為 JSON,所以需要修改 response_class 到 PlainTextResponse 來返回字符串。
async 字段會讓異步代碼更簡單,雖然不是必需,但除非你的代碼不支持異步,否則我建議你最好加上。
@app.get("/", response_class=PlainTextResponse)async def hello(): return "Hello World!"
復制代碼
隨機數(Flask)
在 Flask 服務器上返回隨機生成數字 API 的代碼如下。
@app.route('/random-number')def random_number(): return str(random.randrange(100))
復制代碼
隨機數(FastAPI)
FastAPI 的代碼只需簡單修改:
@app.get('/random-number', response_class=PlainTextResponse)async def random_number(): return str(random.randrange(100))
復制代碼
檢查 isAlpha(Flask)
下面我們將測試一個接收名為 text 的查詢參數,并返回 JSON 結果的 API。在 Flask 中,這一步是通過路由裝飾器設置完成的。在這里我們將其設置為僅接受 GET 請求。
@app.route('/alpha', methods=['GET'])def alpha(): text = request.args.get('text', '') result = {'text': text, 'is_alpha' : text.isalpha()} return jsonify(result)
復制代碼
檢查 isAlpha(FastAPI)
首先定義 HTTP 請求方法和裝飾器,這在 FastAPI 中被稱作操作(operation)。GET 操作需要調用 app.get 來完成;而對于多個功能相同的 HTTP 請求方法則需要將其邏輯打包到一個函數中,然后在其各自的操作中獨立調用。
查詢參數需要和類型提示一起指定。text: str 表示一個需要字符串查詢參數 text。也可以通過指定默認值 text = ‘text’,使其成為一個可選參數。
app.get('/alpha')async def alpha(text: str): result = {'text': text, 'is_alpha' : text.isalpha()}return result
復制代碼
創建新 User(Flask)
添加新數據到資料庫,通常需要使用 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)
復制代碼
創建新 User(FastAPI)
POST 請求由 app.post 裝飾器處理。默認情況下的實現是基于 JSON 或查詢參數的,如果要聲明輸入參數,則需指定 Form(…)。
@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(Flask)
目前為止,本文已經介紹了查詢參數以及表單字段的代碼。下面這個例子則將根據 JSON 輸入更新一個名為 language 的變量,這類對現有數據更新的操作,建議使用 PUT 方法。展示的代碼中沒有直接指定 PUT 方法,而是通過條件語句來實現。
@app.route('/update-language', methods=['POST', 'PUT', 'GET', 'DELETE'])def update_language(): language = 'english' if request.method == 'PUT': json_data = http://dz.com/request.get_json() language = json_data['language'] return "Successfully updated language to %s" % (language)
復制代碼
更新 Language(FastAPI)
同理,PUT 操作是由 app.put 裝飾器處理。在初始化的過程中,我們曾定義過以下的 class:
class Item(BaseModel): language = 'english'
復制代碼
然后我們需要將這個 class 作為 item 輸入參數的類型提示。直接使用 variable_name.attribute_name 語法調用即可,解析會在后臺正確完成。在本例中,我們用 item.language。
@app.put('/update-language', response_class=PlainTextResponse)async def update_language(item: Item): language = item.languagereturn "Successfully updated language to %s" % (language)
復制代碼
HTML 網頁(Flask)
在 Flask 中服務網頁相對比較簡單,使用 Jinja2 模板引擎即可完成。我們只需要在 templates 的文件夾中聲明 HTML 文件,如果你需要提供靜態文件,則需要將其放入名為 static 的文件夾中。
下面的代碼示例使用 index.html 渲染網頁,變量可以作為輸入參數傳入:
@app.route('/get-webpage', methods=['GET'])def get_webpage(): return render_template('index.html', message="Contact Us")
復制代碼
HTML 網頁(FastAPI)
在初始化過程中,我們“掛載”了一個 static 文件夾,用于提供靜態文件:
app.mount("/static", StaticFiles(directory="static"), name="static")
復制代碼
基于 Jinja2Templates 創建了一個變量:
templates = Jinja2Templates(directory="templates")
復制代碼
這些都是用于渲染 HTML 模板的。為提供 HTML 網頁,我們需要將 response_class 改為 HTMLResponse。如果你用的不是模板引擎,那么可以直接將結果返回為字符串。
Request 參數需要返回模板以及自定義參數。
@app.get('/get-webpage', response_class=HTMLResponse)async def get_webpage(request: Request): return templates.TemplateResponse("index.html", {"request": request, "message": "Contact Us"})
復制代碼
文件響應(Flask)
若要將文件返回給用戶,最佳的處理方式是通過內置函數 send_from_directory,如果路徑或文件是通過用戶輸入獲得,那么則更應如此。該內置函數接受兩個主要輸入:
另外,你也可以額外聲明其他參數,諸如:as_attachment,通過修改 Content-Disposition 頭來指定其為附件。
路徑參數可以通過
語法來指定。在本例中,我們用
。
@app.route('/get-language-file/', methods=['GET'])def get_language_file(language): return send_from_directory('./static/language', language '.json', as_attachment=True)
復制代碼
文件響應(FastAPI)
FastAPI 根據要求和需要,提供了相當多的響應類型。如果需要返回文件,可以用 FileResponse 或 StreamingResponse。在本文中我們將展示 FileResponse 的使用案例,它接受以下輸入:
代碼展示:
@app.get('/get-language-file/{language}')async def get_language_file(language: str): file_name = "%s.json" % (language) file_path = "./static/language/" file_namereturn FileResponse(path=file_path, headers={"Content-Disposition": "attachment; filename=" file_name})
復制代碼
主函數(Flask)
Flask 中主函數應如下:
if __name__ == '__main__': app.run('0.0.0.0',port=8000)
復制代碼
然后在終端中通過這條命令運行文件:
主函數(FastAPI)
FastAPI 則需要導入 uvicorn。
并且用如下方法指定主函數(myapp 為文件名,app 為 FastAPI 實例所聲明的變量名):
if __name__ == '__main__': uvicorn.run('myapp:app', host='0.0.0.0', port=8000)
復制代碼
然后在終端中正常運行即可:
更好的方法則是不調用主函數,在終端中通過 uvicorn 直接運行。
還可以再額外指定一些參數,諸如:
reload:啟用自動加載功能,修改文件后會刷新服務器。對本地開發非常有用。
port:服務器端口,默認為 8000。
這條代碼可以將端口號改為 5000:
Flask 服務器
以下為 Flask 服務器中的完整 代碼。
# 導入聲明from flask import Flask, request, jsonify, render_template, send_from_directoryimport 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 = http://dz.com/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/', methods=['GET'])def get_language_file(language): return send_from_directory('./static/language', language '.json', as_attachment=True)# mainif __name__ == '__main__': app.run('0.0.0.0',port=8000)
復制代碼
FastAPI 服務器
以下 代碼 是使用 FastAPI 實現的相同功能。
# 導入聲明from fastapi import FastAPI, Form, Requestfrom fastapi.responses import PlainTextResponse, HTMLResponse, FileResponsefrom fastapi.staticfiles import StaticFilesfrom fastapi.templating import Jinja2Templatesfrom pydantic import BaseModelimport randomimport uvicorn# 初始化app = FastAPI()# “掛載”靜態文件夾,用于渲染靜態文件app.mount("/static", StaticFiles(directory="static"), name="static")# Jinja2 模板,用于利用模板引擎返回網頁templates = Jinja2Templates(directory="templates")# Pydantic 數據模型 classclass 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})# mainif __name__ == '__main__': uvicorn.run('myapp:app', host='0.0.0.0', port=8000)
復制代碼
第三步:文檔
在成功運行 FastAPI 服務器后,你將得到兩個用于文檔的額外路由。
交互式文檔(Swagger UI)
第一個路由是交互式文檔 Swagger UI。如果在端口 8000 上運行的服務器,那么就可以通過以下 URL 訪問:
進入之后你會看到以下界面:
圖源:Ng Wai Foong
這是一個交互式的文檔,你可以在其中單獨測試 API。點擊 /alpha 路由時,你應該能看到以下界面:
圖源:Ng Wai Foong
在 text 字段輸入字符串后,點擊“Try it out”按鈕。接著再點擊“Execute”按鈕,會得到以下結果:
圖源:Ng Wai Foong
ReDoc
除此之外,FastAPI 還提供另一種文檔 ReDoc。通過以下 URL 訪問:
你會看到以下文檔界面。
圖源:Ng Wai Foong
第四步:結論
本文開篇先是 FastAPI 核心概念的背景介紹,然后是 Flask 和 FastAPI 運行所需模塊的安裝。安裝結束后,我們測試了不同 HTTP 請求方法、輸入請求、輸出響應下的幾種 API,并分別對比了這些功能在 Flask 和 FastAPI 下代碼的區別。
本文概括介紹了如何將 Flask 服務器遷移到 FastAPI 服務器的基本過程,并列舉了使用實例。
原文鏈接:
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