15

Okay, so pardon me if I don't make much sense. I face this 'ObjectId' object is not iterable whenever I run the collections.find() functions. Going through the answers here, I'm not sure where to start. I'm new to programming, please bear with me.

Every time I hit the route which is supposed to fetch me data from Mongodb, I getValueError: [TypeError("'ObjectId' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')].

Help

Chris
  • 18,724
  • 6
  • 46
  • 80
nathanielsenje
  • 151
  • 1
  • 1
  • 4

9 Answers9

8

Exclude the "_id" from the output.

result = collection.find_one({'OpportunityID': oppid}, {'_id': 0})
AlbaOpus
  • 81
  • 2
8

I was having a similar problem to this myself. Not having seen your code I am guessing the traceback similarly traces the error to FastAPI/Starlette not being able to process the "_id" field - what you will therefore need to do is change the "_id" field in the results from an ObjectId to a string type and rename the field to "id" (without the underscore) on return to avoid incurring issues with Pydantic.

James Harrison
  • 323
  • 4
  • 12
7

First of all, if we had some examples of your code, this would be much easier. I can only assume that you are not mapping your MongoDb collection data to your Pydantic BaseModel correctly.

Read this: MongoDB stores data as BSON. FastAPI encodes and decodes data as JSON strings. BSON has support for additional non-JSON-native data types, including ObjectId which can't be directly encoded as JSON. Because of this, we convert ObjectIds to strings before storing them as the _id. I want to draw attention to the id field on this model. MongoDB uses _id, but in Python, underscores at the start of attributes have special meaning. If you have an attribute on your model that starts with an underscore, pydantic—the data validation framework used by FastAPI—will assume that it is a private variable, meaning you will not be able to assign it a value! To get around this, we name the field id but give it an alias of _id. You also need to set allow_population_by_field_name to True in the model's Config class.

Here is a working example:

First create the BaseModel:

class PyObjectId(ObjectId):
    """ Custom Type for reading MongoDB IDs """
    @classmethod
    def __get_validators__(cls):
        yield cls.validate

    @classmethod
    def validate(cls, v):
        if not ObjectId.is_valid(v):
            raise ValueError("Invalid object_id")
        return ObjectId(v)

    @classmethod
    def __modify_schema__(cls, field_schema):
        field_schema.update(type="string")

class Student(BaseModel):
    id: PyObjectId = Field(default_factory=PyObjectId, alias="_id")
    first_name: str
    last_name: str

    class Config:
        allow_population_by_field_name = True
        arbitrary_types_allowed = True
        json_encoders = {ObjectId: str}

Now just unpack everything:

async def get_student(student_id) -> Student:
    data = await collection.find_one({'_id': student_id})
    if data is None:
        raise HTTPException(status_code=404, detail='Student not found.')
    student: Student = Student(**data)
    return student
mighty_mike
  • 714
  • 7
  • 19
  • Doesn't work for me on Mongo 5.05. It works for nested document but the original document id type was changed to string so pymongo throws validation error expecting the id as type of bytes or ObjectId. – Willy satrio nugroho Jun 02 '22 at 10:26
2

Use the response model inside app decorator Here is the sample example

from pydantic import BaseModel
 class Todo(BaseModel):
   title:str
   details:str

main.py

@app.get("/{title}",response_model=Todo)
  async def get_todo(title:str):
  response=await fetch_one_todo(title)
  if not response:
    raise 
         HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail='not found')
  return response
1

use db.collection.find(ObjectId:"12348901384918")
here db.collection is database name and use double quotes for the string .

1

I was trying to iterate through all the documents and what worked for me was this solution https://github.com/tiangolo/fastapi/issues/1515#issuecomment-782835977

These lines just needed to be added after the child of ObjectID class. An example is given in the following link. https://github.com/tiangolo/fastapi/issues/1515#issuecomment-782838556

  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 13 '22 at 12:42
0

I had this issue until I upgraded from mongodb version 5.0.9 to version 6.0.0 so mongodb made some changes on their end to handle this if you have the ability to upgrade! I ran into this issue when creating a test server and when I created a new test server that was 6.0.0, it fixed the error.

kevinb
  • 1
  • 2
0

Okay, so adding this to the topmost answer as something you can run into pymongo insert function updates the response dict within python itself to include a "_id" even though it was passed as a parameter. Just something a new dev might not know.

from pymongo import *
a = {'key1':'value1'}
db1.collection1.insert(a)
print a
{'_id': ObjectId('53ad61aa06998f07cee687c3'), 'key1': 'value1'}

Linked question: Why does db.insert(dict) add _id key to the dict object while using pymongo

Doc: https://pymongo.readthedocs.io/en/stable/tutorial.html#inserting-a-document

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 07 '23 at 21:19
0

I'm sort of new to programming (almost a year experience at the moment of this answer), but I've worked with Mongo a couple of times by now.

I got to this question because I had a similar problem: I needed to collect IDs of my documents, but when tried to subscript the $oid, an error popped up.

It happens that the simplest answer is: if you only havethe ObjectId that Mongo automatically sets inside the _id object, transform the whole _id object to a string and you will get the ID. As to say:

  def get_ids(self) -> list:
    response = []
      results = self.db.collection.find()
      for x in results:
        id = str(x["_id"])
        response.append(id)
    return parse_json(response)

This way you don't get the error from the ObjectId.