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