MongoEngine/mongoengine

Updating an inner dict within a `DictField` of a `DynamicDocument` raise an `AttributeError`

karimelhajoui63 opened this issue · 0 comments

Hi,

Details:

I have an issue with the updating of a DictField of a DynamicDocument that sometimes raises an exception.
It seems that there is a bug in mongoengine (more precisely in the _changed_fields) when we try to update a key of an inner dict, within the DictField that is at the top level of the Document.
As far as I dug, it seems to be because the inner dict is considered as a BaseDict, so when it is updated, the inner dict is added to _changed_fields but this inner dict isn't a field of the document, so there is an AttributeError on save() (see traceback below).

One more thing: this seem to be the case only with DynamicDocument and not with Document. And there is also no exception raised when the document came from the database.

Tested on versions:

  • Python 3.10.13
  • MongoEngine 0.27.0
  • PyMongo 4.6.1

Code to reproduce the bug:

from mongoengine import connect, DictField, DynamicDocument

connect("test", host="mongodb://localhost:27018/test")

class ExampleModel(DynamicDocument):  # but there is no exception at all with `Document`
    root_dict = DictField(default=None)
    

ExampleModel.objects().delete()  # just in case

example = ExampleModel()
example.root_dict = {"inner_dict":{"key": "value"}}
example.save()  # ✅

if example.root_dict["inner_dict"]["key"]:
    print("'inner_dict' is present in 'example'")

# ------------------

# example.reload()  # <-- with this line uncommented, no exception is raised

example.root_dict["inner_dict"]["key"] = "NEW_value"
try:
    example.save()  # raise "AttributeError" ❌
except AttributeError as exc:
    print(exc)
else:
    print("No exception for 'example'")

# ------------------
    
example_from_db = ExampleModel.objects().first()
example_from_db.root_dict["inner_dict"]["key"] = "NEW_value"
try:
    example_from_db.save()  # don't raise any exception ✅
except AttributeError as exc:
    print(exc)
else:
    print("No exception for 'example_from_db'")

Output of this program:

'inner_dict' is present in 'example'
'ExampleModel' object has no attribute 'inner_dict'
No exception for 'example_from_db'

Complete traceback of the error on .save():

Traceback (most recent call last):
  File "/workspaces/bin/personal/mongoengine_dictfield.py", line 23, in <module>
    example.save()
  File "/workspaces/.venv/lib/python3.10/site-packages/mongoengine/document.py", line 429, in save
    object_id, created = self._save_update(
  File "/workspaces/.venv/lib/python3.10/site-packages/mongoengine/document.py", line 545, in _save_update
    update_doc = self._get_update_doc()
  File "/workspaces/.venv/lib/python3.10/site-packages/mongoengine/document.py", line 501, in _get_update_doc
    updates, removals = self._delta()
  File "/workspaces/.venv/lib/python3.10/site-packages/mongoengine/base/document.py", line 746, in _delta
    d = getattr(d, real_path)
AttributeError: 'ExampleModel' object has no attribute 'inner_dict'