FastAPI 问题插入 MongoDB
FastAPI issues inserting into MongoDB
我在通过 FastAPI 插入 MongoDB 时遇到一些问题。
以下代码按预期工作。请注意 response
变量如何未在 response_to_mongo()
.
中使用
model
是一个 sklearn ElasticNet 模型。
app = FastAPI()
def response_to_mongo(r: dict):
client = pymongo.MongoClient("mongodb://mongo:27017")
db = client["models"]
model_collection = db["example-model"]
model_collection.insert_one(r)
@app.post("/predict")
async def predict_model(features: List[float]):
prediction = model.predict(
pd.DataFrame(
[features],
columns=model.feature_names_in_,
)
)
response = {"predictions": prediction.tolist()}
response_to_mongo(
{"predictions": prediction.tolist()},
)
return response
然而,当我这样写 predict_model()
并将 response
变量传递给 response_to_mongo()
时:
@app.post("/predict")
async def predict_model(features: List[float]):
prediction = model.predict(
pd.DataFrame(
[features],
columns=model.feature_names_in_,
)
)
response = {"predictions": prediction.tolist()}
response_to_mongo(
response,
)
return response
我收到一条错误消息:
TypeError: 'ObjectId' object is not iterable
根据我的阅读,这似乎是由于 FastAPI 和 Mongo 之间的 BSON/JSON 问题。但是,为什么当我不使用变量时它在第一种情况下起作用?这是因为 FastAPI 的异步特性吗?
根据 documentation:
When a document is inserted a special key, "_id"
, is automatically
added if the document doesn’t already contain an "_id"
key. The value
of "_id"
must be unique across the collection. insert_one()
returns an
instance of InsertOneResult. For more information on "_id", see the
documentation on _id.
因此,在第二种情况下,当您将字典传递给 insert_one()
函数时,Pymongo 将向您的字典添加唯一标识符(即 ObjectId
),以便从中检索数据数据库;因此,当从端点返回响应时,ObjectId
无法序列化(因为默认情况下,FastAPI 使用 jsonable_encoder
and returns a JSONResponse
序列化数据)。
解决方案 1
在返回之前从 response
字典中删除 "_id"
键(参见 here 如何从字典中删除键):
response.pop('_id', None)
解决方案 2
将加载的 BSON
转储为有效的 JSON
字符串,然后将其作为字典重新加载,如 here and here.
所述
from bson import json_util
import json
response = json.loads(json_util.dumps(response))
解决方案 3
定义自定义 JSONEncoder
, as described here:
import json
from bson import ObjectId
class JSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, ObjectId):
return str(o)
return json.JSONEncoder.default(self, o)
response = JSONEncoder().encode(response)
我在通过 FastAPI 插入 MongoDB 时遇到一些问题。
以下代码按预期工作。请注意 response
变量如何未在 response_to_mongo()
.
model
是一个 sklearn ElasticNet 模型。
app = FastAPI()
def response_to_mongo(r: dict):
client = pymongo.MongoClient("mongodb://mongo:27017")
db = client["models"]
model_collection = db["example-model"]
model_collection.insert_one(r)
@app.post("/predict")
async def predict_model(features: List[float]):
prediction = model.predict(
pd.DataFrame(
[features],
columns=model.feature_names_in_,
)
)
response = {"predictions": prediction.tolist()}
response_to_mongo(
{"predictions": prediction.tolist()},
)
return response
然而,当我这样写 predict_model()
并将 response
变量传递给 response_to_mongo()
时:
@app.post("/predict")
async def predict_model(features: List[float]):
prediction = model.predict(
pd.DataFrame(
[features],
columns=model.feature_names_in_,
)
)
response = {"predictions": prediction.tolist()}
response_to_mongo(
response,
)
return response
我收到一条错误消息:
TypeError: 'ObjectId' object is not iterable
根据我的阅读,这似乎是由于 FastAPI 和 Mongo 之间的 BSON/JSON 问题。但是,为什么当我不使用变量时它在第一种情况下起作用?这是因为 FastAPI 的异步特性吗?
根据 documentation:
When a document is inserted a special key,
"_id"
, is automatically added if the document doesn’t already contain an"_id"
key. The value of"_id"
must be unique across the collection.insert_one()
returns an instance of InsertOneResult. For more information on "_id", see the documentation on _id.
因此,在第二种情况下,当您将字典传递给 insert_one()
函数时,Pymongo 将向您的字典添加唯一标识符(即 ObjectId
),以便从中检索数据数据库;因此,当从端点返回响应时,ObjectId
无法序列化(因为默认情况下,FastAPI 使用 jsonable_encoder
and returns a JSONResponse
序列化数据)。
解决方案 1
在返回之前从 response
字典中删除 "_id"
键(参见 here 如何从字典中删除键):
response.pop('_id', None)
解决方案 2
将加载的 BSON
转储为有效的 JSON
字符串,然后将其作为字典重新加载,如 here and here.
from bson import json_util
import json
response = json.loads(json_util.dumps(response))
解决方案 3
定义自定义 JSONEncoder
, as described here:
import json
from bson import ObjectId
class JSONEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, ObjectId):
return str(o)
return json.JSONEncoder.default(self, o)
response = JSONEncoder().encode(response)