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"技术支持
您就能看到这条新任务已被添加:
图片:添加任务后的截图
更新:切换完成状态
为处理更新,我们使用 PATCH 请求。这展示了“微刷新”的方式:仅更新数据库中的单条任务记录,并基于其特定 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 的现代 Web 开发路径。在本教程中,我们将构建一个响应式、实时的任务管理应用程序,在一个可维护的 Python 文件中演示完整的 CRUD(创建、读取、更新、删除)生命周期。为了在 Python 中上手使用 MongoDB,我推荐课程 Introduction to MongoDB in Python。
什么是 FastHTML?
FastHTML 是一个建立在 FastAPI 基础之上的极简高性能框架。它引入了 Pythonic 的 HTML 范式,使开发者可以用可复用的 Python 函数来构建完整的前端,而无需传统模板。
其核心优势在于与 HTMX 的原生集成。通过使用简单的 HTML 属性驱动服务端更新,HTMX 让您无需复杂的重型 JavaScript 构建栈,也能创建动态的单页应用体验。
什么是 MongoDB?
MongoDB 是领先的通用型、面向文档的 NoSQL 数据库。其灵活的架构与 JSON 风格的 BSON 文档,使其非常适合现代的迭代式开发。
在 Python 端,我们使用官方的异步驱动 Motor,它提供了非阻塞接口,非常契合 FastAPI 与 FastHTML 的性能导向架构。
为何这一技术栈表现出色
将这些技术结合起来,可以构建一个高效的开发环境:
设置与连接
在开始本教程之前,您需要准备以下先决条件:
项目初始化
为展示该技术栈的高效性,我们将在单个 app.py 文件中实现整个应用,包括数据库配置、数据模型与响应式路由。
安装
我们需要 FastHTML 框架、Motor(MongoDB 官方异步驱动)以及用于 ASGI 服务器的 Uvicorn。
MongoDB 连接设置
我们使用
AsyncIOMotorClient建立非阻塞连接。这样在应用等待数据库 I/O 时,仍可继续处理其他并发请求。定义数据模型
在面向文档的工作流中,架构定义在您的应用代码里。我们使用 Pydantic v2 来桥接 MongoDB 的 BSON ObjectId 与标准的 Python 字符串。这可确保进入或离开数据库的每个文档都按我们的要求进行校验。
我们定义了自定义的
PyObjectId类。这是必要的,因为 Pydantic 并不原生支持 MongoDB 的 ObjectId 类型。通过__get_pydantic_core_schema__,我们让 Pydantic 在 JSON 中将其视为字符串,但在数据库端按 BSON 对象进行校验。布局与初始路由
布局组件是一个可复用的高阶函数,用于包裹视图。这确保了 Tailwind CSS(用于样式)和 HTMX(用于交互)等关键依赖在整个应用中保持一致。
借助 FastHTML 的标题式组件(如 Main 与 Div),我们保持了干净的 Pythonic 结构,映射出 HTML 树。
现在运行应用,您的界面将如下所示:
图片:完成初始设置后的截图
目前还没有任务。我们将在下一节添加和更新任务。
完整 CRUD 实现
读取:显示任务(GET /)
要显示任务,我们会获取所有文档并在组件中渲染。不过在真实场景中,数据库可能离线。我们更新后的 TaskList 会优雅地处理这一情况,在界面中直接提供故障排除步骤。
健壮的数据获取
我们使用
collection.find().to_list(length=None)异步获取文档。通过放入 try/except 块,我们可以检测 MongoDB 是否断开连接,并立即向用户反馈。创建:添加新任务
创建流程使用带有 HTMX 属性增强的 HTML 表单。在更新版本中,我们从 Request 对象中显式提取表单数据,并使用
model_dump准备要插入 MongoDB 的文档。如果我们在终端运行以下代码:
您就能看到这条新任务已被添加:
图片:添加任务后的截图
更新:切换完成状态
为处理更新,我们使用 PATCH 请求。这展示了“微刷新”的方式:仅更新数据库中的单条任务记录,并基于其特定 ID 在界面中重新渲染。
删除:移除任务
删除由 HTMX 触发。一旦 MongoDB 确认删除,服务器返回
Empty()响应。HTMX 会将这个空响应视为移除目标元素(最近的 div)的信号,并从 DOM 中移除它。如果我们执行……
……任务就会被删除。
图片:删除后的截图
您可以在 GitHub 查看完整脚本。
结论
借助现代、以 Python 为中心的技术栈,您构建了一个无需传统客户端 JavaScript 框架复杂性的响应式应用。此架构为现代 Web 开发带来多项关键优势。FastHTML 的基于组件的 UI 与 MongoDB 灵活的文档模型相互协同,使您能够在单一、连贯的生态中维护业务逻辑、数据完整性与呈现层。这种“全栈皆 Python”的方式显著降低了开发开销与部署复杂度。
读者的后续步骤