圍繞 Python 形成的龐大社區正在改進該語言的各個方面。越來越多的開源庫正被發布,以解決從人工智能、機器學習到Web開發等眾多領域的挑戰。除了整個社區提供的強大支持外,Python 軟件基金會還提供了卓越的文檔,使新采用者能夠快速掌握其核心要點。

為什么選擇 Flask?

在 Python 的 Web 開發領域,三大主要框架分別是 Django、Flask 和一個相對較新的參與者 FastAPI。Django 歷史悠久、成熟且廣受歡迎,在 GitHub 上擁有約 66k 顆星、2.2k 名貢獻者、約 350 個版本和超過 25k 個分支。

FastAPI 正在迅速崛起,其在 GitHub 上擁有 48k 顆星、370 名貢獻者以及超過 3.9k 個分支。這個專為高性能和快速編碼 API 設計的優雅框架備受矚目。

盡管 Flask 在受歡迎程度上可能稍遜一籌,但它同樣不容小覷。在 GitHub 上,Flask 擁有近 60k 顆星、約 650 名貢獻者、23 個版本和近 15k 個分支。

盡管 Django 歷史更為悠久且社區規模稍大,但 Flask 具有其獨特優勢。從一開始,Flask 就以可擴展性和簡潔性為核心。與 Django 應用相比,Flask 應用以其輕量級而著稱。Flask 開發人員將其稱為 microframework,這里的 micro(微?。┮馕吨浜诵闹荚诒3趾唵蔚蓴U展。Flask 不會為開發者預設很多決策,例如使用哪種數據庫或選擇哪種模板引擎。此外,Flask 提供了詳盡的文檔,滿足開發者入門所需的一切。FastAPI 遵循與 Flask 類似的“微”理念,盡管它提供了更多工具,如自動生成的 Swagger UI,是 API 開發的絕佳選擇。然而,由于其相對較新,與 Django 和 Flask 等框架相比,與之兼容的資源和庫可能較少。

Flask 以其輕量級、易用性、詳盡的文檔和廣泛的受歡迎程度,成為開發 RESTful API 的優選之一。

引導 Flask 應用程序

首先,我們需要在開發計算機上安裝一些依賴項。這包括 Python 3、Pip(Python 包索引)以及 Flask。

安裝 Python 3

如果你使用的是最新的流行 Linux 發行版(如 Ubuntu)或 macOS,那么你的計算機可能已經預裝了 Python 3。而如果你在使用 Windows,可能需要手動安裝 Python 3,因為該操作系統默認不附帶 Python。

在機器上安裝 Python 3 后,你可以通過運行以下命令來檢查一切是否設置正確:

python --version
# Python 3.8.9

請注意,當系統中存在不同版本的 Python 時,上述命令可能會產生不同的輸出。重要的是,您應確保運行的是 Python 3 或更新版本。如果我們輸入 python --version 得到的是 “Python 2”,那么我們可以嘗試使用 python3 --version。如果此命令產生了正確的 Python 3 版本輸出,那么我們必須在整篇文章中替換所有命令,使用 python3 而不是 python。

安裝 Pip

Pip 是安裝 Python 包的推薦工具。雖然官方安裝頁面指出,在 Ubuntu 上安裝 Python 時,如果使用的是 Python 2.7.9 或更高版本,或 Python 3.4 或更高版本,通常不會默認安裝 Pip。因此,我們需要檢查一下是否需要單獨安裝 Pip,或者是否已經擁有它。

# we might need to change pip by pip3
pip --version
# pip 9.0.1 ... (python 3.X)

如果上述指令能生成類似的輸出,那么我們就可以著手進行下一步了。如果結果不符,我們可以嘗試用 pip3 替換 pip。如果在計算機上未能找到與 Python 3 對應的 Pip,我們可以遵循相關說明來安裝 Pip。

安裝 Flask

既然我們已對 Flask 有所了解,接下來讓我們專注于在機器上安裝它,并進行測試,以確保能夠運行基本的 Flask 應用程序。安裝 Flask 的第一步是使用 Pip執行以下命令:

# we might need to replace pip with pip3
pip install Flask

安裝包后,我們將創建一個名為 file 并向其添加五行代碼。由于我們將使用此文件來檢查 Flask 是否已正確安裝,因此我們不需要將其嵌套在新目錄中。

# hello.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
return "Hello, World!"

這 5 行代碼是我們處理 HTTP 請求并返回 “Hello, World!” 。要運行它,我們執行以下命令:

flask --app hello run

* Serving Flask app 'hello'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit

在 Ubuntu 上,若要讓 Flask 命令直接在終端中運行,我們可能需要調整環境變量。為此,請首先執行以下命令:

touch ~/.bash_aliasesecho "export PATH=\$PATH:~/.local/bin" >> ~/.bash_aliases

執行這些命令后,為了使更改生效,你可以重新加載 bash 配置或者開啟一個新的終端窗口。

接著,你可以啟動你的 Flask 應用程序。一旦應用程序運行,你可以通過打開瀏覽器并導航到 http://127.0.0.1:5000/ 來訪問它,或者通過發出 curl http://127.0.0.1:5000/ 命令來測試其響應。

Hello world with Flask

虛擬環境 (virtualenv)

盡管 PyPA(Python Packaging Authority 組)建議作為安裝 Python 包的工具,但我們需要使用另一個包來管理項目的依賴項。確實,pip支持通過requirements.txt文件來管理依賴項,但這種方式在嚴肅項目中,特別是在需要在不同生產和開發機器上運行的項目中,存在一些不足。其中,最突出的問題是:

為了解決這些問題,我們將使用Pipenv。Pipenv是一個依賴項管理器,它能夠將項目隔離在私有的虛擬環境中,從而允許我們按項目安裝軟件包。如果您熟悉NPM(Node Package Manager)或Ruby的打包工具,那么Pipenv在本質上與這些工具是相似的。

pip install pipenv

現在,為了開始構建一個關鍵的 Flask 應用程序,我們首先需要創建一個新目錄來存放源代碼。在本文中,我們將打造一個名為 Cashman 的小型 RESTful API,它將為用戶提供管理收入和支出的功能。因此,我們將創建一個目錄。隨后,我們將使用 pipenv 來啟動項目并管理其依賴項。具體來說,目錄名可以定為 cashman-flask-project,并使用以下命令來初始化 pipenv 環境:

# create our project directory and move to it
mkdir cashman-flask-project && cd cashman-flask-project

# use pipenv to create a Python 3 (--three) virtualenv for our project
pipenv --three

# install flask a dependency on our project
pipenv install flask

第二個命令創建我們的虛擬環境,我們所有的依賴項都安裝在其中,第三個命令將添加 Flask 作為我們的第一個依賴項。如果我們檢查項目的目錄,我們將看到兩個新文件:

  1. Pipfile包含有關我們項目的詳細信息,例如 Python 版本和所需的包。
  2. Pipenv.lock包含項目所依賴的每個包的版本及其傳遞依賴項。

Python 模塊

Python 模塊是組織源代碼的一種方式,與其他主流編程語言中的類似概念(如 Java 的包和 C# 的命名空間)相呼應。在 Python 中,模塊是包含 Python 代碼的文件,這些文件可以被其他 Python 腳本導入。為了將一組相關的模塊組織在一起,可以創建一個文件夾,并在其中添加一個名為 __init__.py 的文件,這樣該文件夾就被視為一個 Python 包。

讓我們在應用程序上創建第一個模塊,即主模塊,其中包含我們所有的 RESTful 端點。在應用程序的目錄中,我們將創建一個新的文件夾。這個新文件夾將命名為我們項目的模塊名,例如?cashmancashman-flask-project。然后,在這個新文件夾中,我們將添加?__init__.py?文件以及其他必要的 Python 腳本,以構成我們的主模塊。

# create source code's root
mkdir cashman && cd cashman

# create an empty __init__.py file
touch __init__.py

在主模塊中,讓我們創建一個名為 .index.py的腳本,我們將定義應用程序的第一個端點。

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello_world():
return "Hello, World!"

與前面的示例一樣,我們的應用程序返回 “Hello, world!” 消息。我們稍后會開始改進它,但首先,讓我們創建一個在應用程序的根目錄中調用的可執行文件bootstrap.sh。

# move to the root directory
cd ..

# create the file
touch bootstrap.sh

# make it executable
chmod +x bootstrap.sh

此文件的目標是促進應用程序的啟動。其源代碼如下:

#!/bin/sh
export FLASK_APP=./cashman/index.py
pipenv run flask --debug run -h 0.0.0.0

第一個命令用于指定 Flask 要執行的主腳本。第二個命令則在虛擬環境的上下文中運行我們的 Flask 應用程序,并使其監聽計算機上的所有接口(通過?-h 0.0.0.0?參數實現)。

注意:為了提升開發體驗并啟用熱重載功能,我們已將 Flask 設置為在調試模式下運行。這意味著在每次更改代碼后,無需手動重啟服務器。但請注意,如果您在生產環境中運行 Flask,應更新這些設置以適應生產環境的需求。

為了驗證此腳本是否正常運行,我們可以執行 ./bootstrap.sh 腳本,預期會得到與運行“Hello, world!”應用程序時相似的結果。

 * Serving Flask app './cashman/index.py'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.1.207:5000
Press CTRL+C to quit

使用 Flask 創建 RESTful 終端節點

現在我們的應用程序已經結構化,我們可以開始編寫一些相關的端點。如前所述,我們應用程序的目標是幫助用戶管理收入和支出。我們將首先定義兩個終端節點來處理收入。讓我們將./cashman/index.py文件的內容替換為以下內容:

from flask import Flask, jsonify, request

app = Flask(__name__)

incomes = [
{ 'description': 'salary', 'amount': 5000 }
]

@app.route('/incomes')
def get_incomes():
return jsonify(incomes)

@app.route('/incomes', methods=['POST'])
def add_income():
incomes.append(request.get_json())
return '', 204

自從我們對應用程序進行改進以來,已經移除了原先向用戶返回“Hello, world!”的終端節點。取而代之的是,我們定義了一個專門處理返回收入HTTP請求的終端節點,以及另一個用于處理添加新收入HTTP請求的終端節點。這些終端節點均附有注釋,清晰地說明了它們各自監聽的路由。Flask 提供了詳盡的文檔,很好地闡述了這些功能的具體實現。具體來說,我們使用了 @app.route 裝飾器來定義 /incomes 的 GET 和 POST 方法。

目前,我們暫將收入以字典的形式進行處理,但很快就會創建專門的類來表示收入和支出。

要與我們創建的兩個端點進行交互,我們可以啟動我們的應用程序并發出一些 HTTP 請求:

# start the cashman application
./bootstrap.sh &

# get incomes
curl http://localhost:5000/incomes

# add new income
curl -X POST -H "Content-Type: application/json" -d '{
"description": "lottery",
"amount": 1000.0
}' http://localhost:5000/incomes

# check if lottery was added
curl localhost:5000/incomes
Interacting with Flask endpoints

使用 Python 類映射模型

在像上面這樣的簡單用例中使用字典就足夠了。但是,對于處理不同實體并具有多個業務規則和驗證的更復雜的應用程序,我們可能需要將數據封裝到 Python 類中。

我們將重構我們的應用程序,以學習將實體(如 incomes)映射為類的過程。我們要做的第一件事是創建一個子模塊來保存我們所有的實體。讓我們在模塊中創建一個目錄,并添加一個名為它的空文件。

# create model directory inside the cashman module
mkdir -p cashman/model

# initialize it as a module
touch cashman/model/__init__.py

映射 Python超類

我們會在這個新的模塊/目錄中創建三個類:其中兩個類的名稱暫時省略,而第一個類將作為這兩個類的基礎類,我們將其命名為Transaction。接下來,我們將在model目錄中創建一個名為transaction.py的文件,并編寫以下代碼:

import datetime as dt

from marshmallow import Schema, fields

class Transaction(object):
def __init__(self, description, amount, type):
self.description = description
self.amount = amount
self.created_at = dt.datetime.now()
self.type = type

def __repr__(self):
return '<Transaction(name={self.description!r})>'.format(self=self)

class TransactionSchema(Schema):
description = fields.Str()
amount = fields.Number()
created_at = fields.Date()
type = fields.Str()

除了類定義之外,我們還定義了一個TransactionSchema。我們將使用這個TransactionSchema來從JSON對象反序列化以及將其實例序列化為JSON對象。這個類繼承自另一個尚未安裝的包中的超類,名為TransactionSchema的基類。

# installing marshmallow as a project dependency
pipenv install marshmallow

Marshmallow 是一個廣受歡迎的 Python 包,它能夠實現復雜數據類型(例如對象)與內置 Python 數據類型之間的轉換。利用這個包,我們可以對數據進行驗證、序列化和反序列化操作。雖然本文不會深入探討驗證功能(這將是另一篇文章的主題),但如前所述,我們將利用 Marshmallow 通過 API 端點對實體進行序列化和反序列化。

將 Income 和 Expense 映射為 Python 類

為了保持代碼的條理性和實用性,我們不會在 API 端點上直接公開 Transaction 類。相反,我們會創建兩個專門用于處理請求的類:一個處理收入(Income),另一個處理支出(Expense)。接下來,我們將在名為 income.py 的模塊內部使用以下代碼來定義一個名為 Transaction 的基類以及 Income 和 Expense 兩個子類。

from marshmallow import post_load

from .transaction import Transaction, TransactionSchema
from .transaction_type import TransactionType

class Income(Transaction):
def __init__(self, description, amount):
super(Income, self).__init__(description, amount, TransactionType.INCOME)

def __repr__(self):
return '<Income(name={self.description!r})>'.format(self=self)

class IncomeSchema(TransactionSchema):
@post_load
def make_income(self, data, **kwargs):
return Income(**data)

這個類目前為我們的應用程序帶來的唯一價值在于它對事務類型進行了硬編碼。事務類型將采用 Python 枚舉器來表示,盡管我們尚需創建這個枚舉器,但它將在未來幫助我們過濾交易。接下來,我們將在名為 transaction_type.py 的文件中定義這個枚舉器。

from enum import Enum

class TransactionType(Enum):
INCOME = "INCOME"
EXPENSE = "EXPENSE"

枚舉器的代碼相當簡潔,只需定義一個名為?TransactionTypeEnum?的類,該類將繼承自 Python 的?Enum?類,并包含兩種類型:INCOME?和?EXPENSE。

最后,為了表示支出,我們將創建一個新的類。為此,我們將在名為 expense.py 的文件中添加相應的代碼。

from marshmallow import post_load

from .transaction import Transaction, TransactionSchema
from .transaction_type import TransactionType

class Expense(Transaction):
def __init__(self, description, amount):
super(Expense, self).__init__(description, -abs(amount), TransactionType.EXPENSE)

def __repr__(self):
return '<Expense(name={self.description!r})>'.format(self=self)

class ExpenseSchema(TransactionSchema):
@post_load
def make_expense(self, data, **kwargs):
return Expense(**data)

與之前提到的類不同,現在的Income類會將給定金額(amount)轉換為負數。這樣做的目的是無論用戶發送的是正值還是負值,我們都能夠統一將其存儲為負值,從而簡化后續的計算過程。而Expense類在處理金額時可能不會有這樣的轉換,或者會有不同的處理方式。

使用 Marshmallow 序列化和反序列化對象

隨著超類及其特化的充分實現,我們現在可以增強我們的端點來處理這些類。讓我們將內容替換為:

from flask import Flask, jsonify, request

from cashman.model.expense import Expense, ExpenseSchema
from cashman.model.income import Income, IncomeSchema
from cashman.model.transaction_type import TransactionType

app = Flask(__name__)

transactions = [
Income('Salary', 5000),
Income('Dividends', 200),
Expense('pizza', 50),
Expense('Rock Concert', 100)
]

@app.route('/incomes')
def get_incomes():
schema = IncomeSchema(many=True)
incomes = schema.dump(
filter(lambda t: t.type == TransactionType.INCOME, transactions)
)
return jsonify(incomes)

@app.route('/incomes', methods=['POST'])
def add_income():
income = IncomeSchema().load(request.get_json())
transactions.append(income)
return "", 204

@app.route('/expenses')
def get_expenses():
schema = ExpenseSchema(many=True)
expenses = schema.dump(
filter(lambda t: t.type == TransactionType.EXPENSE, transactions)
)
return jsonify(expenses)

@app.route('/expenses', methods=['POST'])
def add_expense():
expense = ExpenseSchema().load(request.get_json())
transactions.append(expense)
return "", 204

if __name__ == "__main__":
app.run()

對于用于檢索收入的終端節點,我們定義了一個IncomeSchema實例來生成收入的JSON表示。同時,我們使用過濾功能(filter)從transactions列表中僅提取出收入類型的交易。最后,我們將這個JSON格式的收入數組發送回給用戶。

負責接受新收入的端點也經過了重構。此終端節點的關鍵更改是添加了基于用戶發送的JSON數據來加載Income實例的功能。由于transactions列表包含Transaction及其子類(如IncomeExpense)的實例,因此我們在列表中添加了新的Income實例。

負責處理費用的另外兩個端點(get_expensesadd_expense)幾乎是處理收入端點(get_incomesadd_income)的副本。區別在于:

這樣就完成了我們 API 的實現。如果我們現在運行 Flask 應用程序,我們將能夠與端點進行交互,如下所示:

# start the application
./bootstrap.sh

# get expenses
curl http://localhost:5000/expenses

# add a new expense
curl -X POST -H "Content-Type: application/json" -d '{
"amount": 20,
"description": "lottery ticket"
}' http://localhost:5000/expenses

# get incomes
curl http://localhost:5000/incomes

# add a new income
curl -X POST -H "Content-Type: application/json" -d '{
"amount": 300.0,
"description": "loan payment"
}' http://localhost:5000/incomes

Docker 化 Flask 應用程序

由于我們打算最終將 API 發布到云端,因此需要創建一個文件來描述在 Docker 容器上運行應用程序所需的內容。為了測試和運行項目的 Docker 化實例,我們需要在開發計算機上安裝 Docker。定義一個 Dockerfile 將有助于我們在不同的環境中運行 API。也就是說,在將來,我們還將在生產、暫存等環境中安裝 Docker 并運行我們的程序。

讓我們使用以下代碼在項目的根目錄中創建 Dockerfile

# Using lightweight alpine image
FROM python:3.8-alpine

# Installing packages
RUN apk update
RUN pip install --no-cache-dir pipenv

# Defining working directory and adding source code
WORKDIR /usr/src/app
COPY Pipfile Pipfile.lock bootstrap.sh ./
COPY cashman ./cashman

# Install API dependencies
RUN pipenv install --system --deploy

# Start app
EXPOSE 5000
ENTRYPOINT ["/usr/src/app/bootstrap.sh"]

配方中的第一項是定義一個基于默認的 Python 3 Docker 映像來創建 Docker 容器。隨后,我們會更新 APK(注意:這里可能是個筆誤,通常與 Docker 容器相關的是 apt-get 更新,用于安裝或更新軟件包,而非 APK,APK 是 Android 應用包的格式)并安裝必要的軟件。接著,我們使用 pipenv(一個 Python 包管理工具,用于管理依賴關系和虛擬環境)來定義將在 Docker 映像中使用的工作目錄,并復制啟動和運行應用程序所需的代碼到該目錄。

注意:對于我們的 ,我們使用 Python 版本 3.8,但是,根據您的系統配置,可能在文件中為 Python 設置了不同的版本。請確保兩者中的 Python 版本一致,否則 docker 容器將無法啟動服務器。

要基于我們創建的容器創建和運行 Docker 容器,我們可以執行以下命令:

# build the image
docker build -t cashman .

# run a new docker container named cashman
docker run --name cashman \
-d -p 5000:5000 \
cashman

# fetch incomes from the dockerized instance
curl http://localhost:5000/incomes/

這很簡單但非常有效,使用起來也同樣方便。憑借這些命令和Dockerfile,我們可以輕松地運行所需數量的API實例,而不會出現任何問題。只需在主機上(甚至是在另一臺主機上)定義另一個端口即可。

使用 Auth0 保護 Python API

使用 Auth0 保護 Python API 非常簡單,并且帶來了許多很棒的功能。使用 Auth0,我們只需編寫幾行代碼即可獲得:

例如,要保護使用 Flask 編寫的 Python API,我們可以簡單地創建一個裝飾器requires_auth

# Format error response and append status code

def get_token_auth_header():
"""Obtains the access token from the Authorization Header
"""
auth = request.headers.get("Authorization", None)
if not auth:
raise AuthError({"code": "authorization_header_missing",
"description":
"Authorization header is expected"}, 401)

parts = auth.split()

if parts[0].lower() != "bearer":
raise AuthError({"code": "invalid_header",
"description":
"Authorization header must start with"
" Bearer"}, 401)
elif len(parts) == 1:
raise AuthError({"code": "invalid_header",
"description": "Token not found"}, 401)
elif len(parts) > 2:
raise AuthError({"code": "invalid_header",
"description":
"Authorization header must be"
" Bearer token"}, 401)

token = parts[1]
return token

def requires_auth(f):
"""Determines if the access token is valid
"""
@wraps(f)
def decorated(*args, **kwargs):
token = get_token_auth_header()
jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json")
jwks = json.loads(jsonurl.read())
unverified_header = jwt.get_unverified_header(token)
rsa_key = {}
for key in jwks["keys"]:
if key["kid"] == unverified_header["kid"]:
rsa_key = {
"kty": key["kty"],
"kid": key["kid"],
"use": key["use"],
"n": key["n"],
"e": key["e"]
}
if rsa_key:
try:
payload = jwt.decode(
token,
rsa_key,
algorithms=ALGORITHMS,
audience=API_AUDIENCE,
issuer="https://"+AUTH0_DOMAIN+"/"
)
except jwt.ExpiredSignatureError:
raise AuthError({"code": "token_expired",
"description": "token is expired"}, 401)
except jwt.JWTClaimsError:
raise AuthError({"code": "invalid_claims",
"description":
"incorrect claims,"
"please check the audience and issuer"}, 401)
except Exception:
raise AuthError({"code": "invalid_header",
"description":
"Unable to parse authentication"
" token."}, 400)

_app_ctx_stack.top.current_user = payload
return f(*args, **kwargs)
raise AuthError({"code": "invalid_header",
"description": "Unable to find appropriate key"}, 400)
return decorated

然后在我們的端點中使用它:

# Controllers API

# This doesn't need authentication
@app.route("/ping")
@cross_origin(headers=['Content-Type', 'Authorization'])
def ping():
return "All good. You don't need to be authenticated to call this"

# This does need authentication
@app.route("/secured/ping")
@cross_origin(headers=['Content-Type', 'Authorization'])
@requires_auth
def secured_ping():
return "All good. You only get this message if you're authenticated"

要了解有關使用 Auth0 保護 Python API 的更多信息,請查看本教程。除了后端技術(如 Python、Java 和 PHP)的教程外,Auth0 Docs 網頁還提供移動/本機應用程序和單頁應用程序的教程。

后續步驟

在本文中,我們已了解了開發結構清晰的 Flask 應用程序所需的核心組件。我們探討了如何使用?pipenv?來管理 API 的依賴項。隨后,我們安裝并運用了 Flask 和 Marshmallow,以創建能夠接收和發送 JSON 響應的終端節點。最后,我們還研究了如何將 API Docker 化,以便將應用程序部署到云端。

盡管我們的 API 結構清晰,但其功能性仍有待提升。在后續的文章中,我們將介紹以下可改進之處:

敬請持續關注!

原文鏈接:https://auth0.com/blog/developing-restful-apis-with-python-and-flask/

上一篇:

使用 Auth0 向 Sinatra API 添加授權

下一篇:

如何使用 TypeScript 編寫 API
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費