FastAPI 憑藉其簡潔易用的語法和高效能特性,已成為 Python 技術圈建構 API 的首選框架。除了基本的路由設定和請求處理,FastAPI 也提供豐富的功能,例如自動生成 OpenAPI 檔案、資料驗證、依賴注入等,讓開發者能更專注於業務邏輯的實作。善用 FastAPI 的型別提示和 Pydantic 模型,可以有效提升程式碼可讀性和 maintainability,同時降低錯誤發生的機率。從定義 API 端點到處理各種 HTTP 方法,FastAPI 提供了結構化的方式來組織程式碼,並簡化了複雜的 API 開發流程。

使用者資源

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
    id: int
    username: str
    firstname: str
    lastname: str
    middle_initial: str

# 使用者資料函式庫
valid_users = {}
valid_profiles = {}

# 更新使用者資料
@app.put("/users/{username}")
def update_user(username: str, new_names: dict):
    if username in valid_users:
        profile = valid_profiles[username]
        profile.firstname = new_names['fname']
        profile.lastname = new_names['lname']
        profile.middle_initial = new_names['mi']
        valid_profiles[username] = profile
        return {"message": "successfully updated"}
    else:
        raise HTTPException(status_code=404, detail="user does not exist")

# 刪除使用者發表的討論
@app.delete("/ch01/discussion/posts/remove/{username}")
def delete_discussion(username: str, id: str):
    if username not in valid_users:
        raise HTTPException(status_code=404, detail="user does not exist")
    elif id not in discussion_posts:
        raise HTTPException(status_code=404, detail="post does not exist")
    else:
        del discussion_posts[id]
        return {"message": "main post deleted"}

API 端點

FastAPI 框架提供了強大的 API 端點管理功能,包括自動生成 API 檔案和 Swagger UI。以下是如何設定 API 端點:

from fastapi import FastAPI

app = FastAPI()

# 定義 API 端點
@app.get("/items/")
def read_items():
    return [{"name": "Item Foo"}]

# 定義 API 端點群組
@app.get("/ch01/items/")
def read_ch01_items():
    return [{"name": "Item Bar"}]

FastAPI 的請求體和回應

FastAPI 框架提供了強大的請求體和回應管理功能,包括自動解析 JSON 資料和傳回 JSON 資料。以下是如何定義請求體和回應:

from fastapi import FastAPI, Request
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str

# 定義請求體
@app.post("/items/")
def create_item(item: Item):
    return item

# 定義回應
@app.get("/items/")
def read_items():
    return [{"name": "Item Foo"}]

OpenAPI 檔案

FastAPI 框架提供了自動生成 OpenAPI 檔案的功能,以下是如何存取 OpenAPI 檔案:

http://localhost:8000/docs

這將顯示 Swagger UI,列出所有 API 端點和方法。

管理使用者請求和伺服器回應

FastAPI 框架允許使用者透過路徑引數、查詢引數或標頭傳遞請求資料給伺服器端點 URL,以實作服務交易。根據服務的目標,我們使用這些引數來影響和構建使用者所需的回應。但在討論這些引數之前,讓我們先探索如何在 FastAPI 的本地引數宣告中使用型別提示。

引數型別宣告

所有請求引數都需要在服務方法的方法簽名中宣告型別,適用於 PEP 484 標準的型別提示。FastAPI 支援常見的型別,如 Noneboolintfloat,以及容器型別,如 listtupledictsetfrozensetdeque。此外,FastAPI 還支援 Python 的 typing 模組中包含的資料型別,例如 OptionalListDictSetUnionTupleFrozenSetIterableDeque

路徑引數

FastAPI 允許您透過路徑引數或路徑變數從 API 端點 URL 取得請求資料,使 URL 變得動態。這個引數持有一個值,成為 URL 的一部分。設定路徑引數後,FastAPI 需要這些引數被宣告。

以下是 delete_user() 服務的示例,它是一個 DELETE API 方法,使用 username 路徑引數來搜尋登入記錄以進行刪除:

@app.delete("/ch01/login/remove/{username}")
def delete_user(username: str):
    if username is None:
        return {"message": "invalid user"}
    else:
        del valid_users[username]
        return {"message": "deleted user"}

多個路徑引數

多個路徑引數是可接受的,如果最左邊的變數更有可能被填充值。這意味著最左邊的路徑變數的重要性將使過程更相關和正確。這個標準被應用以確保端點 URL 不會與其他 URL 相似,從而避免衝突和混淆。

以下是 login_with_token() 服務的示例,它遵循這個標準,因為 username 是主鍵,且與其下一個引數 password 一樣強,甚至更強。由於 username 總是需要的,因此可以確保 URL 每次存取時都會是唯一的:

@app.get("/ch01/login/{username}/{password}")
def login_with_token(username: str, password: str, id: UUID):
    if valid_users.get(username) is None:
        return {"message": "user does not exist"}
    else:
        user = valid_users[username]
        if user.id == id and checkpw(password.encode(), user.passphrase):
            return user
        else:
            return {"message": "invalid user"}

圖表翻譯:

  flowchart TD
    A[使用者請求] --> B[路徑引數]
    B --> C[服務方法]
    C --> D[回應]
    D --> E[使用者]

內容解密:

上述程式碼示例展示瞭如何使用路徑引數和型別提示在 FastAPI 中實作使用者請求和伺服器回應的管理。透過使用路徑引數和型別提示,可以構建動態的 URL 和安全的服務方法。

FastAPI 基本設定與路由設計

FastAPI 是一種現代化的 Python Web 框架,旨在快速建構 API 服務。以下是設定 FastAPI 的基本步驟和路由設計的介紹。

安裝 FastAPI

首先,需要安裝 FastAPI 和 uvicorn。可以使用 pip 進行安裝:

pip install fastapi uvicorn

基本設定

以下是 FastAPI 的基本設定範例:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello, World!"}

這個範例建立了一個 FastAPI 服務,當存取根路由 (/) 時,會傳回一個 JSON 物件,包含一條訊息。

路由設計

FastAPI 支援多種路由設計方式,包括靜態路由和動態路由。

靜態路由

靜態路由是指路由路徑不包含任何變數。以下是靜態路由的範例:

from fastapi import FastAPI

app = FastAPI()

@app.get("/ch01/login/details/info")
def login_info():
    return {"message": "username and password are needed"}

這個範例建立了一個靜態路由,當存取 /ch01/login/details/info 時,會傳回一個 JSON 物件,包含一條訊息。

動態路由

動態路由是指路由路徑包含變數。以下是動態路由的範例:

from fastapi import FastAPI

app = FastAPI()

@app.get("/ch01/login/{username}/{password}")
def login_with_token(username: str, password: str):
    # 處理登入邏輯
    return {"message": "login success"}

這個範例建立了一個動態路由,當存取 /ch01/login/{username}/{password} 時,會傳回一個 JSON 物件,包含一條訊息。

查詢引數

查詢引數是指在 URL 中使用 ? 符號傳遞的引數。以下是查詢引數的範例:

from fastapi import FastAPI

app = FastAPI()

@app.get("/ch01/login/")
def login(username: str, password: str):
    # 處理登入邏輯
    return {"message": "login success"}

這個範例建立了一個路由,當存取 /ch01/login/?username={username}&password={password} 時,會傳回一個 JSON 物件,包含一條訊息。

路由優先順序

FastAPI 會按照路由的定義順序來匹配路由。因此,靜態路由應該定義在動態路由之前,以避免路由衝突。

使用 FastAPI 處理使用者請求和伺服器回應

FastAPI 是一個現代化的 Python Web 框架,允許您使用 Python 3.7+ 來建構 API。它提供了強大的功能,包括自動 API 檔案生成、支援非同步和等待、以及高效能。

處理使用者請求

在 FastAPI 中,您可以使用 @app 裝飾器來定義 API 端點。例如,以下程式碼定義了一個 delete_users 端點,接受一個 usernames 引數,該引數是一個字串列表:

from typing import List
from fastapi import FastAPI

app = FastAPI()

@app.delete("/ch01/login/remove/all")
def delete_users(usernames: List[str]):
    for user in usernames:
        del valid_users[user]
    return {"message": "deleted users"}

在這個例子中,usernames 引數是一個字串列表,FastAPI 會自動將請求中的資料轉換為該型別。

處理伺服器回應

FastAPI 也允許您使用 return 陳述式來傳回伺服器回應。例如,以下程式碼定義了一個 update_profile_names 端點,接受一個 username 引數、一個 id 引數和一個 new_names 引數,該引數是一個字典:

from typing import Dict
from fastapi import FastAPI
from uuid import UUID

app = FastAPI()

@app.patch("/ch01/account/profile/update/names/{username}")
def update_profile_names(username: str, id: UUID, new_names: Dict[str, str]):
    if valid_users.get(username) is None:
        return {"message": "user does not exist"}
    elif new_names is None:
        return {"message": "new names are required"}
    else:
        user = valid_users.get(username)
        if user.id == id:
            profile = valid_profiles[username]
            profile.firstname = new_names['fname']
            profile.lastname = new_names['lname']
            profile.middle_initial = new_names['mi']
            valid_profiles[username] = profile
            return {"message": "successfully updated"}
        else:
            return {"message": "user does not exist"}

在這個例子中,update_profile_names 端點傳回了一個字典,包含一個 message 欄位,該欄位的值取決於請求的結果。

使用預設值

FastAPI 也允許您使用 Optional 型別來定義可選引數。例如,以下程式碼定義了一個 login 端點,接受一個 username 引數和一個 password 引數,該引數是可選的:

from typing import Optional
from fastapi import FastAPI

app = FastAPI()

@app.post("/ch01/login")
def login(username: str, password: Optional[str] = None):
    if password is None:
        return {"message": "password is required"}
    else:
        # 處理登入邏輯
        return {"message": "login successfully"}

在這個例子中,password 引數是可選的,如果沒有提供,則預設為 None

FastAPI 入門:設定預設引數

在 FastAPI 中,為了避免驗證錯誤訊息,例如「欄位必填」或「值遺失錯誤」,我們需要為某些 API 服務的查詢引數和路徑引數指定預設值。設定預設值允許 API 方法在提供或未提供引數值的情況下執行。根據需求,分配的預設值通常是數值型別的 0、布林型別的 False、字串型別的空字串、列表型別的空列表([])和字典型別的空字典({})。

以下範例展示瞭如何將預設值應用於查詢引數和路徑引數。首先,我們定義了一個刪除待處理使用者的 API 服務,該服務接受一個名為 accounts 的查詢引數,其預設值為一個空列表:

from fastapi import FastAPI
from typing import List

app = FastAPI()

pending_users = {}

@app.delete("/刪除待處理使用者")
def 刪除待處理使用者(accounts: List[str] = []):
    for user in accounts:
        if user in pending_users:
            del pending_users[user]
    return {"message": "刪除待處理使用者成功"}

接下來,我們定義了一個修改密碼的 API 服務,該服務接受三個引數:usernameold_passwnew_passw,其中 old_passwnew_passw 的預設值均為空字串:

from fastapi import FastAPI
from typing import Optional
import string
import random
from passlib.hash import pbkdf2_sha256 as hashpw
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["pbkdf2_sha256"], default="pbkdf2_sha256")

valid_users = {}

@app.get("/修改密碼")
def 修改密碼(username: str, old_passw: str = '', new_passw: str = ''):
    if valid_users.get(username) is None:
        return {"message": "使用者不存在"}
    
    elif old_passw == '' or new_passw == '':
        characters = string.ascii_lowercase
        temporary_passwd = ''.join(random.choice(characters) for i in range(8))
        user = valid_users.get(username)
        user.password = temporary_passwd
        user.passphrase = hashpw.hash(temporary_passwd)
        return {"message": "密碼已重置"}
    else:
        # 實際修改密碼邏輯
        pass

內容解密:

  1. 刪除待處理使用者:此 API 服務接受一個名為 accounts 的查詢引數,其預設值為一個空列表。如果未提供 accounts 引數,則不會刪除任何使用者。
  2. 修改密碼:此 API 服務接受三個引數:usernameold_passwnew_passw。如果 old_passwnew_passw 未提供(即為空字串),則會生成一個臨時密碼並更新使用者的密碼。

圖表翻譯:

  flowchart TD
    A[開始] --> B[檢查使用者存在]
    B -->|存在| C[檢查舊密碼和新密碼]
    C -->|未提供| D[生成臨時密碼]
    D --> E[更新使用者密碼]
    C -->|已提供| F[修改密碼邏輯]
    E --> G[傳回成功訊息]
    F --> G

此流程圖展示了修改密碼 API 服務的邏輯流程。首先,檢查使用者是否存在,如果存在,則進一步檢查舊密碼和新密碼是否已提供。如果未提供,則生成一個臨時密碼並更新使用者的密碼。如果已提供,則執行修改密碼的邏輯。最終,傳回成功訊息。

使用者請求和伺服器回應管理

在管理使用者請求和伺服器回應時,需要考慮到使用者驗證和授權的安全性。以下是使用 Python 和 FastAPI 框架實作的使用者驗證和授權的範例:

使用者驗證

使用者驗證是指驗證使用者是否具有合法的使用者名稱和密碼。以下是使用者驗證的範例:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from bcrypt import hashpw, gensalt

app = FastAPI()

class User(BaseModel):
    username: str
    password: str

valid_users = {}

@app.post("/login")
def login(user: User):
    if user.username in valid_users:
        if valid_users[user.username].password == hashpw(user.password.encode(), gensalt()):
            return {"message": "login success"}
        else:
            raise HTTPException(status_code=401, detail="invalid password")
    else:
        raise HTTPException(status_code=401, detail="invalid username")

@app.post("/change_password")
def change_password(username: str, old_password: str, new_password: str):
    if username in valid_users:
        if valid_users[username].password == hashpw(old_password.encode(), gensalt()):
            valid_users[username].password = hashpw(new_password.encode(), gensalt())
            return {"message": "password changed"}
        else:
            raise HTTPException(status_code=401, detail="invalid old password")
    else:
        raise HTTPException(status_code=401, detail="invalid username")

選擇性引數

在 FastAPI 中,可以使用 Optional 型別來定義選擇性引數。以下是使用選擇性引數的範例:

from typing import Optional
from fastapi import FastAPI

app = FastAPI()

@app.post("/unlock_username")
def unlock_username(id: Optional[str] = None):
    if id is None:
        return {"message": "token needed"}
    else:
        # 處理 id
        return {"username": "username"}

@app.post("/unlock_password")
def unlock_password(username: Optional[str] = None, id: Optional[str] = None):
    if username is None:
        return {"message": "username is required"}
    else:
        # 處理 username 和 id
        return {"password": "password"}

Mermaid 圖表

以下是使用 Mermaid 圖表來展示使用者驗證和授權的流程:

  flowchart TD
    A[使用者請求] --> B[驗證使用者名稱和密碼]
    B --> C[授權使用者]
    C --> D[傳回授權結果]
    D --> E[使用者登入]

圖表翻譯

此圖表展示了使用者驗證和授權的流程。使用者請求登入,然後驗證使用者名稱和密碼。如果驗證成功,則授權使用者並傳回授權結果。使用者可以登入系統。

從系統架構的視角來看,FastAPI 提供了簡潔而強大的機制來管理使用者請求和伺服器回應。藉由型別提示、路徑引數、查詢引數以及請求體定義,FastAPI 讓開發者能清晰地定義 API 端點,並自動處理資料驗證和轉換。內建的 OpenAPI 檔案生成更簡化了 API 開發和檔案撰寫流程,有效提升開發效率。然而,FastAPI 的路由設計需要仔細規劃,尤其在處理動態路由和靜態路由的優先順序時,需避免潛在的路由衝突。

深入分析 FastAPI 的安全性機制,其使用者驗證和授權功能仍需仰賴 bcrypt 等外部函式函式庫,並由開發者自行實作。雖然 FastAPI 提供了必要的工具和框架,但確保 API 的安全性仍需開發者投入額外心力,例如匯入 OAuth 2.0 等標準授權流程,以及強化輸入驗證以防止常見的攻擊,例如 SQL 注入和跨站指令碼攻擊。此外,針對不同情境下的錯誤處理和異常管理,FastAPI 提供了 HTTPException 等機制,讓開發者能更精細地控制伺服器回應,提升 API 的穩定性和可靠性。

展望未來,FastAPI 與 Python 生態的緊密整合將使其持續受益於 Python 語言和相關函式函式庫的發展。預期 FastAPI 將持續強化非同步處理能力,以更好地應對高併發和 I/O 密集型應用場景。此外,隨著 Serverless 架構的興起,FastAPI 在雲端原生應用開發中的角色將更加重要。

玄貓認為,FastAPI 憑藉其簡潔、高效和易於學習的特性,已成為構建高效能 API 的首選框架之一。對於追求開發效率和效能的團隊,FastAPI 值得深入研究和應用。但開發者仍需關注安全性議題,並持續學習相關最佳實務,才能充分發揮 FastAPI 的潛力。