FastAPI无法返回MongoDB对象的问题求助及报错排查
问题描述
我尝试向以下路由发起请求:
http://127.0.0.1:8000/testadmins
使用如下函数:
@app.get("/testadmins/") async def get_admins(): return await get_database()
get_database()函数实现如下:
async def get_database(): uri = "(my mongo uri)" client = MongoClient(uri, server_api=ServerApi('1')) adms = client.test['admins'].find() print(adms) return adms
该函数应返回集合中的所有管理员数据。控制台打印admins数据正常,但路由无法返回,出现错误:TypeError('vars() argument must have __dict__ attribute')。我尝试添加async await、用FastAPI的json_encodable()包装函数,但都无效。在Postman中请求返回500(内部服务器错误)。
(更新:我推测问题与日期时间类型有关,因为逐个测试键值时,仅datetime.datetime(...)格式的字段无法正常返回。)
(__v和_id字段也无法正常返回,但不确定原因)
错误追踪信息:
Traceback (most recent call last): File "/opt/homebrew/lib/python3.11/site-packages/fastapi/encoders.py", line 235, in jsonable_encoder data = vars(obj) ^^^^^^^^^ TypeError: vars() argument must have __dict__ attribute The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/opt/homebrew/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 426, in run_asgi result = await app( # type: ignore[func-returns-value] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/homebrew/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__ return await self.app(scope, receive, send) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/homebrew/lib/python3.11/site-packages/fastapi/applications.py", line 289, in __call__ await super().__call__(scope, receive, send) File "/opt/homebrew/lib/python3.11/site-packages/starlette/applications.py", line 122, in __call__ await self.middleware_stack(scope, receive, send) File "/opt/homebrew/lib/python3.11/site-packages/starlette/middleware/errors.py", line 184, in __call__ raise exc File "/opt/homebrew/lib/python3.11/site-packages/starlette/middleware/errors.py", line 162, in __call__ await self.app(scope, receive, _send) File "/opt/homebrew/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 79, in __call__ raise exc File "/opt/homebrew/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 68, in __call__ await self.app(scope, receive, sender) File "/opt/homebrew/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__ raise e File "/opt/homebrew/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__ await self.app(scope, receive, send) File "/opt/homebrew/lib/python3.11/site-packages/starlette/routing.py", line 718, in __call__ await route.handle(scope, receive, send) File "/opt/homebrew/lib/python3.11/site-packages/starlette/routing.py", line 276, in handle await self.app(scope, receive, send) File "/opt/homebrew/lib/python3.11/site-packages/starlette/routing.py", line 66, in app response = await func(request) ^^^^^^^^^^^^^^^^^^^ File "/opt/homebrew/lib/python3.11/site-packages/fastapi/routing.py", line 291, in app content = await serialize_response( ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/homebrew/lib/python3.11/site-packages/fastapi/routing.py", line 179, in serialize_response return jsonable_encoder(response_content) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/opt/homebrew/lib/python3.11/site-packages/fastapi/encoders.py", line 209, in jsonable_encoder jsonable_encoder( File "/opt/homebrew/lib/python3.11/site-packages/fastapi/encoders.py", line 238, in jsonable_encoder raise ValueError(errors) from e ValueError: [TypeError("'ObjectId' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]
解决方案
问题核心是MongoDB返回的游标、ObjectId、datetime类型无法被FastAPI默认的JSON编码器序列化,具体解决步骤如下:
1. 转换游标为Python列表
find()返回的是MongoDB游标对象,不是可直接序列化的列表,需要先转为列表:
adms = list(client.test['admins'].find())
2. 手动处理特殊字段
MongoDB的_id是ObjectId类型,datetime字段也无法直接转为JSON,可遍历数据逐个处理:
async def get_database(): uri = "(my mongo uri)" client = MongoClient(uri, server_api=ServerApi('1')) cursor = client.test['admins'].find() adms = [] for doc in cursor: # 将ObjectId转为字符串 doc['_id'] = str(doc['_id']) # 将datetime字段转为ISO格式字符串(替换为你实际的日期字段名) if 'create_time' in doc: doc['create_time'] = doc['create_time'].isoformat() # 不需要__v字段可直接删除 if '__v' in doc: del doc['__v'] adms.append(doc) return adms
3. 使用MongoDB自带的JSON编码器
用bson.json_util工具一键处理所有MongoDB特殊类型:
from bson import json_util import json async def get_database(): uri = "(my mongo uri)" client = MongoClient(uri, server_api=ServerApi('1')) cursor = client.test['admins'].find() adms = list(cursor) # 序列化后转回字典,让FastAPI可正常返回 json_str = json_util.dumps(adms) return json.loads(json_str)
4. 全局自定义FastAPI编码器
如果需要全局处理MongoDB类型,可自定义编码器并绑定到FastAPI应用:
from fastapi import FastAPI import json from bson import ObjectId from datetime import datetime class CustomJSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, ObjectId): return str(obj) if isinstance(obj, datetime): return obj.isoformat() return super().default(obj) app = FastAPI() @app.get("/testadmins/") async def get_admins(): data = await get_database() return json.loads(json.dumps(data, cls=CustomJSONEncoder))
以上任意一种方式都能解决序列化错误,让路由正常返回MongoDB数据。
内容的提问来源于stack exchange,提问作者Tiron Raul
相关产品推荐
相关产品推荐

