BeanieODM / beanie

Asynchronous Python ODM for MongoDB
http://beanie-odm.dev/
Apache License 2.0
1.9k stars 201 forks source link

[BUG] Problem in save method #412

Closed miladva closed 1 year ago

miladva commented 1 year ago

Bug in action of save method I wanna save my document to database and use it after insert but I have problem with this situation:

This is My code

class Child(BaseModel):
    child_field: str

class Sample(Document):
    field: Dict[str, Child]

  instance1 = Sample(field={"Bar": Child(child_field="Foo")})
  print(instance1)
  await instance1.save()
  print(instance1)

Expected behavior

# first print :
id=None revision_id=None field={'Bar': Child(child_field='Foo')}
# second print:
id=ObjectId('636b9d2997bb72433b944ef4') revision_id=None field={'Bar': Child(child_field='Foo')}

But I got this:

# first print :
id=None revision_id=None field={'Bar': Child(child_field='Foo')}
# second print:
id=ObjectId('636b9d2997bb72433b944ef4') revision_id=None field={'Bar': {'child_field': 'Foo'}}
field={'Bar': {'child_field': 'Foo'}} != field={'Bar': Child(child_field='Foo')}

It's okay when I wanna fetch from db and my field filled by Child model but when I want to save to db, this situation happened!!!

LinuxKernel31 commented 1 year ago

Your code looks right, when you initialize the Sample document the id will be set to None. Once you call the instance1.save() attribute mongodb will automatically generate an ObjectId which is an bson object. The fields inside instance1: Sample will be populated.

miladva commented 1 year ago

my question is about field not id. I want to get this after save :

field={'Bar': Child(child_field='Foo')}

But I got this:

field={'Bar': {'child_field': 'Foo'}}
LinuxKernel31 commented 1 year ago

Can you give me an explanation on why you need the object `Child?

If you really need it that way, try to have a copy of your variable before saving.

class Child(BaseModel):
    child_field: str

class Sample(Document):
    field: Dict[str, Child]

  instance1 = Sample(field={"Bar": Child(child_field="Foo")})
  copy_instance1 = instance1
  await instance1.save()

  print(copy_instance1)
  # Sample(field={'Bar': Child(child_field='Foo')})

  #access the field inside
  print(copy_instance1.field)
  #{'Bar': Child(child_field='Foo')}

  print(instance1)
  #{'field': {'Bar': {'child_field': 'Foo'}}}

Once you call .save() it will automatically convert to to dictionary.

if you want to convert copy_instance1 to a dictionary just do copy_instance1.dict()

miladva commented 1 year ago

Ok, This is my full example:

class Child(BaseModel):
    child_field: str

    def do_something(self)
         ...

class Sample(Document):
    field: Dict[str, Child]

  instance1 = Sample(field={"Bar": Child(child_field="Foo")})
  await instance1.save()
  instance1.field["Bar"].do_something()
  await instance1.save()
AttributeError: 'dict' object has no attribute 'do_something'

I do this pattern more than once, with lot of save and update

LinuxKernel31 commented 1 year ago

You're doing it differently, this is how you should do it:

This is just a scenario:

class Child(BaseModel):
    child_field: str

    def do_something(self)
         #capitalize
         self.child_field = self.child_field.upper()

class Sample(Document):
    field: Dict[str, Child]

  child = Child(child_field="Foo")
  # this will capitalize the child_field: str
  child.do_something()
  instance1 = Sample(field={"Bar": child})
  await instance1.save()

Initially, you wanted to process the Child class first before passing it to Sample

LinuxKernel31 commented 1 year ago

Anyway, this issue needs to be closed as it is not a bug from Beanie after all. My advice is to read the Pydantic documentation first and learn how it works.

miladva commented 1 year ago

I am talking about after save, your are telling me about capitalize ? When I call save function, it changes my models and I can't work with that model

roman-right commented 1 year ago

Hey, Thank you for the catch. The thing is dict is mutable, and it was changed during encoding (to pass a dict object to the db driver). I'll publish the fix in ~ half an hour

roman-right commented 1 year ago

Fixed in 1.15.3. @miladvayani please, try.