Improve perf of Doc.save by preventing a full to_mongo() call just to get the created
variable
This commit is contained in:
parent
1907133f99
commit
9ae8fe7c2d
@ -7,6 +7,7 @@ Development
|
||||
- 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:
|
||||
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 (Generic)EmbeddedDocument subclasses fields #475
|
||||
- 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.
|
||||
"""
|
||||
if not fields:
|
||||
fields = []
|
||||
fields = fields or []
|
||||
|
||||
data = SON()
|
||||
data['_id'] = None
|
||||
|
@ -259,7 +259,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
||||
data = super(Document, self).to_mongo(*args, **kwargs)
|
||||
|
||||
# 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 self._data.get('id') is None:
|
||||
del data['_id']
|
||||
@ -365,10 +365,11 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
||||
.. versionchanged:: 0.10.7
|
||||
Add signal_kwargs argument
|
||||
"""
|
||||
signal_kwargs = signal_kwargs or {}
|
||||
|
||||
if self._meta.get('abstract'):
|
||||
raise InvalidDocumentError('Cannot save an abstract document.')
|
||||
|
||||
signal_kwargs = signal_kwargs or {}
|
||||
signals.pre_save.send(self.__class__, document=self, **signal_kwargs)
|
||||
|
||||
if validate:
|
||||
@ -377,9 +378,8 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
||||
if write_concern is None:
|
||||
write_concern = {}
|
||||
|
||||
doc = self.to_mongo()
|
||||
|
||||
created = ('_id' not in doc or self._created or force_insert)
|
||||
doc_id = self.to_mongo(fields=['id'])
|
||||
created = ('_id' not in doc_id or self._created or force_insert)
|
||||
|
||||
signals.pre_save_post_validation.send(self.__class__, document=self,
|
||||
created=created, **signal_kwargs)
|
||||
|
@ -16,7 +16,7 @@ from mongoengine.pymongo_support import list_collection_names
|
||||
from tests import fixtures
|
||||
from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
|
||||
PickleDynamicEmbedded, PickleDynamicTest)
|
||||
from tests.utils import MongoDBTestCase
|
||||
from tests.utils import MongoDBTestCase, get_as_pymongo
|
||||
|
||||
from mongoengine import *
|
||||
from mongoengine.base import get_document, _document_registry
|
||||
@ -715,39 +715,74 @@ class InstanceTest(MongoDBTestCase):
|
||||
acc1 = Account.objects.first()
|
||||
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):
|
||||
class TestDocument(Document):
|
||||
status = StringField()
|
||||
pub_date = DateTimeField()
|
||||
cleaned = BooleanField(default=False)
|
||||
|
||||
def clean(self):
|
||||
if self.status == 'draft' and self.pub_date is not None:
|
||||
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()
|
||||
self.cleaned = True
|
||||
|
||||
TestDocument.drop_collection()
|
||||
|
||||
t = TestDocument(status="draft", pub_date=datetime.now())
|
||||
|
||||
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})
|
||||
t = TestDocument(status="draft")
|
||||
|
||||
# Ensure clean=False prevent call to clean
|
||||
t = TestDocument(status="published")
|
||||
t.save(clean=False)
|
||||
|
||||
self.assertEqual(t.pub_date, None)
|
||||
self.assertEqual(t.status, "published")
|
||||
self.assertEqual(t.cleaned, False)
|
||||
|
||||
t = TestDocument(status="published")
|
||||
self.assertEqual(t.cleaned, False)
|
||||
t.save(clean=True)
|
||||
|
||||
self.assertEqual(type(t.pub_date), datetime)
|
||||
self.assertEqual(t.status, "published")
|
||||
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):
|
||||
class TestEmbeddedDocument(EmbeddedDocument):
|
||||
@ -887,19 +922,39 @@ class InstanceTest(MongoDBTestCase):
|
||||
person.save()
|
||||
|
||||
# Ensure that the object is in the database
|
||||
collection = self.db[self.Person._get_collection_name()]
|
||||
person_obj = collection.find_one({'name': 'Test User'})
|
||||
self.assertEqual(person_obj['name'], 'Test User')
|
||||
self.assertEqual(person_obj['age'], 30)
|
||||
self.assertEqual(person_obj['_id'], person.id)
|
||||
raw_doc = get_as_pymongo(person)
|
||||
self.assertEqual(
|
||||
raw_doc,
|
||||
{
|
||||
'_cls': 'Person',
|
||||
'name': 'Test User',
|
||||
'age': 30,
|
||||
'_id': person.id
|
||||
})
|
||||
|
||||
# Test skipping validation on save
|
||||
def test_save_skip_validation(self):
|
||||
class Recipient(Document):
|
||||
email = EmailField(required=True)
|
||||
|
||||
recipient = Recipient(email='not-an-email')
|
||||
self.assertRaises(ValidationError, recipient.save)
|
||||
with self.assertRaises(ValidationError):
|
||||
recipient.save()
|
||||
|
||||
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):
|
||||
class Thing(EmbeddedDocument):
|
||||
|
Loading…
x
Reference in New Issue
Block a user