FastAPI 連接 NoSQL 資料庫 (MongoDB)
MongoDB 是一種非常流行的 NoSQL 資料庫,它的文件結構 (BSON) 與 JSON 非常貼合,因此在 Web 開發中非常受歡迎。
為了發揮 FastAPI 的非同步效能,我們推薦使用 Motor,這是 MongoDB 官方提供的 Python 非同步驅動程式。
安裝 Motor
pip install motor
資料庫連線
通常我們會將資料庫連線放在 lifespan 或 startup event 中。
from fastapi import FastAPI
from motor.motor_asyncio import AsyncIOMotorClient
app = FastAPI()
@app.on_event("startup")
async def startup_db_client():
app.mongodb_client = AsyncIOMotorClient("mongodb://localhost:27017")
app.mongodb = app.mongodb_client["my_database"]
@app.on_event("shutdown")
async def shutdown_db_client():
app.mongodb_client.close()
定義 Pydantic Model with ObjectId
MongoDB 使用 _id (ObjectId) 作為主鍵,但 ObjectId 不是標準 JSON 格式,這可能會在 Pydantic 驗證或 JSON 序列化時出問題。
我們需要稍微客製化一下 Pydantic Model:
from typing import Optional, List
from pydantic import BaseModel, Field, BeforeValidator
from typing_extensions import Annotated
# 定義一個可以處理 ObjectId 的型別
PyObjectId = Annotated[str, BeforeValidator(str)]
class StudentModel(BaseModel):
# 將 MongoDB 的 _id 對應到 id 欄位
id: PyObjectId | None = Field(alias="_id", default=None)
name: str = Field(...)
email: str = Field(...)
course: str = Field(...)
gpa: float = Field(..., le=4.0)
model_config = {
"populate_by_name": True,
"arbitrary_types_allowed": True,
}
實作 CRUD
from fastapi import Body, HTTPException, status
from fastapi.encoders import jsonable_encoder
@app.post("/students/", response_description="Add new student", response_model=StudentModel)
async def create_student(student: StudentModel = Body(...)):
student = jsonable_encoder(student)
new_student = await app.mongodb["students"].insert_one(student)
created_student = await app.mongodb["students"].find_one({"_id": new_student.inserted_id})
return created_student
@app.get("/students/", response_description="List all students", response_model=List[StudentModel])
async def list_students(limit: int = 1000):
students = await app.mongodb["students"].find().to_list(limit)
return students
ODM 工具 (Beanie)
雖然直接用 Motor 很靈活,但寫起來比較繁瑣。如果你喜歡類似 ORM 的開發體驗,推薦使用 Beanie。
Beanie 是一個基於 Motor 和 Pydantic 的 ODM (Object Document Mapper)。
使用 Beanie 初始化
from beanie import init_beanie, Document
class Student(Document):
name: str
gpa: float
@app.on_event("startup")
async def startup_event():
client = AsyncIOMotorClient("mongodb://localhost:27017")
await init_beanie(database=client.db_name, document_models=[Student])
使用 Beanie 操作資料
# 新增
student = Student(name="John", gpa=3.8)
await student.insert()
# 查詢
students = await Student.find(Student.gpa > 3.0).to_list()
總結
- 使用 Motor 進行非同步 MongoDB 操作。
- 處理 ObjectId 需要一些額外的 Pydantic 設定。
- 推薦使用 Beanie ODM,它能讓 MongoDB 的操作像操作 Python 物件一樣簡單,完美整合 Pydantic。