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)