MongoEngine / mongoengine

A Python Object-Document-Mapper for working with MongoDB
http://mongoengine.org
MIT License
4.23k stars 1.23k forks source link

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

Open karimelhajoui63 opened 8 months ago

karimelhajoui63 commented 8 months ago

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:

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'
karimelhajoui63 commented 2 weeks ago

Hi, Who should I tag for this ? @bagerard ?