Skip to content

Commit bb9ba73

Browse files
authored
Merge pull request #2545 from bagerard/fix_embedded_instance_deepcopy
Fix embedded instance deepcopy
2 parents 3b10236 + 66978ae commit bb9ba73

6 files changed

Lines changed: 69 additions & 27 deletions

File tree

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,4 @@ that much better:
260260
* Stankiewicz Mateusz (https://github.com/mas15)
261261
* Felix Schultheiß (https://github.com/felix-smashdocs)
262262
* Jan Stein (https://github.com/janste63)
263+
* Timothé Perez (https://github.com/AchilleAsh)

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Development
88
===========
99
- (Fill this out as you fix issues and develop your features).
1010
- EnumField improvements: now `choices` limits the values of an enum to allow
11+
- Fix deepcopy of EmbeddedDocument #2202
1112

1213
Changes in 0.23.1
1314
===========

mongoengine/document.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ def __eq__(self, other):
9999
def __ne__(self, other):
100100
return not self.__eq__(other)
101101

102+
def __getstate__(self):
103+
data = super().__getstate__()
104+
data["_instance"] = None
105+
return data
106+
107+
def __setstate__(self, state):
108+
super().__setstate__(state)
109+
self._instance = state["_instance"]
110+
102111
def to_mongo(self, *args, **kwargs):
103112
data = super().to_mongo(*args, **kwargs)
104113

@@ -126,7 +135,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
126135
create a specialised version of the document that will be stored in the
127136
same collection. To facilitate this behaviour a `_cls`
128137
field is added to documents (hidden though the MongoEngine interface).
129-
To enable this behaviourset :attr:`allow_inheritance` to ``True`` in the
138+
To enable this behaviour set :attr:`allow_inheritance` to ``True`` in the
130139
:attr:`meta` dictionary.
131140
132141
A :class:`~mongoengine.Document` may use a **Capped Collection** by

tests/document/test_instance.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ def tearDown(self):
6565
for collection in list_collection_names(self.db):
6666
self.db.drop_collection(collection)
6767

68-
def assertDbEqual(self, docs):
68+
def _assert_db_equal(self, docs):
6969
assert list(self.Person._get_collection().find().sort("id")) == sorted(
7070
docs, key=lambda doc: doc["_id"]
7171
)
7272

73-
def assertHasInstance(self, field, instance):
73+
def _assert_has_instance(self, field, instance):
7474
assert hasattr(field, "_instance")
7575
assert field._instance is not None
7676
if isinstance(field._instance, weakref.ProxyType):
@@ -740,11 +740,11 @@ class Doc(Document):
740740
Doc.drop_collection()
741741

742742
doc = Doc(embedded_field=Embedded(string="Hi"))
743-
self.assertHasInstance(doc.embedded_field, doc)
743+
self._assert_has_instance(doc.embedded_field, doc)
744744

745745
doc.save()
746746
doc = Doc.objects.get()
747-
self.assertHasInstance(doc.embedded_field, doc)
747+
self._assert_has_instance(doc.embedded_field, doc)
748748

749749
def test_embedded_document_complex_instance(self):
750750
"""Ensure that embedded documents in complex fields can reference
@@ -759,11 +759,11 @@ class Doc(Document):
759759

760760
Doc.drop_collection()
761761
doc = Doc(embedded_field=[Embedded(string="Hi")])
762-
self.assertHasInstance(doc.embedded_field[0], doc)
762+
self._assert_has_instance(doc.embedded_field[0], doc)
763763

764764
doc.save()
765765
doc = Doc.objects.get()
766-
self.assertHasInstance(doc.embedded_field[0], doc)
766+
self._assert_has_instance(doc.embedded_field[0], doc)
767767

768768
def test_embedded_document_complex_instance_no_use_db_field(self):
769769
"""Ensure that use_db_field is propagated to list of Emb Docs."""
@@ -792,11 +792,11 @@ class Account(Document):
792792

793793
acc = Account()
794794
acc.email = Email(email="test@example.com")
795-
self.assertHasInstance(acc._data["email"], acc)
795+
self._assert_has_instance(acc._data["email"], acc)
796796
acc.save()
797797

798798
acc1 = Account.objects.first()
799-
self.assertHasInstance(acc1._data["email"], acc1)
799+
self._assert_has_instance(acc1._data["email"], acc1)
800800

801801
def test_instance_is_set_on_setattr_on_embedded_document_list(self):
802802
class Email(EmbeddedDocument):
@@ -808,11 +808,11 @@ class Account(Document):
808808
Account.drop_collection()
809809
acc = Account()
810810
acc.emails = [Email(email="test@example.com")]
811-
self.assertHasInstance(acc._data["emails"][0], acc)
811+
self._assert_has_instance(acc._data["emails"][0], acc)
812812
acc.save()
813813

814814
acc1 = Account.objects.first()
815-
self.assertHasInstance(acc1._data["emails"][0], acc1)
815+
self._assert_has_instance(acc1._data["emails"][0], acc1)
816816

817817
def test_save_checks_that_clean_is_called(self):
818818
class CustomError(Exception):
@@ -921,7 +921,7 @@ def test_modify_empty(self):
921921
with pytest.raises(InvalidDocumentError):
922922
self.Person().modify(set__age=10)
923923

924-
self.assertDbEqual([dict(doc.to_mongo())])
924+
self._assert_db_equal([dict(doc.to_mongo())])
925925

926926
def test_modify_invalid_query(self):
927927
doc1 = self.Person(name="bob", age=10).save()
@@ -931,7 +931,7 @@ def test_modify_invalid_query(self):
931931
with pytest.raises(InvalidQueryError):
932932
doc1.modify({"id": doc2.id}, set__value=20)
933933

934-
self.assertDbEqual(docs)
934+
self._assert_db_equal(docs)
935935

936936
def test_modify_match_another_document(self):
937937
doc1 = self.Person(name="bob", age=10).save()
@@ -941,7 +941,7 @@ def test_modify_match_another_document(self):
941941
n_modified = doc1.modify({"name": doc2.name}, set__age=100)
942942
assert n_modified == 0
943943

944-
self.assertDbEqual(docs)
944+
self._assert_db_equal(docs)
945945

946946
def test_modify_not_exists(self):
947947
doc1 = self.Person(name="bob", age=10).save()
@@ -951,7 +951,7 @@ def test_modify_not_exists(self):
951951
n_modified = doc2.modify({"name": doc2.name}, set__age=100)
952952
assert n_modified == 0
953953

954-
self.assertDbEqual(docs)
954+
self._assert_db_equal(docs)
955955

956956
def test_modify_update(self):
957957
other_doc = self.Person(name="bob", age=10).save()
@@ -977,7 +977,7 @@ def test_modify_update(self):
977977
assert doc.to_json() == doc_copy.to_json()
978978
assert doc._get_changed_fields() == []
979979

980-
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
980+
self._assert_db_equal([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
981981

982982
def test_modify_with_positional_push(self):
983983
class Content(EmbeddedDocument):

tests/fields/test_embedded_document_field.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
from copy import deepcopy
2+
13
import pytest
4+
from bson import ObjectId
25

36
from mongoengine import (
47
Document,
@@ -9,6 +12,7 @@
912
InvalidQueryError,
1013
ListField,
1114
LookUpError,
15+
MapField,
1216
StringField,
1317
ValidationError,
1418
)
@@ -350,3 +354,30 @@ class Person(Document):
350354
# Test existing attribute
351355
assert Person.objects(settings__base_foo="basefoo").first().id == p.id
352356
assert Person.objects(settings__sub_foo="subfoo").first().id == p.id
357+
358+
def test_deepcopy_set__instance(self):
359+
"""Ensure that the _instance attribute on EmbeddedDocument exists after a deepcopy"""
360+
361+
class Wallet(EmbeddedDocument):
362+
money = IntField()
363+
364+
class Person(Document):
365+
wallet = EmbeddedDocumentField(Wallet)
366+
wallet_map = MapField(EmbeddedDocumentField(Wallet))
367+
368+
# Test on fresh EmbeddedDoc
369+
emb_doc = Wallet(money=1)
370+
assert emb_doc._instance is None
371+
copied_emb_doc = deepcopy(emb_doc)
372+
assert copied_emb_doc._instance is None
373+
374+
# Test on attached EmbeddedDoc
375+
doc = Person(
376+
id=ObjectId(), wallet=Wallet(money=2), wallet_map={"test": Wallet(money=2)}
377+
)
378+
assert doc.wallet._instance == doc
379+
copied_emb_doc = deepcopy(doc.wallet)
380+
assert copied_emb_doc._instance is None
381+
382+
copied_map_emb_doc = deepcopy(doc.wallet_map)
383+
assert copied_map_emb_doc["test"]._instance is None

tests/queryset/test_modify.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def setUp(self):
1919
connect(db="mongoenginetest")
2020
Doc.drop_collection()
2121

22-
def assertDbEqual(self, docs):
22+
def _assert_db_equal(self, docs):
2323
assert list(Doc._collection.find().sort("id")) == docs
2424

2525
def test_modify(self):
@@ -28,7 +28,7 @@ def test_modify(self):
2828

2929
old_doc = Doc.objects(id=1).modify(set__value=-1)
3030
assert old_doc.to_json() == doc.to_json()
31-
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
31+
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
3232

3333
def test_modify_with_new(self):
3434
Doc(id=0, value=0).save()
@@ -37,45 +37,45 @@ def test_modify_with_new(self):
3737
new_doc = Doc.objects(id=1).modify(set__value=-1, new=True)
3838
doc.value = -1
3939
assert new_doc.to_json() == doc.to_json()
40-
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
40+
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
4141

4242
def test_modify_not_existing(self):
4343
Doc(id=0, value=0).save()
4444
assert Doc.objects(id=1).modify(set__value=-1) is None
45-
self.assertDbEqual([{"_id": 0, "value": 0}])
45+
self._assert_db_equal([{"_id": 0, "value": 0}])
4646

4747
def test_modify_with_upsert(self):
4848
Doc(id=0, value=0).save()
4949
old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True)
5050
assert old_doc is None
51-
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
51+
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
5252

5353
def test_modify_with_upsert_existing(self):
5454
Doc(id=0, value=0).save()
5555
doc = Doc(id=1, value=1).save()
5656

5757
old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True)
5858
assert old_doc.to_json() == doc.to_json()
59-
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
59+
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
6060

6161
def test_modify_with_upsert_with_new(self):
6262
Doc(id=0, value=0).save()
6363
new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1)
6464
assert new_doc.to_mongo() == {"_id": 1, "value": 1}
65-
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
65+
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
6666

6767
def test_modify_with_remove(self):
6868
Doc(id=0, value=0).save()
6969
doc = Doc(id=1, value=1).save()
7070

7171
old_doc = Doc.objects(id=1).modify(remove=True)
7272
assert old_doc.to_json() == doc.to_json()
73-
self.assertDbEqual([{"_id": 0, "value": 0}])
73+
self._assert_db_equal([{"_id": 0, "value": 0}])
7474

7575
def test_find_and_modify_with_remove_not_existing(self):
7676
Doc(id=0, value=0).save()
7777
assert Doc.objects(id=1).modify(remove=True) is None
78-
self.assertDbEqual([{"_id": 0, "value": 0}])
78+
self._assert_db_equal([{"_id": 0, "value": 0}])
7979

8080
def test_modify_with_order_by(self):
8181
Doc(id=0, value=3).save()
@@ -85,7 +85,7 @@ def test_modify_with_order_by(self):
8585

8686
old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
8787
assert old_doc.to_json() == doc.to_json()
88-
self.assertDbEqual(
88+
self._assert_db_equal(
8989
[
9090
{"_id": 0, "value": 3},
9191
{"_id": 1, "value": 2},
@@ -100,7 +100,7 @@ def test_modify_with_fields(self):
100100

101101
old_doc = Doc.objects(id=1).only("id").modify(set__value=-1)
102102
assert old_doc.to_mongo() == {"_id": 1}
103-
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
103+
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
104104

105105
def test_modify_with_push(self):
106106
class BlogPost(Document):

0 commit comments

Comments
 (0)