beanie 的 find 默认返回 document 子类实例(含数据库方法和状态缓存),非纯 pydantic 模型;as_pydantic() 或转 dict 会丢失变更跟踪能力,导致 save() 失效。

beanie 的 find 默认不返回 Pydantic 模型实例?
不是不返回,是它默认返回的是 Document 子类的实例——但这个实例和纯 BaseModel 不同,自带数据库操作方法(比如 .save()、.delete()),也缓存了原始字段状态。如果你用 find(...).to_list() 得到对象,直接改字段再 .save() 是能触发增量更新的;但如果先用 dict() 或 model_dump() 转成 dict 再构造新模型,就丢了这些能力。
- 常见错误现象:
doc.name = "new"后调用doc.save()没生效 → 实际可能是你用了FindMany(...).as_pydantic()或手动转成了普通BaseModel - 正确做法:保持用
Document子类实例操作,避免中间转 dict;需要纯数据时再用.model_dump() - 性能影响:用
.as_pydantic()会跳过内部状态跟踪,省一点内存但失去变更检测,频繁更新场景反而更慢
odmantic 的 MotorClient 初始化方式容易漏掉 async 模式
odmantic 依赖 motor,但它的 AsyncIOEngine 构造时如果传入的 MotorClient 没显式指定 io_loop 或没在 async 上下文里创建,运行时不会报错,但所有查询会卡住或静默失败——尤其在 FastAPI 依赖注入里直接 new MotorClient() 最危险。
- 常见错误现象:调用
engine.find()无响应、协程永远不 resolve、日志里看不到 MongoDB 查询日志 - 正确做法:确保
MotorClient创建在 async 环境中,推荐用MotorClient(..., io_loop=asyncio.get_event_loop())(Py3.7+ 可省略),或更稳妥地用await motor.motor_asyncio.AsyncIOMotorClient(...).get_io_loop() - 兼容性注意:odmantic 0.15+ 已弃用
SyncIOEngine,别混用同步 client 和异步 engine
beanie 的 ReplaceOne 更新不触发 model 验证?
beanie 的 update_one()、find_one_and_update() 这类基于 pymongo 原生命令的操作,默认绕过 Pydantic 模型验证——它只校验字段是否存在、类型是否匹配 schema,但不会跑 @field_validator 或 model_config 里的约束逻辑。
- 常见错误现象:字段设了
min_length=3,但用update_one({"$set": {"name": "a"}})成功写入了非法值 - 解决路径:要么改用
document.save()(走完整验证),要么在 update 前手动调用MyModel.model_validate({"name": "a"})做前置校验 - 性能权衡:全量验证 + save() 会多一次 round-trip(先查后写),高频更新场景需评估延迟容忍度
迁移时别忽略索引定义语法差异
beanie 用 IndexModel + @document 的 indexes 参数,odmantic 用 @model_index 装饰器,两者都支持复合索引、唯一、TTL,但 TTL 字段名和单位不一致:beanie 要求字段名是 "expire_at",odmantic 认 "expire_after_seconds" 且值单位是秒而非毫秒。
立即学习“Python免费学习笔记(深入)”;
- 容易踩的坑:复制 odmantic 的
@model_index(..., expire_after_seconds=3600)到 beanie 项目里,结果 TTL 完全不生效 - 实操建议:检查
collection.index_information()输出,确认索引文档里"expireAfterSeconds"字段存在且值正确 - 兼容性提醒:MongoDB 6.0+ 对 TTL 索引的后台线程行为有调整,两个库生成的索引定义若混用,可能造成清理延迟波动
最麻烦的其实是嵌套文档更新逻辑——beanie 把嵌套 Document 当子集合处理,odmantic 当内联结构;同一个 JSON Schema,在两边映射出的 Python 类行为可能差一个 .save() 调用的距离。








