Merge pull request #2545 from bagerard/fix_embedded_instance_deepcopy
Fix embedded instance deepcopy
This commit is contained in:
commit
bb9ba73a7b
1
AUTHORS
1
AUTHORS
@ -260,3 +260,4 @@ that much better:
|
|||||||
* Stankiewicz Mateusz (https://github.com/mas15)
|
* Stankiewicz Mateusz (https://github.com/mas15)
|
||||||
* Felix Schultheiß (https://github.com/felix-smashdocs)
|
* Felix Schultheiß (https://github.com/felix-smashdocs)
|
||||||
* Jan Stein (https://github.com/janste63)
|
* Jan Stein (https://github.com/janste63)
|
||||||
|
* Timothé Perez (https://github.com/AchilleAsh)
|
||||||
|
@ -8,6 +8,7 @@ Development
|
|||||||
===========
|
===========
|
||||||
- (Fill this out as you fix issues and develop your features).
|
- (Fill this out as you fix issues and develop your features).
|
||||||
- EnumField improvements: now `choices` limits the values of an enum to allow
|
- EnumField improvements: now `choices` limits the values of an enum to allow
|
||||||
|
- Fix deepcopy of EmbeddedDocument #2202
|
||||||
|
|
||||||
Changes in 0.23.1
|
Changes in 0.23.1
|
||||||
===========
|
===========
|
||||||
|
@ -99,6 +99,15 @@ class EmbeddedDocument(BaseDocument, metaclass=DocumentMetaclass):
|
|||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self.__eq__(other)
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
data = super().__getstate__()
|
||||||
|
data["_instance"] = None
|
||||||
|
return data
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
super().__setstate__(state)
|
||||||
|
self._instance = state["_instance"]
|
||||||
|
|
||||||
def to_mongo(self, *args, **kwargs):
|
def to_mongo(self, *args, **kwargs):
|
||||||
data = super().to_mongo(*args, **kwargs)
|
data = super().to_mongo(*args, **kwargs)
|
||||||
|
|
||||||
@ -126,7 +135,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
|
|||||||
create a specialised version of the document that will be stored in the
|
create a specialised version of the document that will be stored in the
|
||||||
same collection. To facilitate this behaviour a `_cls`
|
same collection. To facilitate this behaviour a `_cls`
|
||||||
field is added to documents (hidden though the MongoEngine interface).
|
field is added to documents (hidden though the MongoEngine interface).
|
||||||
To enable this behaviourset :attr:`allow_inheritance` to ``True`` in the
|
To enable this behaviour set :attr:`allow_inheritance` to ``True`` in the
|
||||||
:attr:`meta` dictionary.
|
:attr:`meta` dictionary.
|
||||||
|
|
||||||
A :class:`~mongoengine.Document` may use a **Capped Collection** by
|
A :class:`~mongoengine.Document` may use a **Capped Collection** by
|
||||||
|
@ -65,12 +65,12 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
for collection in list_collection_names(self.db):
|
for collection in list_collection_names(self.db):
|
||||||
self.db.drop_collection(collection)
|
self.db.drop_collection(collection)
|
||||||
|
|
||||||
def assertDbEqual(self, docs):
|
def _assert_db_equal(self, docs):
|
||||||
assert list(self.Person._get_collection().find().sort("id")) == sorted(
|
assert list(self.Person._get_collection().find().sort("id")) == sorted(
|
||||||
docs, key=lambda doc: doc["_id"]
|
docs, key=lambda doc: doc["_id"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def assertHasInstance(self, field, instance):
|
def _assert_has_instance(self, field, instance):
|
||||||
assert hasattr(field, "_instance")
|
assert hasattr(field, "_instance")
|
||||||
assert field._instance is not None
|
assert field._instance is not None
|
||||||
if isinstance(field._instance, weakref.ProxyType):
|
if isinstance(field._instance, weakref.ProxyType):
|
||||||
@ -740,11 +740,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
Doc.drop_collection()
|
Doc.drop_collection()
|
||||||
|
|
||||||
doc = Doc(embedded_field=Embedded(string="Hi"))
|
doc = Doc(embedded_field=Embedded(string="Hi"))
|
||||||
self.assertHasInstance(doc.embedded_field, doc)
|
self._assert_has_instance(doc.embedded_field, doc)
|
||||||
|
|
||||||
doc.save()
|
doc.save()
|
||||||
doc = Doc.objects.get()
|
doc = Doc.objects.get()
|
||||||
self.assertHasInstance(doc.embedded_field, doc)
|
self._assert_has_instance(doc.embedded_field, doc)
|
||||||
|
|
||||||
def test_embedded_document_complex_instance(self):
|
def test_embedded_document_complex_instance(self):
|
||||||
"""Ensure that embedded documents in complex fields can reference
|
"""Ensure that embedded documents in complex fields can reference
|
||||||
@ -759,11 +759,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
|
|
||||||
Doc.drop_collection()
|
Doc.drop_collection()
|
||||||
doc = Doc(embedded_field=[Embedded(string="Hi")])
|
doc = Doc(embedded_field=[Embedded(string="Hi")])
|
||||||
self.assertHasInstance(doc.embedded_field[0], doc)
|
self._assert_has_instance(doc.embedded_field[0], doc)
|
||||||
|
|
||||||
doc.save()
|
doc.save()
|
||||||
doc = Doc.objects.get()
|
doc = Doc.objects.get()
|
||||||
self.assertHasInstance(doc.embedded_field[0], doc)
|
self._assert_has_instance(doc.embedded_field[0], doc)
|
||||||
|
|
||||||
def test_embedded_document_complex_instance_no_use_db_field(self):
|
def test_embedded_document_complex_instance_no_use_db_field(self):
|
||||||
"""Ensure that use_db_field is propagated to list of Emb Docs."""
|
"""Ensure that use_db_field is propagated to list of Emb Docs."""
|
||||||
@ -792,11 +792,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
|
|
||||||
acc = Account()
|
acc = Account()
|
||||||
acc.email = Email(email="test@example.com")
|
acc.email = Email(email="test@example.com")
|
||||||
self.assertHasInstance(acc._data["email"], acc)
|
self._assert_has_instance(acc._data["email"], acc)
|
||||||
acc.save()
|
acc.save()
|
||||||
|
|
||||||
acc1 = Account.objects.first()
|
acc1 = Account.objects.first()
|
||||||
self.assertHasInstance(acc1._data["email"], acc1)
|
self._assert_has_instance(acc1._data["email"], acc1)
|
||||||
|
|
||||||
def test_instance_is_set_on_setattr_on_embedded_document_list(self):
|
def test_instance_is_set_on_setattr_on_embedded_document_list(self):
|
||||||
class Email(EmbeddedDocument):
|
class Email(EmbeddedDocument):
|
||||||
@ -808,11 +808,11 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
Account.drop_collection()
|
Account.drop_collection()
|
||||||
acc = Account()
|
acc = Account()
|
||||||
acc.emails = [Email(email="test@example.com")]
|
acc.emails = [Email(email="test@example.com")]
|
||||||
self.assertHasInstance(acc._data["emails"][0], acc)
|
self._assert_has_instance(acc._data["emails"][0], acc)
|
||||||
acc.save()
|
acc.save()
|
||||||
|
|
||||||
acc1 = Account.objects.first()
|
acc1 = Account.objects.first()
|
||||||
self.assertHasInstance(acc1._data["emails"][0], acc1)
|
self._assert_has_instance(acc1._data["emails"][0], acc1)
|
||||||
|
|
||||||
def test_save_checks_that_clean_is_called(self):
|
def test_save_checks_that_clean_is_called(self):
|
||||||
class CustomError(Exception):
|
class CustomError(Exception):
|
||||||
@ -921,7 +921,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
with pytest.raises(InvalidDocumentError):
|
with pytest.raises(InvalidDocumentError):
|
||||||
self.Person().modify(set__age=10)
|
self.Person().modify(set__age=10)
|
||||||
|
|
||||||
self.assertDbEqual([dict(doc.to_mongo())])
|
self._assert_db_equal([dict(doc.to_mongo())])
|
||||||
|
|
||||||
def test_modify_invalid_query(self):
|
def test_modify_invalid_query(self):
|
||||||
doc1 = self.Person(name="bob", age=10).save()
|
doc1 = self.Person(name="bob", age=10).save()
|
||||||
@ -931,7 +931,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
with pytest.raises(InvalidQueryError):
|
with pytest.raises(InvalidQueryError):
|
||||||
doc1.modify({"id": doc2.id}, set__value=20)
|
doc1.modify({"id": doc2.id}, set__value=20)
|
||||||
|
|
||||||
self.assertDbEqual(docs)
|
self._assert_db_equal(docs)
|
||||||
|
|
||||||
def test_modify_match_another_document(self):
|
def test_modify_match_another_document(self):
|
||||||
doc1 = self.Person(name="bob", age=10).save()
|
doc1 = self.Person(name="bob", age=10).save()
|
||||||
@ -941,7 +941,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
n_modified = doc1.modify({"name": doc2.name}, set__age=100)
|
n_modified = doc1.modify({"name": doc2.name}, set__age=100)
|
||||||
assert n_modified == 0
|
assert n_modified == 0
|
||||||
|
|
||||||
self.assertDbEqual(docs)
|
self._assert_db_equal(docs)
|
||||||
|
|
||||||
def test_modify_not_exists(self):
|
def test_modify_not_exists(self):
|
||||||
doc1 = self.Person(name="bob", age=10).save()
|
doc1 = self.Person(name="bob", age=10).save()
|
||||||
@ -951,7 +951,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
n_modified = doc2.modify({"name": doc2.name}, set__age=100)
|
n_modified = doc2.modify({"name": doc2.name}, set__age=100)
|
||||||
assert n_modified == 0
|
assert n_modified == 0
|
||||||
|
|
||||||
self.assertDbEqual(docs)
|
self._assert_db_equal(docs)
|
||||||
|
|
||||||
def test_modify_update(self):
|
def test_modify_update(self):
|
||||||
other_doc = self.Person(name="bob", age=10).save()
|
other_doc = self.Person(name="bob", age=10).save()
|
||||||
@ -977,7 +977,7 @@ class TestDocumentInstance(MongoDBTestCase):
|
|||||||
assert doc.to_json() == doc_copy.to_json()
|
assert doc.to_json() == doc_copy.to_json()
|
||||||
assert doc._get_changed_fields() == []
|
assert doc._get_changed_fields() == []
|
||||||
|
|
||||||
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
|
self._assert_db_equal([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
|
||||||
|
|
||||||
def test_modify_with_positional_push(self):
|
def test_modify_with_positional_push(self):
|
||||||
class Content(EmbeddedDocument):
|
class Content(EmbeddedDocument):
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from bson import ObjectId
|
||||||
|
|
||||||
from mongoengine import (
|
from mongoengine import (
|
||||||
Document,
|
Document,
|
||||||
@ -9,6 +12,7 @@ from mongoengine import (
|
|||||||
InvalidQueryError,
|
InvalidQueryError,
|
||||||
ListField,
|
ListField,
|
||||||
LookUpError,
|
LookUpError,
|
||||||
|
MapField,
|
||||||
StringField,
|
StringField,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
@ -350,3 +354,30 @@ class TestGenericEmbeddedDocumentField(MongoDBTestCase):
|
|||||||
# Test existing attribute
|
# Test existing attribute
|
||||||
assert Person.objects(settings__base_foo="basefoo").first().id == p.id
|
assert Person.objects(settings__base_foo="basefoo").first().id == p.id
|
||||||
assert Person.objects(settings__sub_foo="subfoo").first().id == p.id
|
assert Person.objects(settings__sub_foo="subfoo").first().id == p.id
|
||||||
|
|
||||||
|
def test_deepcopy_set__instance(self):
|
||||||
|
"""Ensure that the _instance attribute on EmbeddedDocument exists after a deepcopy"""
|
||||||
|
|
||||||
|
class Wallet(EmbeddedDocument):
|
||||||
|
money = IntField()
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
wallet = EmbeddedDocumentField(Wallet)
|
||||||
|
wallet_map = MapField(EmbeddedDocumentField(Wallet))
|
||||||
|
|
||||||
|
# Test on fresh EmbeddedDoc
|
||||||
|
emb_doc = Wallet(money=1)
|
||||||
|
assert emb_doc._instance is None
|
||||||
|
copied_emb_doc = deepcopy(emb_doc)
|
||||||
|
assert copied_emb_doc._instance is None
|
||||||
|
|
||||||
|
# Test on attached EmbeddedDoc
|
||||||
|
doc = Person(
|
||||||
|
id=ObjectId(), wallet=Wallet(money=2), wallet_map={"test": Wallet(money=2)}
|
||||||
|
)
|
||||||
|
assert doc.wallet._instance == doc
|
||||||
|
copied_emb_doc = deepcopy(doc.wallet)
|
||||||
|
assert copied_emb_doc._instance is None
|
||||||
|
|
||||||
|
copied_map_emb_doc = deepcopy(doc.wallet_map)
|
||||||
|
assert copied_map_emb_doc["test"]._instance is None
|
||||||
|
@ -19,7 +19,7 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
connect(db="mongoenginetest")
|
connect(db="mongoenginetest")
|
||||||
Doc.drop_collection()
|
Doc.drop_collection()
|
||||||
|
|
||||||
def assertDbEqual(self, docs):
|
def _assert_db_equal(self, docs):
|
||||||
assert list(Doc._collection.find().sort("id")) == docs
|
assert list(Doc._collection.find().sort("id")) == docs
|
||||||
|
|
||||||
def test_modify(self):
|
def test_modify(self):
|
||||||
@ -28,7 +28,7 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
|
|
||||||
old_doc = Doc.objects(id=1).modify(set__value=-1)
|
old_doc = Doc.objects(id=1).modify(set__value=-1)
|
||||||
assert old_doc.to_json() == doc.to_json()
|
assert old_doc.to_json() == doc.to_json()
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||||
|
|
||||||
def test_modify_with_new(self):
|
def test_modify_with_new(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
@ -37,18 +37,18 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
new_doc = Doc.objects(id=1).modify(set__value=-1, new=True)
|
new_doc = Doc.objects(id=1).modify(set__value=-1, new=True)
|
||||||
doc.value = -1
|
doc.value = -1
|
||||||
assert new_doc.to_json() == doc.to_json()
|
assert new_doc.to_json() == doc.to_json()
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||||
|
|
||||||
def test_modify_not_existing(self):
|
def test_modify_not_existing(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
assert Doc.objects(id=1).modify(set__value=-1) is None
|
assert Doc.objects(id=1).modify(set__value=-1) is None
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}])
|
self._assert_db_equal([{"_id": 0, "value": 0}])
|
||||||
|
|
||||||
def test_modify_with_upsert(self):
|
def test_modify_with_upsert(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True)
|
old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True)
|
||||||
assert old_doc is None
|
assert old_doc is None
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
||||||
|
|
||||||
def test_modify_with_upsert_existing(self):
|
def test_modify_with_upsert_existing(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
@ -56,13 +56,13 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
|
|
||||||
old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True)
|
old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True)
|
||||||
assert old_doc.to_json() == doc.to_json()
|
assert old_doc.to_json() == doc.to_json()
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||||
|
|
||||||
def test_modify_with_upsert_with_new(self):
|
def test_modify_with_upsert_with_new(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1)
|
new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1)
|
||||||
assert new_doc.to_mongo() == {"_id": 1, "value": 1}
|
assert new_doc.to_mongo() == {"_id": 1, "value": 1}
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
|
||||||
|
|
||||||
def test_modify_with_remove(self):
|
def test_modify_with_remove(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
@ -70,12 +70,12 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
|
|
||||||
old_doc = Doc.objects(id=1).modify(remove=True)
|
old_doc = Doc.objects(id=1).modify(remove=True)
|
||||||
assert old_doc.to_json() == doc.to_json()
|
assert old_doc.to_json() == doc.to_json()
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}])
|
self._assert_db_equal([{"_id": 0, "value": 0}])
|
||||||
|
|
||||||
def test_find_and_modify_with_remove_not_existing(self):
|
def test_find_and_modify_with_remove_not_existing(self):
|
||||||
Doc(id=0, value=0).save()
|
Doc(id=0, value=0).save()
|
||||||
assert Doc.objects(id=1).modify(remove=True) is None
|
assert Doc.objects(id=1).modify(remove=True) is None
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}])
|
self._assert_db_equal([{"_id": 0, "value": 0}])
|
||||||
|
|
||||||
def test_modify_with_order_by(self):
|
def test_modify_with_order_by(self):
|
||||||
Doc(id=0, value=3).save()
|
Doc(id=0, value=3).save()
|
||||||
@ -85,7 +85,7 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
|
|
||||||
old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
|
old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
|
||||||
assert old_doc.to_json() == doc.to_json()
|
assert old_doc.to_json() == doc.to_json()
|
||||||
self.assertDbEqual(
|
self._assert_db_equal(
|
||||||
[
|
[
|
||||||
{"_id": 0, "value": 3},
|
{"_id": 0, "value": 3},
|
||||||
{"_id": 1, "value": 2},
|
{"_id": 1, "value": 2},
|
||||||
@ -100,7 +100,7 @@ class TestFindAndModify(unittest.TestCase):
|
|||||||
|
|
||||||
old_doc = Doc.objects(id=1).only("id").modify(set__value=-1)
|
old_doc = Doc.objects(id=1).only("id").modify(set__value=-1)
|
||||||
assert old_doc.to_mongo() == {"_id": 1}
|
assert old_doc.to_mongo() == {"_id": 1}
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
self._assert_db_equal([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||||
|
|
||||||
def test_modify_with_push(self):
|
def test_modify_with_push(self):
|
||||||
class BlogPost(Document):
|
class BlogPost(Document):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user