motorgridfsbucket 初始化必须传 motorclient 实例而非 motordatabase,正确写法为 gridfsbucket(client["mydb"]);上传用 upload_from_stream(),下载用 open_download_stream() 配合 async for,删除需手动清理 chunks。

MotorGridFSBucket 的初始化必须传 client 而非 database
很多人直接照搬 PyMongo 的用法,把 MotorDatabase 实例传给 MotorGridFSBucket 构造函数,结果报错:TypeError: expected MotorClient, got MotorDatabase。这是因为 Motor 的 GridFS 实现不支持数据库级初始化,必须从客户端开始——它内部需要访问 client.delegate 来协调异步 I/O。
正确写法是:
from motor.motor_asyncio import AsyncIOMotorClient
from gridfs import GridFSBucket
<p>client = AsyncIOMotorClient("mongodb://localhost:27017")
bucket = GridFSBucket(client["mydb"]) # ✅ 传 database 实例(MotorDatabase)</p><h1>注意:不是 GridFSBucket(client) 也不是 GridFSBucket(db)</h1>
虽然参数名是 database,但类型必须是 MotorDatabase;传 MotorClient 会触发类型检查失败。
上传文件时不能直接 await write(),要用 upload_from_stream()
MotorGridFSBucket 没有 write() 方法,也没有同步风格的 put()。常见错误是想当然调用 await bucket.write(...),结果抛出 AttributeError。
上传必须走流式接口,且需注意返回值是 ObjectId(不是 coroutine):
file_id = await bucket.upload_from_stream(
"report.pdf",
io.BytesIO(b"%PDF-1.5..."),
metadata={"author": "admin", "version": 2}
)
-
upload_from_stream()是唯一推荐的上传入口,内部自动处理分块和异步写入 -
filename必须是字符串,不支持路径(如"logs/2024/app.log"会被当作完整文件名存储) -
metadata字典只支持 JSON 序列化类型(不能放 datetime、ObjectId 等,除非手动转为字符串)
下载大文件务必用 open_download_stream() + async for,别用 read()
对几百 MB 以上的文件,如果直接 await grid_out.read(),会一次性加载全部内容到内存,极易 OOM。Motor 的 GridOut 对象支持异步迭代,这才是流式下载的正确姿势。
示例:边读边写入本地磁盘
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
grid_out = await bucket.open_download_stream(file_id)
async with aiofiles.open("/tmp/downloaded.pdf", "wb") as f:
async for chunk in grid_out:
await f.write(chunk)
关键点:
-
open_download_stream()返回的是GridOut实例,它实现了__aiter__,所以能async for -
chunk是bytes,每次默认读取 255KB(可调chunk_size参数) - 不要在循环里调
await grid_out.read()—— 它会重置游标,导致重复读或跳块
删除文件后 metadata 不会自动清理,要手动 drop chunks 和 files
delete() 方法只删 fs.files 文档,fs.chunks 中对应的数据块仍残留。这会导致磁盘空间不释放,且后续通过 find() 查到的文件可能无法完整读取(GridOut 构造时会校验 chunk 数量)。
安全删除需两步:
await bucket.delete(file_id)
await client["mydb"]["fs.chunks"].delete_many({"files_id": file_id})
更稳妥的做法是封装成事务(MongoDB 4.0+ 支持副本集事务),但注意:GridFSBucket 本身不参与事务,必须显式操作底层集合。
另外,find() 返回的 GridOutCursor 不支持 await cursor.to_list(),得用 async for 遍历,否则会卡住。
GridFS 的 chunk 大小、文件元数据一致性、并发上传时的 ObjectId 冲突,这些细节在压测时才容易暴露——别只在单文件小数据下验证逻辑。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










