隨著應用程式規模擴大,容器化技術變得日益重要。本文將示範如何使用 Docker 容器化 FastAPI 應用程式,簡化佈署流程並提升可攜性。同時,我們會探討 FastAPI 路由機制,包含使用 APIRouter 管理多個路由,以及利用 Pydantic 模型驗證請求主體,確保 API 的安全性與資料一致性。從簡單的路由範例到複雜的巢狀模型驗證,本文提供逐步教學,幫助讀者掌握 FastAPI 路由和資料驗證的核心概念。
設定 Docker
隨著我們的應用程式成長為具有多個層(如資料函式庫),將應用程式耦合到單個單元使我們能夠佈署我們的應用程式。我們將使用 Docker 將我們的應用程式層容器化為單個映像,然後可以輕鬆地在本地或雲端佈署。
此外,使用 Dockerfile 和 docker-compose 檔案消除了上傳和共用應用程式映像的需求。可以從 Dockerfile 構建新版本的應用程式,並使用 docker-compose 檔案佈署。應用程式映像也可以儲存在 Docker Hub 中並檢索。這被稱為推播和提取操作。
Dockerfile 簡介
Dockerfile 包含有關如何構建應用程式映像的指令。以下是 Dockerfile 的範例:
FROM python:3.8
# 設定工作目錄為 /usr/src/app
WORKDIR /usr/src/app
# 將目前本地目錄的內容複製到容器的工作目錄中
ADD . /usr/src/app
# 執行命令
CMD ["python", "hello.py"]
接下來,我們將構建應用程式容器映像並將其標記為 getting_started,如下所示:
$ docker build -t getting_started .
如果 Dockerfile 不在執行命令的目錄中,則應正確附加 Dockerfile 的路徑,如下所示:
$ docker build -t api api/Dockerfile
可以使用以下命令執行容器映像:
$ docker run getting-started
Docker 是容器化的高效工具。我們只介紹了基本操作,我們將在第 9 章「佈署 FastAPI 應用程式」中更實際地學習更多操作。
內容解密:
FROM python:3.8:指定基礎映像為 Python 3.8 的指令。WORKDIR /usr/src/app:設定容器中的工作目錄。ADD . /usr/src/app:將本地目錄的內容複製到容器的工作目錄中。CMD ["python", "hello.py"]:指定容器啟動時執行的命令。
使用 FastAPI 建構簡單應用程式
在前一章中,我們完成了開發環境的設定。現在,我們將開始使用 FastAPI 建構一個簡單的應用程式。本章將介紹 FastAPI 的基本操作,並在後續章節中探討各項功能。
建立 FastAPI 應用程式
首先,在之前建立的 todos 資料夾中安裝必要的相依性套件。所需的套件如下:
fastapi:用於建構應用程式的框架。uvicorn:一個非同步伺服器閘道介面(ASGI)模組,用於執行應用程式。
首先,透過以下命令啟動開發環境:
$ source venv/bin/activate
接著,安裝所需的相依性套件:
(venv)$ pip install fastapi uvicorn
接下來,建立一個新的 api.py 檔案,並建立一個 FastAPI 例項:
from fastapi import FastAPI
app = FastAPI()
透過在 app 變數中例項化 FastAPI,我們可以開始建立路由。讓我們建立一個歡迎路由。
建立路由
路由是透過定義一個裝飾器來指定操作的型別,接著是一個包含當此路由被呼叫時要執行的操作的函式。以下範例建立了一個只接受 GET 請求的 “/” 路由,並在存取時傳回歡迎訊息:
@app.get("/")
async def welcome() -> dict:
return {"message": "Hello World"}
內容解密:
此段程式碼定義了一個處理 GET 請求的路由 /。當客戶端傳送 GET 請求到此路由時,welcome 函式將被呼叫,並傳回一個包含歡迎訊息的字典。async 關鍵字表示此函式是一個非同步函式,可以處理非同步操作。
啟動應用程式
接下來,使用 uvicorn 啟動我們的應用程式。在終端機中執行以下命令:
(venv)$ uvicorn api:app --port 8000 --reload
在上述命令中,uvicorn 接受以下引數:
file:instance:包含 FastAPI 例項的檔案和儲存 FastAPI 例項的變數名稱。--port PORT:應用程式將在其上執行的埠。--reload:一個可選引數,用於在每次檔案變更時重新啟動應用程式。
命令執行後,將輸出以下結果:
(venv) ➜ todos uvicorn api:app --port 8080 --reload
INFO: Will watch for changes in these directories: ['/Users/youngestdev/Documents/todos']
INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
INFO: Started reloader process [3982] using statreload
INFO: Started server process [3984]
INFO: Waiting for application startup.
INFO: Application startup complete.
測試應用程式
下一步是透過傳送 GET 請求到 API 來測試應用程式。在新的終端機中,使用 curl 傳送 GET 請求,如下所示:
$ curl http://0.0.0.0:8080/
應用程式的回應將記錄在控制檯中,如下所示:
{"message":"Hello World"}
FastAPI 中的路由
路由是建構 Web 應用程式的重要組成部分。FastAPI 中的路由非常靈活且易於使用。路由是指處理從客戶端傳送到伺服器的 HTTP 請求的過程。HTTP 請求被傳送到定義的路由,這些路由具有定義的處理程式來處理請求並回應。這些處理程式稱為路由處理程式。
在本章結束時,您將瞭解如何使用 APIRouter 例項建立路由並連線到主 FastAPI 應用程式。您還將學習到什麼是模型,以及如何使用它們來驗證請求主體。您還將學習路徑引數和查詢引數,以及如何在 FastAPI 應用程式中使用它們。瞭解 FastAPI 中的路由對於建構小型和大型應用程式至關重要。
技術需求
瞭解 FastAPI 中的路由
路由被定義為接受來自 HTTP 請求方法的請求,並可選地接受引數。當請求被傳送到路由時,應用程式會檢查該路由是否已定義,然後在路由處理程式中處理請求。另一方面,路由處理程式是一個處理傳送到伺服器的請求的函式。路由處理程式的一個例子是,當請求透過路由傳送到路由器時,從資料函式庫檢索記錄的函式。
HTTP 請求方法是什麼?
HTTP 方法是用於指示要執行的操作型別的識別符號。標準方法包括 GET、POST、PUT、PATCH 和 DELETE。
您可以透過以下網址瞭解更多關於 HTTP 方法的資訊:https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods。
路由範例
在前一章的專案腳手架部分,我們建立了一個單一路由應用程式。路由由在 app 變數中初始化的 FastAPI() 例項處理:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def welcome() -> dict:
return {"message": "Hello World"}
uvicorn 工具被指向 FastAPI 的例項以服務應用程式:
(venv)$ uvicorn api:app --port 8080 --reload
內容解密:
此段落展示瞭如何使用 FastAPI 和 uvicorn 建立和執行一個簡單的 Web 應用程式。其中,@app.get("/") 裝飾器定義了一個處理 GET 請求的路由,而 welcome 函式則是對此請求的處理程式,傳回一個包含歡迎訊息的字典。
使用 APIRouter 類別進行路由管理
在 FastAPI 中,傳統上會使用 FastAPI() 例項來進行路由操作。然而,這種方法通常適用於只需要單一路徑的應用程式。當需要建立多個執行不同功能的路由時,使用 FastAPI() 例項會導致應用程式無法同時執行多個路由,因為 uvicorn 只能執行一個進入點。
那麼,如何處理需要一系列執行不同功能的路由的複雜應用程式呢?下一節將探討 APIRouter 類別如何協助多重路由管理。
APIRouter 類別的路由管理
APIRouter 類別屬於 FastAPI 套件,用於為多個路由建立路徑操作。它促進了應用程式路由和邏輯的模組化和組織化。
首先,從 fastapi 套件中匯入 APIRouter 類別,並建立一個例項:
from fastapi import APIRouter
router = APIRouter()
然後,從建立的例項中建立和分發路由方法,例如:
@router.get("/hello")
async def say_hello() -> dict:
return {"message": "Hello!"}
接下來,建立一個新的路徑操作,使用 APIRouter 類別來建立和檢索待辦事項。在 todos 資料夾中,建立一個新的檔案 todo.py:
from fastapi import APIRouter
todo_router = APIRouter()
todo_list = []
@todo_router.post("/todo")
async def add_todo(todo: dict) -> dict:
todo_list.append(todo)
return {"message": "Todo added successfully"}
@todo_router.get("/todo")
async def retrieve_todos() -> dict:
return {"todos": todo_list}
在上述程式碼區塊中,建立了兩個路由來處理待辦事項操作。第一個路由透過 POST 方法將待辦事項新增至待辦事項清單,第二個路由透過 GET 方法檢索所有待辦事項。
程式碼解密:
todo_list = []:建立一個空的待辦事項清單,用於暫時儲存待辦事項。@todo_router.post("/todo"):定義一個 POST 路由,用於新增待辦事項。async def add_todo(todo: dict) -> dict::定義一個非同步函式,接收一個字典型別的待辦事項,並傳回一個字典型別的回應。todo_list.append(todo):將待辦事項新增至待辦事項清單。@todo_router.get("/todo"):定義一個 GET 路由,用於檢索所有待辦事項。async def retrieve_todos() -> dict::定義一個非同步函式,傳回一個字典型別的回應,包含所有待辦事項。
將 APIRouter 例項加入至主要 FastAPI 應用程式
要使 todo 路由可見,需要將 todo_router 路徑操作處理器加入至主要 FastAPI 例項中,使用 include_router() 方法。
首先,在 api.py 中匯入 todo_router:
from todo import todo_router
然後,將 todo_router 加入至 FastAPI 應用程式中:
from fastapi import FastAPI
from todo import todo_router
app = FastAPI()
@app.get("/")
async def welcome() -> dict:
return {"message": "Hello World"}
app.include_router(todo_router)
程式碼解密:
app.include_router(todo_router):將todo_router加入至主要FastAPI應用程式,使其路徑操作可見。
啟動應用程式並測試路由
啟動應用程式:
(venv)$ uvicorn api:app --port 8000 --reload
使用 curl 測試應用程式:
(venv)$ curl http://0.0.0.0:8000/
回應應該是:
{"message": "Hello World"}
測試 todo 路由:
(venv)$ curl -X 'GET' 'http://127.0.0.1:8000/todo' -H 'accept: application/json'
回應應該是:
{"todos": []}
新增一個待辦事項:
(venv)$ curl -X 'POST' 'http://127.0.0.1:8000/todo' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"id": 1, "item": "First Todo is to finish this book!"}'
回應應該是:
{"message": "Todo added successfully"}
使用 Pydantic 模型驗證請求主體
在 FastAPI 中,可以使用 Pydantic 模型來驗證請求主體,確保只接收到定義好的資料。這對於保護應用程式免受惡意攻擊至關重要。
Pydantic 是什麼?
Pydantic 是一個 Python 函式庫,使用 Python 型別註解來處理資料驗證。
定義 Pydantic 模型
Pydantic 模型是一個結構化的類別,定義了資料應該如何被接收或解析。模型是透過繼承 Pydantic 的 BaseModel 類別來建立的。
例如:
from pydantic import BaseModel
class PacktBook(BaseModel):
id: int
name: str
publishers: str
isbn: str
程式碼解密:
class PacktBook(BaseModel)::定義一個名為PacktBook的 Pydantic 模型,繼承自BaseModel。id: int、name: str、publishers: str、isbn: str:定義模型的屬性及其型別。
在上述範例中,變數型別被提示為 PacktBook 類別,只能接收四個定義好的欄位。
在我們的待辦事項應用程式中,先前定義了一個路由來新增待辦事項。在路由定義中,將請求主體設定為字典:
async def add_todo(todo: dict) -> dict:
...
這裡可以使用 Pydantic 模型來驗證請求主體。下一節將探討如何使用 Pydantic 模型來驗證請求主體。
使用Pydantic模型驗證請求主體
在前面的範例中,我們建立了一個POST請求,但未驗證請求主體的結構。為了確保請求主體包含特定的欄位,我們可以使用Pydantic模型。
建立Pydantic模型
首先,在model.py檔案中建立一個Pydantic模型:
from pydantic import BaseModel
class Todo(BaseModel):
id: int
item: str
這個模型定義了兩個欄位:id(整數)和item(字串)。
在路由中使用Pydantic模型
接下來,在api.py檔案中匯入Todo模型,並將其用作POST路由的請求主體型別:
from model import Todo
todo_list = []
@todo_router.post("/todo")
async def add_todo(todo: Todo) -> dict:
todo_list.append(todo)
return {"message": "Todo added successfully"}
現在,當客戶端傳送一個POST請求時,FastAPI會驗證請求主體是否符合Todo模型的結構。如果請求主體無效,FastAPI會傳回一個錯誤回應。
內容解密:
- Pydantic模型的建立:我們定義了一個
Todo模型,包含id和item兩個欄位,分別為整數和字串型別。 - 路由中使用Pydantic模型:在
add_todo路由中,我們將todo引數的型別設為Todo,這樣FastAPI就會驗證請求主體是否符合Todo模型的結構。 - 驗證請求主體:當客戶端傳送一個POST請求時,FastAPI會檢查請求主體是否包含
id和item欄位,如果沒有,FastAPI會傳回一個錯誤回應。
測試請求主體驗證
使用curl命令傳送一個空的請求主體:
curl -X 'POST' \
'http://127.0.0.1:8000/todo' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{}'
回應應該包含一個錯誤訊息,指出請求主體缺少必要的欄位。
巢狀模型
Pydantic模型也可以巢狀使用,例如:
class Item(BaseModel):
item: str
status: str
class Todo(BaseModel):
id: int
item: Item
這樣,Todo模型就會包含一個巢狀的Item模型。
內容解密:
- 巢狀模型的定義:我們定義了一個
Item模型,包含item和status兩個欄位,然後在Todo模型中使用了Item模型。 - 巢狀模型的使用:這樣,
Todo模型就會包含一個巢狀的Item模型,可以用來表示更複雜的資料結構。