python
from pydantic import BaseModel, Field
from pydantic_core import core_schema
from pydantic.json_schema import JsonSchemaValue
class PyObjectId(ObjectId):
"""Custom type to bridge MongoDB ObjectId and Pydantic v2 validation."""
@classmethod
def __get_pydantic_core_schema__(cls, source_type, handler):
# Defines how Pydantic should validate this type
return core_schema.no_info_plain_validator_function(cls.validate)
@classmethod
def validate(cls, v):
if isinstance(v, ObjectId): return v
if isinstance(v, str) and ObjectId.is_valid(v): return ObjectId(v)
raise ValueError("Invalid ObjectId")
@classmethod
def __get_pydantic_json_schema__(cls, _core_schema, handler) -> JsonSchemaValue:
# Ensures the OpenAPI/JSON schema reflects this as a string
return {"type": "string"}
class Task(BaseModel):
"""Pydantic model representing the Task document schema."""
# Maps MongoDB's internal '_id' to a developer-friendly 'id' field
id: PyObjectId | None = Field(None, alias='_id')
title: str
description: str | None = None
completed: bool = False
model_config = ConfigDict(
arbitrary_types_allowed=True
)ขับเคลื่อนโดย
@app.post('/add-task')
async def add_task(req: Request):
"""Create: Inserts a task and returns the refreshed list."""
try:
form_data = await req.form()
task = Task(
title=form_data.get('title', ''),
description=form_data.get('description') or None
)
# model_dump(by_alias=True) ensures 'id' is converted back to '_id' for Mongo
task_dict = task.model_dump(exclude_none=True, by_alias=True)
# Remove _id if it's None so MongoDB can generate its own unique ID
if '_id' in task_dict and task_dict['_id'] is None:
del task_dict['_id']
await collection.insert_one(task_dict)
return await TaskList()
except Exception as e:
return Div(P(f'Error: {str(e)}', cls='text-red-500 p-4'), id='task-list')ขับเคลื่อนโดย
หากรันโค้ดด้านล่างในเทอร์มินัล:
curl -X POST http://localhost:8000/add-task -d "title=Complete FastHTML MongoDB integration" -d "description=Verify that tasks can be created, updated, and deleted successfully" -H "Content-Type: application/x-www-form-urlencoded"ขับเคลื่อนโดย
จะเห็นว่างานใหม่นี้ถูกเพิ่มแล้ว:
ภาพ: สกรีนช็อตหลังเพิ่มงาน
Update: สลับสถานะเสร็จสิ้น
สำหรับการอัปเดต เราใช้คำขอ PATCH ซึ่งสาธิต "micro-refresh" ที่อัปเดตเฉพาะแถวของงานในฐานข้อมูลและเรนเดอร์ใหม่ใน UI โดยอ้างอิงตาม ID เฉพาะ
@app.patch('/toggle-task/{task_id}')
async def toggle_task(task_id: str):
"""Update: Toggles completion status and returns the single row fragment."""
task_doc = await collection.find_one({"_id": ObjectId(task_id)})
if not task_doc: raise HTTPException(404, "Task not found")
await collection.update_one(
{"_id": ObjectId(task_id)},
{"$set": {"completed": not task_doc['completed']}}
)
# Return only the updated TaskItem for a surgical DOM update
updated_doc = await collection.find_one({"_id": ObjectId(task_id)})
return TaskItem(Task(**updated_doc))ขับเคลื่อนโดย
@app.delete('/delete-task/{task_id}')
async def delete_task(task_id: str):
"""Delete: Removes a task from MongoDB."""
await collection.delete_one({"_id": ObjectId(task_id)})
# Signal HTMX to remove the element
return Empty()ขับเคลื่อนโดย
FastHTML และ MongoDB มอบแนวทางการพัฒนาเว็บสมัยใหม่ที่รวดเร็วและเนทีฟสำหรับ Python ในบทความนี้ เราจะสร้างแอปตัวจัดการงานแบบ reactive และเรียลไทม์ โดยสาธิตวงจร CRUD (Create, Read, Update, Delete) แบบครบถ้วนภายในไฟล์ Python เดียวที่ดูแลง่าย หากต้องการทดลองใช้ MongoDB กับ Python แบบลงมือทำ ขอแนะนำคอร์ส Introduction to MongoDB in Python।
FastHTML คืออะไร?
FastHTML คือเฟรมเวิร์กมินิมัล ประสิทธิภาพสูง ที่สร้างบนรากฐานของ FastAPI มันนำเสนอกระบวนทัศน์ HTML แบบ Pythonic ทำให้นักพัฒนาสามารถสร้างฟรอนต์เอนด์ทั้งหมดด้วยฟังก์ชัน Python ที่นำกลับมาใช้ซ้ำได้ แทนการใช้เทมเพลตแบบเดิม
จุดแข็งหลักอยู่ที่การผสานรวมกับ HTMX แบบเนทีฟ โดยใช้แอตทริบิวต์ HTML ที่เรียบง่ายเพื่อขับเคลื่อนการอัปเดตฝั่งเซิร์ฟเวอร์ HTMX ช่วยให้สร้างประสบการณ์แบบ single-page application แบบไดนามิกได้ โดยไม่ต้องพึ่งสแต็กที่หนักด้วย JavaScript
MongoDB คืออะไร?
MongoDB เป็นฐานข้อมูล NoSQL ที่อิงเอกสารแบบ general-purpose ชั้นนำ สคีมาที่ยืดหยุ่นและการใช้เอกสาร BSON ที่คล้าย JSON ทำให้เหมาะกับการพัฒนาสมัยใหม่ที่วนซ้ำได้รวดเร็ว
สำหรับ Python เราใช้ไดรเวอร์แบบอะซิงโครนัสอย่างเป็นทางการชื่อ Motor ซึ่งให้ส่วนติดต่อแบบ non-blocking ที่เหมาะอย่างยิ่งกับสถาปัตยกรรมที่เน้นประสิทธิภาพของ FastAPI และ FastHTML
ทำไมสแต็กนี้จึงโดดเด่น
การผสานเทคโนโลยีเหล่านี้สร้างสภาพแวดล้อมการพัฒนาที่มีประสิทธิผลอย่างยิ่ง:
การตั้งค่าและการเชื่อมต่อ
มีข้อกำหนดเบื้องต้นบางอย่างที่ต้องมีเพื่อทำตามบทเรียนนี้:
การเริ่มต้นโปรเจกต์
เพื่อแสดงประสิทธิภาพของสแต็กนี้ เราจะทำให้ทั้งแอปเสร็จภายในไฟล์เดียว app.py รวมถึงการตั้งค่าฐานข้อมูล โมเดลข้อมูล และเส้นทางแบบ reactive
การติดตั้ง
เราต้องใช้เฟรมเวิร์ก FastHTML, Motor (ไดรเวอร์ MongoDB แบบอะซิงก์อย่างเป็นทางการ) และ Uvicorn สำหรับ ASGI server
การตั้งค่าการเชื่อมต่อ MongoDB
เราใช้
AsyncIOMotorClientเพื่อสร้างการเชื่อมต่อแบบ non-blocking ทำให้ในขณะที่แอปกำลังรอ I/O ของฐานข้อมูล ก็ยังประมวลผลคำขออื่น ๆ ที่เกิดขึ้นพร้อมกันต่อไปได้การกำหนดโมเดลข้อมูล
ในเวิร์กโฟลว์แบบ document-oriented สคีมาอยู่ในโค้ดแอปพลิเคชันของคุณ เราใช้ Pydantic v2 เพื่อเชื่อมช่องว่างระหว่าง BSON ObjectId ของ MongoDB และสตริงมาตรฐานของ Python เพื่อให้แน่ใจว่าเอกสารทุกฉบับที่เข้าออกฐานข้อมูลจะถูกตรวจสอบตามข้อกำหนดของเรา
เราสร้างคลาสกำหนดเองชื่อ
PyObjectIdซึ่งจำเป็นเพราะ Pydantic ไม่รู้วิธีจัดการชนิด ObjectId ของ MongoDB โดยกำเนิด ด้วยการใช้__get_pydantic_core_schema__เราบอกให้ Pydantic ปฏิบัติต่อชนิดนี้เป็นสตริงใน JSON แต่ตรวจสอบความถูกต้องเป็นวัตถุ BSON สำหรับฐานข้อมูลเลเอาต์และเส้นทางเริ่มต้น
คอมโพเนนต์เลเอาต์เป็นฟังก์ชันระดับสูงที่นำกลับมาใช้ซ้ำได้ ใช้ห่อมุมมองของเรา เพื่อให้แน่ใจว่าการพึ่งพาที่สำคัญ เช่น Tailwind CSS สำหรับการตกแต่ง และ HTMX สำหรับความโต้ตอบ มีความสอดคล้องกันทั่วทั้งแอป
ด้วยการใช้คอมโพเนนต์ตัวพิมพ์ใหญ่ของ FastHTML (อย่างเช่น Main และ Div) เรารักษาโครงสร้างแบบ Pythonic ที่สะอาดตา ซึ่งสะท้อนโครงสร้างต้นไม้ของ HTML
ตอนนี้ หากรันแอป UI จะมีหน้าตาแบบนี้:
ภาพ: สกรีนช็อตหลังตั้งค่าเสร็จ
ยังไม่มียานใด ๆ เราจะเพิ่มและอัปเดตงานในส่วนถัดไป
การทำ CRUD แบบครบถ้วน
Read: แสดงงาน (GET /)
เพื่อแสดงงาน เราดึงเอกสารทั้งหมดและเรนเดอร์ภายในคอมโพเนนต์ อย่างไรก็ตาม ในโลกจริง ฐานข้อมูลอาจออฟไลน์ เวอร์ชันอัปเดตของ TaskList จะจัดการอย่างนุ่มนวล โดยแสดงขั้นตอนการแก้ปัญหาโดยตรงใน UI
การดึงข้อมูลที่ทนทาน
เราใช้
collection.find().to_list(length=None)เพื่อดึงเอกสารแบบอะซิงก์ ด้วยการห่อในบล็อก try/except เราสามารถตรวจจับได้หาก MongoDB ตัดการเชื่อมต่อ และให้ข้อเสนอแนะกับผู้ใช้ได้ทันทีCreate: การเพิ่มงานใหม่
โฟลว์การสร้างใช้ฟอร์ม HTML ที่เสริมด้วยแอตทริบิวต์ HTMX ในเวอร์ชันอัปเดตนี้ เราดึงข้อมูลฟอร์มจากออบเจ็กต์ Request โดยตรง และใช้
model_dumpเพื่อเตรียมเอกสารสำหรับแทรกลง MongoDBหากรันโค้ดด้านล่างในเทอร์มินัล:
จะเห็นว่างานใหม่นี้ถูกเพิ่มแล้ว:
ภาพ: สกรีนช็อตหลังเพิ่มงาน
Update: สลับสถานะเสร็จสิ้น
สำหรับการอัปเดต เราใช้คำขอ PATCH ซึ่งสาธิต "micro-refresh" ที่อัปเดตเฉพาะแถวของงานในฐานข้อมูลและเรนเดอร์ใหม่ใน UI โดยอ้างอิงตาม ID เฉพาะ
Delete: ลบงาน
การลบถูกเริ่มต้นโดย HTMX เมื่อ MongoDB ยืนยันการลบ เซิร์ฟเวอร์จะส่งการตอบกลับแบบ
Empty()HTMX จะตีความการตอบกลับว่างนี้เป็นสัญญาณให้ลบองค์ประกอบเป้าหมาย (div ที่อยู่ใกล้ที่สุด) ออกจาก DOMหากเราทำ…
…งานนั้นจะถูกลบ
ภาพ: สกรีนช็อตหลังการลบ
ดูสคริปต์ทั้งหมดได้ที่ GitHub.
สรุป
ด้วยการใช้สแต็กสมัยใหม่ที่เน้น Python คุณได้สร้างแอปแบบ reactive ที่หลีกเลี่ยงความซับซ้อนแบบดั้งเดิมของเฟรมเวิร์ก JavaScript ฝั่งไคลเอนต์ สถาปัตยกรรมนี้มอบข้อได้เปรียบสำคัญหลายประการสำหรับการพัฒนาเว็บสมัยใหม่ การทำงานร่วมกันระหว่าง UI แบบคอมโพเนนต์ของ FastHTML และโมเดลเอกสารที่ยืดหยุ่นของ MongoDB ทำให้คงไว้ซึ่งลอจิกธุรกิจ ความถูกต้องของข้อมูล และการนำเสนอภายในระบบนิเวศเดียวที่เหนียวแน่น แนวทางแบบ "ทั้งหมดใน Python" ช่วยลดภาระการพัฒนาและความซับซ้อนในการดีพลอยอย่างมีนัยสำคัญ
ขั้นถัดไปสำหรับผู้อ่าน