Improve perf of Doc.save by preventing a full to_mongo() call just to get the created variable
				
					
				
			This commit is contained in:
		@@ -7,6 +7,7 @@ Development
 | 
				
			|||||||
- Add support for MongoDB 3.6 and Python3.7 in travis
 | 
					- Add support for MongoDB 3.6 and Python3.7 in travis
 | 
				
			||||||
- BREAKING CHANGE: Changed the custom field validator (i.e `validation` parameter of Field) so that it now requires:
 | 
					- BREAKING CHANGE: Changed the custom field validator (i.e `validation` parameter of Field) so that it now requires:
 | 
				
			||||||
    the callable to raise a ValidationError (i.o return True/False).
 | 
					    the callable to raise a ValidationError (i.o return True/False).
 | 
				
			||||||
 | 
					- Prevent an expensive call to to_mongo in Document.save() to improve performance #?
 | 
				
			||||||
- Fix querying on List(EmbeddedDocument) subclasses fields #1961 #1492
 | 
					- Fix querying on List(EmbeddedDocument) subclasses fields #1961 #1492
 | 
				
			||||||
- Fix querying on (Generic)EmbeddedDocument subclasses fields #475
 | 
					- Fix querying on (Generic)EmbeddedDocument subclasses fields #475
 | 
				
			||||||
- expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all`
 | 
					- expose `mongoengine.connection.disconnect` and `mongoengine.connection.disconnect_all`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -293,8 +293,7 @@ class BaseDocument(object):
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        Return as SON data ready for use with MongoDB.
 | 
					        Return as SON data ready for use with MongoDB.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if not fields:
 | 
					        fields = fields or []
 | 
				
			||||||
            fields = []
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data = SON()
 | 
					        data = SON()
 | 
				
			||||||
        data['_id'] = None
 | 
					        data['_id'] = None
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -259,7 +259,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
 | 
				
			|||||||
        data = super(Document, self).to_mongo(*args, **kwargs)
 | 
					        data = super(Document, self).to_mongo(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # If '_id' is None, try and set it from self._data. If that
 | 
					        # If '_id' is None, try and set it from self._data. If that
 | 
				
			||||||
        # doesn't exist either, remote '_id' from the SON completely.
 | 
					        # doesn't exist either, remove '_id' from the SON completely.
 | 
				
			||||||
        if data['_id'] is None:
 | 
					        if data['_id'] is None:
 | 
				
			||||||
            if self._data.get('id') is None:
 | 
					            if self._data.get('id') is None:
 | 
				
			||||||
                del data['_id']
 | 
					                del data['_id']
 | 
				
			||||||
@@ -365,10 +365,11 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
 | 
				
			|||||||
        .. versionchanged:: 0.10.7
 | 
					        .. versionchanged:: 0.10.7
 | 
				
			||||||
            Add signal_kwargs argument
 | 
					            Add signal_kwargs argument
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 | 
					        signal_kwargs = signal_kwargs or {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self._meta.get('abstract'):
 | 
					        if self._meta.get('abstract'):
 | 
				
			||||||
            raise InvalidDocumentError('Cannot save an abstract document.')
 | 
					            raise InvalidDocumentError('Cannot save an abstract document.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        signal_kwargs = signal_kwargs or {}
 | 
					 | 
				
			||||||
        signals.pre_save.send(self.__class__, document=self, **signal_kwargs)
 | 
					        signals.pre_save.send(self.__class__, document=self, **signal_kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if validate:
 | 
					        if validate:
 | 
				
			||||||
@@ -377,9 +378,8 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
 | 
				
			|||||||
        if write_concern is None:
 | 
					        if write_concern is None:
 | 
				
			||||||
            write_concern = {}
 | 
					            write_concern = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        doc = self.to_mongo()
 | 
					        doc_id = self.to_mongo(fields=['id'])
 | 
				
			||||||
 | 
					        created = ('_id' not in doc_id or self._created or force_insert)
 | 
				
			||||||
        created = ('_id' not in doc or self._created or force_insert)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        signals.pre_save_post_validation.send(self.__class__, document=self,
 | 
					        signals.pre_save_post_validation.send(self.__class__, document=self,
 | 
				
			||||||
                                              created=created, **signal_kwargs)
 | 
					                                              created=created, **signal_kwargs)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ from mongoengine.pymongo_support import list_collection_names
 | 
				
			|||||||
from tests import fixtures
 | 
					from tests import fixtures
 | 
				
			||||||
from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
 | 
					from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
 | 
				
			||||||
                            PickleDynamicEmbedded, PickleDynamicTest)
 | 
					                            PickleDynamicEmbedded, PickleDynamicTest)
 | 
				
			||||||
from tests.utils import MongoDBTestCase
 | 
					from tests.utils import MongoDBTestCase, get_as_pymongo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from mongoengine import *
 | 
					from mongoengine import *
 | 
				
			||||||
from mongoengine.base import get_document, _document_registry
 | 
					from mongoengine.base import get_document, _document_registry
 | 
				
			||||||
@@ -715,39 +715,74 @@ class InstanceTest(MongoDBTestCase):
 | 
				
			|||||||
        acc1 = Account.objects.first()
 | 
					        acc1 = Account.objects.first()
 | 
				
			||||||
        self.assertHasInstance(acc1._data["emails"][0], acc1)
 | 
					        self.assertHasInstance(acc1._data["emails"][0], acc1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_save_checks_that_clean_is_called(self):
 | 
				
			||||||
 | 
					        class CustomError(Exception):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class TestDocument(Document):
 | 
				
			||||||
 | 
					            def clean(self):
 | 
				
			||||||
 | 
					                raise CustomError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with self.assertRaises(CustomError):
 | 
				
			||||||
 | 
					            TestDocument().save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        TestDocument().save(clean=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_save_signal_pre_save_post_validation_makes_change_to_doc(self):
 | 
				
			||||||
 | 
					        class BlogPost(Document):
 | 
				
			||||||
 | 
					            content = StringField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @classmethod
 | 
				
			||||||
 | 
					            def pre_save_post_validation(cls, sender, document, **kwargs):
 | 
				
			||||||
 | 
					                document.content = 'checked'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        signals.pre_save_post_validation.connect(BlogPost.pre_save_post_validation, sender=BlogPost)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        BlogPost.drop_collection()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        post = BlogPost(content='unchecked').save()
 | 
				
			||||||
 | 
					        self.assertEqual(post.content, 'checked')
 | 
				
			||||||
 | 
					        # Make sure pre_save_post_validation changes makes it to the db
 | 
				
			||||||
 | 
					        raw_doc = get_as_pymongo(post)
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            raw_doc,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                'content': 'checked',
 | 
				
			||||||
 | 
					                '_id': post.id
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_document_clean(self):
 | 
					    def test_document_clean(self):
 | 
				
			||||||
        class TestDocument(Document):
 | 
					        class TestDocument(Document):
 | 
				
			||||||
            status = StringField()
 | 
					            status = StringField()
 | 
				
			||||||
            pub_date = DateTimeField()
 | 
					            cleaned = BooleanField(default=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            def clean(self):
 | 
					            def clean(self):
 | 
				
			||||||
                if self.status == 'draft' and self.pub_date is not None:
 | 
					                self.cleaned = True
 | 
				
			||||||
                    msg = 'Draft entries may not have a publication date.'
 | 
					 | 
				
			||||||
                    raise ValidationError(msg)
 | 
					 | 
				
			||||||
                # Set the pub_date for published items if not set.
 | 
					 | 
				
			||||||
                if self.status == 'published' and self.pub_date is None:
 | 
					 | 
				
			||||||
                    self.pub_date = datetime.now()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        TestDocument.drop_collection()
 | 
					        TestDocument.drop_collection()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        t = TestDocument(status="draft", pub_date=datetime.now())
 | 
					        t = TestDocument(status="draft")
 | 
				
			||||||
 | 
					 | 
				
			||||||
        with self.assertRaises(ValidationError) as cm:
 | 
					 | 
				
			||||||
            t.save()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        expected_msg = "Draft entries may not have a publication date."
 | 
					 | 
				
			||||||
        self.assertIn(expected_msg, cm.exception.message)
 | 
					 | 
				
			||||||
        self.assertEqual(cm.exception.to_dict(), {'__all__': expected_msg})
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Ensure clean=False prevent call to clean
 | 
				
			||||||
        t = TestDocument(status="published")
 | 
					        t = TestDocument(status="published")
 | 
				
			||||||
        t.save(clean=False)
 | 
					        t.save(clean=False)
 | 
				
			||||||
 | 
					        self.assertEqual(t.status, "published")
 | 
				
			||||||
        self.assertEqual(t.pub_date, None)
 | 
					        self.assertEqual(t.cleaned, False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        t = TestDocument(status="published")
 | 
					        t = TestDocument(status="published")
 | 
				
			||||||
 | 
					        self.assertEqual(t.cleaned, False)
 | 
				
			||||||
        t.save(clean=True)
 | 
					        t.save(clean=True)
 | 
				
			||||||
 | 
					        self.assertEqual(t.status, "published")
 | 
				
			||||||
        self.assertEqual(type(t.pub_date), datetime)
 | 
					        self.assertEqual(t.cleaned, True)
 | 
				
			||||||
 | 
					        raw_doc = get_as_pymongo(t)
 | 
				
			||||||
 | 
					        # Make sure clean changes makes it to the db
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            raw_doc,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                'status': 'published',
 | 
				
			||||||
 | 
					                'cleaned': True,
 | 
				
			||||||
 | 
					                '_id': t.id
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_document_embedded_clean(self):
 | 
					    def test_document_embedded_clean(self):
 | 
				
			||||||
        class TestEmbeddedDocument(EmbeddedDocument):
 | 
					        class TestEmbeddedDocument(EmbeddedDocument):
 | 
				
			||||||
@@ -887,19 +922,39 @@ class InstanceTest(MongoDBTestCase):
 | 
				
			|||||||
        person.save()
 | 
					        person.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Ensure that the object is in the database
 | 
					        # Ensure that the object is in the database
 | 
				
			||||||
        collection = self.db[self.Person._get_collection_name()]
 | 
					        raw_doc = get_as_pymongo(person)
 | 
				
			||||||
        person_obj = collection.find_one({'name': 'Test User'})
 | 
					        self.assertEqual(
 | 
				
			||||||
        self.assertEqual(person_obj['name'], 'Test User')
 | 
					            raw_doc,
 | 
				
			||||||
        self.assertEqual(person_obj['age'], 30)
 | 
					            {
 | 
				
			||||||
        self.assertEqual(person_obj['_id'], person.id)
 | 
					                '_cls': 'Person',
 | 
				
			||||||
 | 
					                'name': 'Test User',
 | 
				
			||||||
 | 
					                'age': 30,
 | 
				
			||||||
 | 
					                '_id': person.id
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Test skipping validation on save
 | 
					    def test_save_skip_validation(self):
 | 
				
			||||||
        class Recipient(Document):
 | 
					        class Recipient(Document):
 | 
				
			||||||
            email = EmailField(required=True)
 | 
					            email = EmailField(required=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        recipient = Recipient(email='not-an-email')
 | 
					        recipient = Recipient(email='not-an-email')
 | 
				
			||||||
        self.assertRaises(ValidationError, recipient.save)
 | 
					        with self.assertRaises(ValidationError):
 | 
				
			||||||
 | 
					            recipient.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        recipient.save(validate=False)
 | 
					        recipient.save(validate=False)
 | 
				
			||||||
 | 
					        raw_doc = get_as_pymongo(recipient)
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            raw_doc,
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                'email': 'not-an-email',
 | 
				
			||||||
 | 
					                '_id': recipient.id
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_save_with_bad_id(self):
 | 
				
			||||||
 | 
					        class Clown(Document):
 | 
				
			||||||
 | 
					            id = IntField(primary_key=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with self.assertRaises(ValidationError):
 | 
				
			||||||
 | 
					            Clown(id="not_an_int").save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_save_to_a_value_that_equates_to_false(self):
 | 
					    def test_save_to_a_value_that_equates_to_false(self):
 | 
				
			||||||
        class Thing(EmbeddedDocument):
 | 
					        class Thing(EmbeddedDocument):
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user