From 437b11af9ac3f2a160868265337a22eb6fcb6ff5 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Mon, 10 Jul 2017 16:43:24 -0400 Subject: [PATCH 01/85] docs: use explicit register_delete_rule example The previous example of creating bi-directional delete rules was vague since the example defined only one class and the relationship between "Foo" and "Bar" wasn't clear. I added a more explicit example where the relationship between the two classes is explicit. --- mongoengine/fields.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 0d402712..0029d68b 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -971,11 +971,13 @@ class ReferenceField(BaseField): .. code-block:: python - class Bar(Document): - content = StringField() - foo = ReferenceField('Foo') + class Org(Document): + owner = ReferenceField('User') - Foo.register_delete_rule(Bar, 'foo', NULLIFY) + class User(Document): + org = ReferenceField('Org', reverse_delete_rule=CASCADE) + + User.register_delete_rule(Org, 'owner', DENY) .. versionchanged:: 0.5 added `reverse_delete_rule` """ From 2a795e91381053294cc9bf312f785b5ed9f1ab49 Mon Sep 17 00:00:00 2001 From: Ali Date: Fri, 4 Aug 2017 11:31:29 +0100 Subject: [PATCH 02/85] QuerySet limit function now returns all docs in cursor when 0 is passed --- docs/changelog.rst | 1 + mongoengine/queryset/base.py | 7 ++++--- tests/queryset/queryset.py | 5 +++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index c60bbf09..aa8584a7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog Development =========== +- QuerySet limit function behaviour: Passing 0 as parameter will return all the documents in the cursor #1611 - (Fill this out as you fix issues and develop your features). Changes in 0.14.0 diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index 6f9c372c..be4b9d66 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -384,7 +384,7 @@ class BaseQuerySet(object): :meth:`skip` that has been applied to this cursor into account when getting the count """ - if self._limit == 0 and with_limit_and_skip or self._none: + if self._limit == 0 and with_limit_and_skip is False or self._none: return 0 return self._cursor.count(with_limit_and_skip=with_limit_and_skip) @@ -759,10 +759,11 @@ class BaseQuerySet(object): """Limit the number of returned documents to `n`. This may also be achieved using array-slicing syntax (e.g. ``User.objects[:5]``). - :param n: the maximum number of objects to return + :param n: the maximum number of objects to return if n is greater than 0. + When 0 is passed, returns all the documents in the cursor """ queryset = self.clone() - queryset._limit = n if n != 0 else 1 + queryset._limit = n # If a cursor object has already been created, apply the limit to it. if queryset._cursor_obj: diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index c78ed985..ac545629 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -124,6 +124,11 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(len(people2), 1) self.assertEqual(people2[0], user_a) + # Test limit with 0 as parameter + people = self.Person.objects.limit(0) + self.assertEqual(people.count(with_limit_and_skip=True), 2) + self.assertEqual(len(people), 2) + # Test chaining of only after limit person = self.Person.objects().limit(1).only('name').first() self.assertEqual(person, user_a) From ee5686e91ab9dec45c0d98d22d20c17864f02e0f Mon Sep 17 00:00:00 2001 From: Ali Turki Date: Mon, 7 Aug 2017 11:12:15 +0100 Subject: [PATCH 03/85] test case for #1602 --- tests/queryset/queryset.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index ac545629..57126ac0 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -5133,6 +5133,16 @@ class QuerySetTest(unittest.TestCase): # in a way we'd expect) should raise a TypeError, too self.assertRaises(TypeError, BlogPost.objects(authors__in=author).count) + def test_create_count(self): + self.Person.drop_collection() + self.Person.objects.create(name="Foo") + self.Person.objects.create(name="Bar") + self.Person.objects.create(name="Baz") + self.assertEqual(self.Person.objects.count(with_limit_and_skip=True), 3) + + newPerson = self.Person.objects.create(name="Foo_1") + self.assertEqual(self.Person.objects.count(with_limit_and_skip=True), 4) + if __name__ == '__main__': unittest.main() From c4de879b207e4e7087b099459ff801b8897620f1 Mon Sep 17 00:00:00 2001 From: Paulo Matos Date: Fri, 11 Aug 2017 09:09:33 +0200 Subject: [PATCH 04/85] Clarify comment in validation example --- docs/guide/document-instances.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guide/document-instances.rst b/docs/guide/document-instances.rst index 0e9fcef6..64f17c08 100644 --- a/docs/guide/document-instances.rst +++ b/docs/guide/document-instances.rst @@ -57,7 +57,8 @@ document values for example:: def clean(self): """Ensures that only published essays have a `pub_date` and - automatically sets the pub_date if published and not set""" + automatically sets `pub_date` if essay is published and `pub_date` + is not set""" if self.status == 'Draft' and self.pub_date is not None: msg = 'Draft entries should not have a publication date.' raise ValidationError(msg) From 2f075be6f8857d297d9e939d9150417158d93d5a Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Mon, 2 Oct 2017 22:46:27 +0800 Subject: [PATCH 05/85] parse read_preference from conn_host #1665 --- mongoengine/connection.py | 11 +++++++++++ tests/test_connection.py | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/mongoengine/connection.py b/mongoengine/connection.py index 34ff4dc3..ef815343 100644 --- a/mongoengine/connection.py +++ b/mongoengine/connection.py @@ -103,6 +103,17 @@ def register_connection(alias, name=None, host=None, port=None, conn_settings['authentication_source'] = uri_options['authsource'] if 'authmechanism' in uri_options: conn_settings['authentication_mechanism'] = uri_options['authmechanism'] + if 'readpreference' in uri_options: + read_preferences = (ReadPreference.NEAREST, + ReadPreference.PRIMARY, + ReadPreference.PRIMARY_PREFERRED, + ReadPreference.SECONDARY, + ReadPreference.SECONDARY_PREFERRED) + read_pf_mode = uri_options['readpreference'] + for preference in read_preferences: + if preference.mode == read_pf_mode: + conn_settings['read_preference'] = preference + break else: resolved_hosts.append(entity) conn_settings['host'] = resolved_hosts diff --git a/tests/test_connection.py b/tests/test_connection.py index cdcf1377..f0c272e4 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -364,6 +364,11 @@ class ConnectionTest(unittest.TestCase): date_doc = DateDoc.objects.first() self.assertEqual(d, date_doc.the_date) + def test_read_preference_from_parse(self): + from pymongo import ReadPreference + conn = connect(host="mongodb://a1.vpc,a2.vpc,a3.vpc/prod?readPreference=secondaryPreferred") + self.assertEqual(conn.read_preference, ReadPreference.SECONDARY_PREFERRED) + def test_multiple_connection_settings(self): connect('mongoenginetest', alias='t1', host="localhost") From 416486c370cbe565128b04e9fadcc65e643d12d9 Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Mon, 2 Oct 2017 23:13:25 +0800 Subject: [PATCH 06/85] use read_preference only pymongo3.x #1665 --- mongoengine/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongoengine/connection.py b/mongoengine/connection.py index ef815343..419af6bc 100644 --- a/mongoengine/connection.py +++ b/mongoengine/connection.py @@ -103,7 +103,7 @@ def register_connection(alias, name=None, host=None, port=None, conn_settings['authentication_source'] = uri_options['authsource'] if 'authmechanism' in uri_options: conn_settings['authentication_mechanism'] = uri_options['authmechanism'] - if 'readpreference' in uri_options: + if IS_PYMONGO_3 and 'readpreference' in uri_options: read_preferences = (ReadPreference.NEAREST, ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, From 5c4ce8754e29d435a621a1780ef96c3a6bb60ebf Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Mon, 2 Oct 2017 23:15:37 +0800 Subject: [PATCH 07/85] run tests only pymongo3 #1565 --- tests/test_connection.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_connection.py b/tests/test_connection.py index f0c272e4..f58b1a3e 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -365,9 +365,10 @@ class ConnectionTest(unittest.TestCase): self.assertEqual(d, date_doc.the_date) def test_read_preference_from_parse(self): - from pymongo import ReadPreference - conn = connect(host="mongodb://a1.vpc,a2.vpc,a3.vpc/prod?readPreference=secondaryPreferred") - self.assertEqual(conn.read_preference, ReadPreference.SECONDARY_PREFERRED) + if IS_PYMONGO_3: + from pymongo import ReadPreference + conn = connect(host="mongodb://a1.vpc,a2.vpc,a3.vpc/prod?readPreference=secondaryPreferred") + self.assertEqual(conn.read_preference, ReadPreference.SECONDARY_PREFERRED) def test_multiple_connection_settings(self): connect('mongoenginetest', alias='t1', host="localhost") From 6e2db1ced6a8621e35cf38f27993751fe5ea1e6b Mon Sep 17 00:00:00 2001 From: Erdenezul Date: Tue, 3 Oct 2017 09:23:17 +0800 Subject: [PATCH 08/85] read_preference from parse_uri only PYMONGO_3 #1665 --- mongoengine/connection.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/mongoengine/connection.py b/mongoengine/connection.py index 419af6bc..feba0b58 100644 --- a/mongoengine/connection.py +++ b/mongoengine/connection.py @@ -104,14 +104,15 @@ def register_connection(alias, name=None, host=None, port=None, if 'authmechanism' in uri_options: conn_settings['authentication_mechanism'] = uri_options['authmechanism'] if IS_PYMONGO_3 and 'readpreference' in uri_options: - read_preferences = (ReadPreference.NEAREST, - ReadPreference.PRIMARY, - ReadPreference.PRIMARY_PREFERRED, - ReadPreference.SECONDARY, - ReadPreference.SECONDARY_PREFERRED) - read_pf_mode = uri_options['readpreference'] + read_preferences = ( + ReadPreference.NEAREST, + ReadPreference.PRIMARY, + ReadPreference.PRIMARY_PREFERRED, + ReadPreference.SECONDARY, + ReadPreference.SECONDARY_PREFERRED) + read_pf_mode = uri_options['readpreference'].lower() for preference in read_preferences: - if preference.mode == read_pf_mode: + if preference.name.lower() == read_pf_mode: conn_settings['read_preference'] = preference break else: From 15451ff42b7cc7b5a9a76190b60e96d062ecff81 Mon Sep 17 00:00:00 2001 From: Mandeep Singh Date: Tue, 17 Oct 2017 00:11:47 +0530 Subject: [PATCH 09/85] return instead of raising StopIteration --- mongoengine/queryset/base.py | 2 +- mongoengine/queryset/queryset.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index 6f9c372c..2dcfdbea 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -1461,7 +1461,7 @@ class BaseQuerySet(object): """Wrap the result in a :class:`~mongoengine.Document` object. """ if self._limit == 0 or self._none: - raise StopIteration + return raw_doc = self._cursor.next() diff --git a/mongoengine/queryset/queryset.py b/mongoengine/queryset/queryset.py index cf913b01..1aadfb76 100644 --- a/mongoengine/queryset/queryset.py +++ b/mongoengine/queryset/queryset.py @@ -89,10 +89,10 @@ class QuerySet(BaseQuerySet): yield self._result_cache[pos] pos += 1 - # Raise StopIteration if we already established there were no more + # return if we already established there were no more # docs in the db cursor. if not self._has_more: - raise StopIteration + return # Otherwise, populate more of the cache and repeat. if len(self._result_cache) <= pos: From 6b38ef3c9f0a69420fe4305dda226a0faf9f0528 Mon Sep 17 00:00:00 2001 From: kcarretto Date: Sat, 11 Nov 2017 03:36:28 -0500 Subject: [PATCH 10/85] Fixed format string issue in mongoengine error message. --- mongoengine/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 62d9b941..8692e3a7 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -1108,7 +1108,7 @@ class ReferenceField(BaseField): if self.document_type._meta.get('abstract') and \ not isinstance(value, self.document_type): self.error( - '%s is not an instance of abstract reference type %s' % ( + '%s is not an instance of abstract reference type %s' % (value, self.document_type._class_name) ) From 8b5df3ca1733a6fb6d6fcc03273f0545f617732b Mon Sep 17 00:00:00 2001 From: swathi Date: Mon, 22 Jan 2018 17:21:28 -0800 Subject: [PATCH 11/85] fix - allow url with underscore in domain --- mongoengine/fields.py | 2 +- tests/fields/fields.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index d812a762..eec31829 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -121,7 +121,7 @@ class URLField(StringField): _URL_REGEX = re.compile( r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately - r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}(? Date: Wed, 21 Feb 2018 05:02:35 -0500 Subject: [PATCH 12/85] Fixed formatting issue --- mongoengine/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 8692e3a7..d75286f0 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -1108,8 +1108,8 @@ class ReferenceField(BaseField): if self.document_type._meta.get('abstract') and \ not isinstance(value, self.document_type): self.error( - '%s is not an instance of abstract reference type %s' % (value, - self.document_type._class_name) + '%s is not an instance of abstract reference type %s' % ( + value, self.document_type._class_name) ) def lookup_member(self, member_name): From 080226dd7289bb3973c46b76eba15174dd8c6977 Mon Sep 17 00:00:00 2001 From: Tal Yalon Date: Fri, 22 Jun 2018 14:16:17 +0300 Subject: [PATCH 13/85] Fix issue #1286 and #844.: when building a query set from filters that reference the same field several times, do not assume each value is a dict --- mongoengine/queryset/transform.py | 2 +- tests/queryset/queryset.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py index 5f777f41..f450c8a3 100644 --- a/mongoengine/queryset/transform.py +++ b/mongoengine/queryset/transform.py @@ -147,7 +147,7 @@ def query(_doc_cls=None, **kwargs): if op is None or key not in mongo_query: mongo_query[key] = value elif key in mongo_query: - if isinstance(mongo_query[key], dict): + if isinstance(mongo_query[key], dict) and isinstance(value, dict): mongo_query[key].update(value) # $max/minDistance needs to come last - convert to SON value_dict = mongo_query[key] diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 497a0d23..9b1b3256 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -1202,6 +1202,14 @@ class QuerySetTest(unittest.TestCase): BlogPost.drop_collection() Blog.drop_collection() + def test_filter_chaining_with_regex(self): + person = self.Person(name='Guido van Rossum') + person.save() + + people = self.Person.objects + people = people.filter(name__startswith='Gui').filter(name__not__endswith='tum') + self.assertEqual(people.count(), 1) + def assertSequence(self, qs, expected): qs = list(qs) expected = list(expected) From 3d45cdc339a08717267b94c7cfe47cb99c2afec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 12 Jun 2018 20:59:12 +0200 Subject: [PATCH 14/85] Implemented lazy regex compiling in Field classes to improve 'import mongoengine' performance --- mongoengine/base/utils.py | 22 ++++++++++++++++++++ mongoengine/common.py | 1 + mongoengine/fields.py | 10 ++++----- mongoengine/python_support.py | 6 +----- tests/test_utils.py | 38 +++++++++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 mongoengine/base/utils.py create mode 100644 tests/test_utils.py diff --git a/mongoengine/base/utils.py b/mongoengine/base/utils.py new file mode 100644 index 00000000..288c2f3e --- /dev/null +++ b/mongoengine/base/utils.py @@ -0,0 +1,22 @@ +import re + + +class LazyRegexCompiler(object): + """Descriptor to allow lazy compilation of regex""" + + def __init__(self, pattern, flags=0): + self._pattern = pattern + self._flags = flags + self._compiled_regex = None + + @property + def compiled_regex(self): + if self._compiled_regex is None: + self._compiled_regex = re.compile(self._pattern, self._flags) + return self._compiled_regex + + def __get__(self, obj, objtype): + return self.compiled_regex + + def __set__(self, instance, value): + raise AttributeError("Can not set attribute LazyRegexCompiler") diff --git a/mongoengine/common.py b/mongoengine/common.py index bde7e78c..2e64130f 100644 --- a/mongoengine/common.py +++ b/mongoengine/common.py @@ -56,3 +56,4 @@ def _import_class(cls_name): _class_registry_cache[cls] = getattr(module, cls) return _class_registry_cache.get(cls_name) + diff --git a/mongoengine/fields.py b/mongoengine/fields.py index f9622b31..28ab25c7 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -5,7 +5,6 @@ import re import socket import time import uuid -import warnings from operator import itemgetter from bson import Binary, DBRef, ObjectId, SON @@ -28,6 +27,7 @@ except ImportError: from mongoengine.base import (BaseDocument, BaseField, ComplexBaseField, GeoJsonBaseField, LazyReference, ObjectIdField, get_document) +from mongoengine.base.utils import LazyRegexCompiler from mongoengine.common import _import_class from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db from mongoengine.document import Document, EmbeddedDocument @@ -123,7 +123,7 @@ class URLField(StringField): .. versionadded:: 0.3 """ - _URL_REGEX = re.compile( + _URL_REGEX = LazyRegexCompiler( r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}(?= 3 # six.BytesIO resolves to StringIO.StringIO in Py2 and io.BytesIO in Py3. StringIO = six.BytesIO diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..562cc1ff --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,38 @@ +import unittest +import re + +from mongoengine.base.utils import LazyRegexCompiler + +signal_output = [] + + +class LazyRegexCompilerTest(unittest.TestCase): + + def test_lazy_regex_compiler_verify_laziness_of_descriptor(self): + class UserEmail(object): + EMAIL_REGEX = LazyRegexCompiler('@', flags=32) + + descriptor = UserEmail.__dict__['EMAIL_REGEX'] + self.assertIsNone(descriptor._compiled_regex) + + regex = UserEmail.EMAIL_REGEX + self.assertEqual(regex, re.compile('@', flags=32)) + self.assertEqual(regex.search('user@domain.com').group(), '@') + + user_email = UserEmail() + self.assertIs(user_email.EMAIL_REGEX, UserEmail.EMAIL_REGEX) + + def test_lazy_regex_compiler_verify_cannot_set_descriptor_on_instance(self): + class UserEmail(object): + EMAIL_REGEX = LazyRegexCompiler('@') + + user_email = UserEmail() + with self.assertRaises(AttributeError): + user_email.EMAIL_REGEX = re.compile('@') + + def test_lazy_regex_compiler_verify_can_override_class_attr(self): + class UserEmail(object): + EMAIL_REGEX = LazyRegexCompiler('@') + + UserEmail.EMAIL_REGEX = re.compile('cookies') + self.assertEqual(UserEmail.EMAIL_REGEX.search('Cake & cookies').group(), 'cookies') From 727866f090668881e425b8a90491be1f23563737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sun, 5 Aug 2018 22:30:51 +0200 Subject: [PATCH 15/85] fix styling flake8 error from CI --- mongoengine/common.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mongoengine/common.py b/mongoengine/common.py index 2e64130f..bde7e78c 100644 --- a/mongoengine/common.py +++ b/mongoengine/common.py @@ -56,4 +56,3 @@ def _import_class(cls_name): _class_registry_cache[cls] = getattr(module, cls) return _class_registry_cache.get(cls_name) - From c09bfca6342a60ee20f931c16731282969c378bc Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Wed, 15 Aug 2018 12:36:13 -0700 Subject: [PATCH 16/85] add __repr__ to Q and QCombination --- mongoengine/queryset/visitor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mongoengine/queryset/visitor.py b/mongoengine/queryset/visitor.py index bcf93a13..3ae053b8 100644 --- a/mongoengine/queryset/visitor.py +++ b/mongoengine/queryset/visitor.py @@ -131,6 +131,10 @@ class QCombination(QNode): else: self.children.append(node) + def __repr__(self): + op = ' & ' if self.operation is self.AND else ' | ' + return '(%s)' % op.join([repr(node) for node in self.children]) + def accept(self, visitor): for i in range(len(self.children)): if isinstance(self.children[i], QNode): @@ -151,6 +155,9 @@ class Q(QNode): def __init__(self, **query): self.query = query + def __repr__(self): + return 'Q(**%s)' % repr(self.query) + def accept(self, visitor): return visitor.visit_query(self) From b71ff6fbb8f91c903d498ff0024f35ee47e128af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sat, 18 Aug 2018 23:04:46 +0200 Subject: [PATCH 17/85] fix validator of BinaryField. In fact bson.Binary fails if we give it unicode in input #273 --- mongoengine/fields.py | 4 ++-- tests/fields/fields.py | 35 ++++++++++++++++------------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index f9622b31..75686e57 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -1460,10 +1460,10 @@ class BinaryField(BaseField): return Binary(value) def validate(self, value): - if not isinstance(value, (six.binary_type, six.text_type, Binary)): + if not isinstance(value, (six.binary_type, Binary)): self.error('BinaryField only accepts instances of ' '(%s, %s, Binary)' % ( - six.binary_type.__name__, six.text_type.__name__)) + six.binary_type.__name__, Binary.__name__)) if self.max_bytes is not None and len(value) > self.max_bytes: self.error('Binary value is too long') diff --git a/tests/fields/fields.py b/tests/fields/fields.py index 7352d242..96b77ee7 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -2967,37 +2967,34 @@ class FieldTest(MongoDBTestCase): self.assertEqual(MIME_TYPE, attachment_1.content_type) self.assertEqual(BLOB, six.binary_type(attachment_1.blob)) - def test_binary_validation(self): - """Ensure that invalid values cannot be assigned to binary fields. + def test_binary_validation_succeeds(self): + """Ensure that valid values can be assigned to binary fields. """ - class Attachment(Document): - blob = BinaryField() - class AttachmentRequired(Document): blob = BinaryField(required=True) class AttachmentSizeLimit(Document): blob = BinaryField(max_bytes=4) - Attachment.drop_collection() - AttachmentRequired.drop_collection() - AttachmentSizeLimit.drop_collection() - - attachment = Attachment() - attachment.validate() - attachment.blob = 2 - self.assertRaises(ValidationError, attachment.validate) - attachment_required = AttachmentRequired() self.assertRaises(ValidationError, attachment_required.validate) attachment_required.blob = Binary(six.b('\xe6\x00\xc4\xff\x07')) attachment_required.validate() - attachment_size_limit = AttachmentSizeLimit( - blob=six.b('\xe6\x00\xc4\xff\x07')) - self.assertRaises(ValidationError, attachment_size_limit.validate) - attachment_size_limit.blob = six.b('\xe6\x00\xc4\xff') - attachment_size_limit.validate() + _5_BYTES = six.b('\xe6\x00\xc4\xff\x07') + _4_BYTES = six.b('\xe6\x00\xc4\xff') + self.assertRaises(ValidationError, AttachmentSizeLimit(blob=_5_BYTES).validate) + AttachmentSizeLimit(blob=_4_BYTES).validate() + + def test_binary_validation_fails(self): + """Ensure that invalid values cannot be assigned to binary fields.""" + + class Attachment(Document): + blob = BinaryField() + + for invalid_data in (2, u'Im_a_unicode', ['some_str']): + self.assertRaises(ValidationError, Attachment(blob=invalid_data).validate) + def test_binary_field_primary(self): class Attachment(Document): From b3b4d992fefaf3ae3e61465818c1dc3e6bdd9256 Mon Sep 17 00:00:00 2001 From: "Terence D. Honles" Date: Mon, 27 Aug 2018 19:24:24 -0700 Subject: [PATCH 18/85] add Q repr tests --- tests/queryset/visitor.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/queryset/visitor.py b/tests/queryset/visitor.py index 6f020e88..6186e726 100644 --- a/tests/queryset/visitor.py +++ b/tests/queryset/visitor.py @@ -296,6 +296,18 @@ class QTest(unittest.TestCase): obj = self.Person.objects(Q(name__not=re.compile('^Gui'))).first() self.assertEqual(obj, None) + def test_q_repr(self): + self.assertEqual(repr(Q()), 'Q(**{})') + self.assertEqual(repr(Q(name='test')), "Q(**{'name': 'test'})") + + self.assertEqual( + repr(Q(name='test') & Q(age__gte=18)), + "(Q(**{'name': 'test'}) & Q(**{'age__gte': 18}))") + + self.assertEqual( + repr(Q(name='test') | Q(age__gte=18)), + "(Q(**{'name': 'test'}) | Q(**{'age__gte': 18}))") + def test_q_lists(self): """Ensure that Q objects query ListFields correctly. """ From 86548fc7bfcd73d75f5cf6fbc8cba690294d8253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 28 Aug 2018 22:42:51 +0200 Subject: [PATCH 19/85] Document the attribute of .from_json --- mongoengine/base/document.py | 10 +++- tests/document/instance.py | 92 ++++++++++++++++++++++++++++-------- tests/queryset/queryset.py | 1 - 3 files changed, 81 insertions(+), 22 deletions(-) diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index f03ce848..85906a3e 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -404,7 +404,15 @@ class BaseDocument(object): @classmethod def from_json(cls, json_data, created=False): - """Converts json data to an unsaved document instance""" + """Converts json data to a Document instance + + :param json_data: The json data to load into the Document + :param created: If True, the document will be considered as a brand new document + If False and an id is provided, it will consider that the data being + loaded corresponds to what's already in the database (This has an impact of subsequent call to .save()) + If False and no id is provided, it will consider the data as a new document + (default ``False``) + """ return cls._from_son(json_util.loads(json_data), created=created) def __expand_dynamic_values(self, name, value): diff --git a/tests/document/instance.py b/tests/document/instance.py index b255e8a6..ca0a5ad0 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -550,21 +550,14 @@ class InstanceTest(unittest.TestCase): pass f = Foo() - try: + with self.assertRaises(Foo.DoesNotExist): f.reload() - except Foo.DoesNotExist: - pass - except Exception: - self.assertFalse("Threw wrong exception") f.save() f.delete() - try: + + with self.assertRaises(Foo.DoesNotExist): f.reload() - except Foo.DoesNotExist: - pass - except Exception: - self.assertFalse("Threw wrong exception") def test_reload_of_non_strict_with_special_field_name(self): """Ensures reloading works for documents with meta strict == False.""" @@ -734,12 +727,12 @@ class InstanceTest(unittest.TestCase): t = TestDocument(status="draft", pub_date=datetime.now()) - try: + with self.assertRaises(ValidationError) as cm: t.save() - except ValidationError as e: - expect_msg = "Draft entries may not have a publication date." - self.assertTrue(expect_msg in e.message) - self.assertEqual(e.to_dict(), {'__all__': expect_msg}) + + 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="published") t.save(clean=False) @@ -773,12 +766,13 @@ class InstanceTest(unittest.TestCase): TestDocument.drop_collection() t = TestDocument(doc=TestEmbeddedDocument(x=10, y=25, z=15)) - try: + + with self.assertRaises(ValidationError) as cm: t.save() - except ValidationError as e: - expect_msg = "Value of z != x + y" - self.assertTrue(expect_msg in e.message) - self.assertEqual(e.to_dict(), {'doc': {'__all__': expect_msg}}) + + expected_msg = "Value of z != x + y" + self.assertIn(expected_msg, cm.exception.message) + self.assertEqual(cm.exception.to_dict(), {'doc': {'__all__': expected_msg}}) t = TestDocument(doc=TestEmbeddedDocument(x=10, y=25)).save() self.assertEqual(t.doc.z, 35) @@ -3148,6 +3142,64 @@ class InstanceTest(unittest.TestCase): self.assertEquals(p.id, None) p.id = "12345" # in case it is not working: "OperationError: Shard Keys are immutable..." will be raised here + def test_from_son_created_False_without_id(self): + class MyPerson(Document): + name = StringField() + + MyPerson.objects.delete() + + p = MyPerson.from_json('{"name": "a_fancy_name"}', created=False) + self.assertFalse(p._created) + self.assertIsNone(p.id) + p.save() + self.assertIsNotNone(p.id) + saved_p = MyPerson.objects.get(id=p.id) + self.assertEqual(saved_p.name, 'a_fancy_name') + + def test_from_son_created_False_with_id(self): + # 1854 + class MyPerson(Document): + name = StringField() + + MyPerson.objects.delete() + + p = MyPerson.from_json('{"_id": "5b85a8b04ec5dc2da388296e", "name": "a_fancy_name"}', created=False) + self.assertFalse(p._created) + self.assertEqual(p._changed_fields, []) + self.assertEqual(p.name, 'a_fancy_name') + self.assertEqual(p.id, ObjectId('5b85a8b04ec5dc2da388296e')) + p.save() + + with self.assertRaises(DoesNotExist): + # Since created=False and we gave an id in the json and _changed_fields is empty + # mongoengine assumes that the document exits with that structure already + # and calling .save() didn't save anything + MyPerson.objects.get(id=p.id) + + self.assertFalse(p._created) + p.name = 'a new fancy name' + self.assertEqual(p._changed_fields, ['name']) + p.save() + saved_p = MyPerson.objects.get(id=p.id) + self.assertEqual(saved_p.name, p.name) + + def test_from_son_created_True_with_an_id(self): + class MyPerson(Document): + name = StringField() + + MyPerson.objects.delete() + + p = MyPerson.from_json('{"_id": "5b85a8b04ec5dc2da388296e", "name": "a_fancy_name"}', created=True) + self.assertTrue(p._created) + self.assertEqual(p._changed_fields, []) + self.assertEqual(p.name, 'a_fancy_name') + self.assertEqual(p.id, ObjectId('5b85a8b04ec5dc2da388296e')) + p.save() + + saved_p = MyPerson.objects.get(id=p.id) + self.assertEqual(saved_p, p) + self.assertEqual(p.name, 'a_fancy_name') + def test_null_field(self): # 734 class User(Document): diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 497a0d23..a405e892 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -4457,7 +4457,6 @@ class QuerySetTest(unittest.TestCase): self.assertNotEqual(bars._CommandCursor__collection.read_preference, ReadPreference.SECONDARY_PREFERRED) - def test_json_simple(self): class Embedded(EmbeddedDocument): From 576f23d5fb371315e2b54b6798915d8a103c3e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 28 Aug 2018 22:58:13 +0200 Subject: [PATCH 20/85] Fix .delete doc of **write_concern as suggested by #1779 --- mongoengine/document.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mongoengine/document.py b/mongoengine/document.py index b9db8bc6..25af273d 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -585,9 +585,8 @@ class Document(BaseDocument): :param signal_kwargs: (optional) kwargs dictionary to be passed to the signal calls. :param write_concern: Extra keyword arguments are passed down which - will be used as options for the resultant - ``getLastError`` command. For example, - ``save(..., write_concern={w: 2, fsync: True}, ...)`` will + will be used as options for the resultant ``getLastError`` command. + For example, ``save(..., w: 2, fsync: True)`` will wait until at least two servers have recorded the write and will force an fsync on the primary server. From b4860de34dd922936b290d98c95e575c12c713d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Wed, 29 Aug 2018 23:04:18 +0200 Subject: [PATCH 21/85] Fix index creation error that was swallowed by hasattr under python2 (#1688) --- mongoengine/dereference.py | 7 ++++++- tests/document/instance.py | 25 +++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/mongoengine/dereference.py b/mongoengine/dereference.py index 18b365cc..40bc72b2 100644 --- a/mongoengine/dereference.py +++ b/mongoengine/dereference.py @@ -133,7 +133,12 @@ class DeReference(object): """ object_map = {} for collection, dbrefs in self.reference_map.iteritems(): - if hasattr(collection, 'objects'): # We have a document class for the refs + + # we use getattr instead of hasattr because as hasattr swallows any exception under python2 + # so it could hide nasty things without raising exceptions (cfr bug #1688)) + ref_document_cls_exists = (getattr(collection, 'objects', None) is not None) + + if ref_document_cls_exists: col_name = collection._get_collection_name() refs = [dbref for dbref in dbrefs if (col_name, dbref) not in object_map] diff --git a/tests/document/instance.py b/tests/document/instance.py index b255e8a6..c933eaf4 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -8,9 +8,12 @@ import weakref from datetime import datetime from bson import DBRef, ObjectId +from pymongo.errors import DuplicateKeyError + from tests import fixtures from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest, PickleDynamicEmbedded, PickleDynamicTest) +from tests.utils import MongoDBTestCase from mongoengine import * from mongoengine.base import get_document, _document_registry @@ -30,12 +33,9 @@ TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), __all__ = ("InstanceTest",) -class InstanceTest(unittest.TestCase): +class InstanceTest(MongoDBTestCase): def setUp(self): - connect(db='mongoenginetest') - self.db = get_db() - class Job(EmbeddedDocument): name = StringField() years = IntField() @@ -3248,6 +3248,23 @@ class InstanceTest(unittest.TestCase): blog.reload() self.assertEqual(blog.tags, [["value1", 123]]) + def test_accessing_objects_with_indexes_error(self): + insert_result = self.db.company.insert_many([{'name': 'Foo'}, + {'name': 'Foo'}]) # Force 2 doc with same name + REF_OID = insert_result.inserted_ids[0] + self.db.user.insert_one({'company': REF_OID}) # Force 2 doc with same name + + class Company(Document): + name = StringField(unique=True) + + class User(Document): + company = ReferenceField(Company) + + + # Ensure index creation exception aren't swallowed (#1688) + with self.assertRaises(DuplicateKeyError): + User.objects().select_related() + if __name__ == '__main__': unittest.main() From 4779106139306257fb9dfd8203ed7cc7c3310385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Thu, 30 Aug 2018 21:50:03 +0200 Subject: [PATCH 22/85] Improve doc of EmbeddedDocumentList.filter and clarify that it does not supports operators like __lte, __gte, etc --- mongoengine/base/datastructures.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mongoengine/base/datastructures.py b/mongoengine/base/datastructures.py index 8948243e..db292f14 100644 --- a/mongoengine/base/datastructures.py +++ b/mongoengine/base/datastructures.py @@ -234,6 +234,9 @@ class EmbeddedDocumentList(BaseList): Filters the list by only including embedded documents with the given keyword arguments. + This method only supports simple comparison (e.g: .filter(name='John Doe')) + and does not support operators like __gte, __lte, __icontains like queryset.filter does + :param kwargs: The keyword arguments corresponding to the fields to filter on. *Multiple arguments are treated as if they are ANDed together.* From 5dbee2a2708722a4e9835ac5738991ba394954c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Thu, 30 Aug 2018 16:03:16 +0200 Subject: [PATCH 23/85] Ensures EmbeddedDocumentField does not accepts references to Document classes in its constructor --- mongoengine/fields.py | 12 +++++++++-- tests/fields/fields.py | 47 ++++++++++++++++++++++++++++++++++++++++++ tests/utils.py | 8 +++++-- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 16f3185f..a54d3a52 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -645,9 +645,17 @@ class EmbeddedDocumentField(BaseField): def document_type(self): if isinstance(self.document_type_obj, six.string_types): if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT: - self.document_type_obj = self.owner_document + resolved_document_type = self.owner_document else: - self.document_type_obj = get_document(self.document_type_obj) + resolved_document_type = get_document(self.document_type_obj) + + if not issubclass(resolved_document_type, EmbeddedDocument): + # Due to the late resolution of the document_type + # There is a chance that it won't be an EmbeddedDocument (#1661) + self.error('Invalid embedded document class provided to an ' + 'EmbeddedDocumentField') + self.document_type_obj = resolved_document_type + return self.document_type_obj def to_python(self, value): diff --git a/tests/fields/fields.py b/tests/fields/fields.py index 7352d242..362acec4 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -2147,6 +2147,15 @@ class FieldTest(MongoDBTestCase): ])) self.assertEqual(a.b.c.txt, 'hi') + def test_embedded_document_field_cant_reference_using_a_str_if_it_does_not_exist_yet(self): + raise SkipTest("Using a string reference in an EmbeddedDocumentField does not work if the class isnt registerd yet") + + class MyDoc2(Document): + emb = EmbeddedDocumentField('MyDoc') + + class MyDoc(EmbeddedDocument): + name = StringField() + def test_embedded_document_validation(self): """Ensure that invalid embedded documents cannot be assigned to embedded document fields. @@ -4388,6 +4397,44 @@ class EmbeddedDocumentListFieldTestCase(MongoDBTestCase): self.assertEqual(custom_data['a'], CustomData.c_field.custom_data['a']) +class TestEmbeddedDocumentField(MongoDBTestCase): + def test___init___(self): + class MyDoc(EmbeddedDocument): + name = StringField() + + field = EmbeddedDocumentField(MyDoc) + self.assertEqual(field.document_type_obj, MyDoc) + + field2 = EmbeddedDocumentField('MyDoc') + self.assertEqual(field2.document_type_obj, 'MyDoc') + + def test___init___throw_error_if_document_type_is_not_EmbeddedDocument(self): + with self.assertRaises(ValidationError): + EmbeddedDocumentField(dict) + + def test_document_type_throw_error_if_not_EmbeddedDocument_subclass(self): + + class MyDoc(Document): + name = StringField() + + emb = EmbeddedDocumentField('MyDoc') + with self.assertRaises(ValidationError) as ctx: + emb.document_type + self.assertIn('Invalid embedded document class provided to an EmbeddedDocumentField', str(ctx.exception)) + + def test_embedded_document_field_only_allow_subclasses_of_embedded_document(self): + # Relates to #1661 + class MyDoc(Document): + name = StringField() + + with self.assertRaises(ValidationError): + class MyFailingDoc(Document): + emb = EmbeddedDocumentField(MyDoc) + + with self.assertRaises(ValidationError): + class MyFailingdoc2(Document): + emb = EmbeddedDocumentField('MyDoc') + class CachedReferenceFieldTest(MongoDBTestCase): def test_cached_reference_field_get_and_save(self): diff --git a/tests/utils.py b/tests/utils.py index 4566d864..acd318c5 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -7,12 +7,12 @@ from mongoengine.connection import get_db, get_connection from mongoengine.python_support import IS_PYMONGO_3 -MONGO_TEST_DB = 'mongoenginetest' +MONGO_TEST_DB = 'mongoenginetest' # standard name for the test database class MongoDBTestCase(unittest.TestCase): """Base class for tests that need a mongodb connection - db is being dropped automatically + It ensures that the db is clean at the beginning and dropped at the end automatically """ @classmethod @@ -32,6 +32,7 @@ def get_mongodb_version(): """ return tuple(get_connection().server_info()['versionArray']) + def _decorated_with_ver_requirement(func, ver_tuple): """Return a given function decorated with the version requirement for a particular MongoDB version tuple. @@ -50,18 +51,21 @@ def _decorated_with_ver_requirement(func, ver_tuple): return _inner + def needs_mongodb_v26(func): """Raise a SkipTest exception if we're working with MongoDB version lower than v2.6. """ return _decorated_with_ver_requirement(func, (2, 6)) + def needs_mongodb_v3(func): """Raise a SkipTest exception if we're working with MongoDB version lower than v3.0. """ return _decorated_with_ver_requirement(func, (3, 0)) + def skip_pymongo3(f): """Raise a SkipTest exception if we're running a test against PyMongo v3.x. From d9fce49b082d774163f355d17ad432f0627ae086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Thu, 30 Aug 2018 22:46:37 +0200 Subject: [PATCH 24/85] minor improvement to DateTimeField doc --- mongoengine/fields.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 9648bb0b..ead86291 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -462,6 +462,8 @@ class DateTimeField(BaseField): installed you can utilise it to convert varying types of date formats into valid python datetime objects. + Note: To default the field to the current datetime, use: DateTimeField(default=datetime.utcnow) + Note: Microseconds are rounded to the nearest millisecond. Pre UTC microsecond support is effectively broken. Use :class:`~mongoengine.fields.ComplexDateTimeField` if you From bd524d2e1e51f2c9ea4bc971003eeb34e8e7a090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Thu, 30 Aug 2018 23:13:10 +0200 Subject: [PATCH 25/85] Documented that it is possible to specify a name when using a dict to define an index --- docs/guide/defining-documents.rst | 3 +++ mongoengine/context_managers.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index 2a8d5418..366d12c7 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -513,6 +513,9 @@ If a dictionary is passed then the following options are available: Allows you to automatically expire data from a collection by setting the time in seconds to expire the a field. +:attr:`name` (Optional) + Allows you to specify a name for the index + .. note:: Inheritance adds extra fields indices see: :ref:`document-inheritance`. diff --git a/mongoengine/context_managers.py b/mongoengine/context_managers.py index ec2e9e8b..0343e163 100644 --- a/mongoengine/context_managers.py +++ b/mongoengine/context_managers.py @@ -215,7 +215,7 @@ class query_counter(object): """Get the number of queries.""" ignore_query = {'ns': {'$ne': '%s.system.indexes' % self.db.name}} count = self.db.system.profile.find(ignore_query).count() - self.counter - self.counter += 1 + self.counter += 1 # Account for the query we just fired return count From a7852a89cc5d8a9bf0f976c99cd42eacade4ef85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sat, 1 Sep 2018 23:30:50 +0200 Subject: [PATCH 26/85] Fixes 2 bugs in no_subclasses context mgr (__exit__ swallows exception + repair feature) --- mongoengine/context_managers.py | 9 +++---- tests/test_context_managers.py | 44 +++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/mongoengine/context_managers.py b/mongoengine/context_managers.py index ec2e9e8b..cfc0cdd4 100644 --- a/mongoengine/context_managers.py +++ b/mongoengine/context_managers.py @@ -145,18 +145,17 @@ class no_sub_classes(object): :param cls: the class to turn querying sub classes on """ self.cls = cls + self.cls_initial_subclasses = None def __enter__(self): """Change the objects default and _auto_dereference values.""" - self.cls._all_subclasses = self.cls._subclasses - self.cls._subclasses = (self.cls,) + self.cls_initial_subclasses = self.cls._subclasses + self.cls._subclasses = (self.cls._class_name,) return self.cls def __exit__(self, t, value, traceback): """Reset the default and _auto_dereference values.""" - self.cls._subclasses = self.cls._all_subclasses - delattr(self.cls, '_all_subclasses') - return self.cls + self.cls._subclasses = self.cls_initial_subclasses class query_counter(object): diff --git a/tests/test_context_managers.py b/tests/test_context_managers.py index 0f6bf815..8c96016c 100644 --- a/tests/test_context_managers.py +++ b/tests/test_context_managers.py @@ -140,8 +140,6 @@ class ContextManagersTest(unittest.TestCase): def test_no_sub_classes(self): class A(Document): x = IntField() - y = IntField() - meta = {'allow_inheritance': True} class B(A): @@ -152,29 +150,29 @@ class ContextManagersTest(unittest.TestCase): A.drop_collection() - A(x=10, y=20).save() - A(x=15, y=30).save() - B(x=20, y=40).save() - B(x=30, y=50).save() - C(x=40, y=60).save() + A(x=10).save() + A(x=15).save() + B(x=20).save() + B(x=30).save() + C(x=40).save() self.assertEqual(A.objects.count(), 5) self.assertEqual(B.objects.count(), 3) self.assertEqual(C.objects.count(), 1) - with no_sub_classes(A) as A: + with no_sub_classes(A): self.assertEqual(A.objects.count(), 2) for obj in A.objects: self.assertEqual(obj.__class__, A) - with no_sub_classes(B) as B: + with no_sub_classes(B): self.assertEqual(B.objects.count(), 2) for obj in B.objects: self.assertEqual(obj.__class__, B) - with no_sub_classes(C) as C: + with no_sub_classes(C): self.assertEqual(C.objects.count(), 1) for obj in C.objects: @@ -185,6 +183,32 @@ class ContextManagersTest(unittest.TestCase): self.assertEqual(B.objects.count(), 3) self.assertEqual(C.objects.count(), 1) + def test_no_sub_classes_modification_to_document_class_are_temporary(self): + class A(Document): + x = IntField() + meta = {'allow_inheritance': True} + + class B(A): + z = IntField() + + self.assertEqual(A._subclasses, ('A', 'A.B')) + with no_sub_classes(A): + self.assertEqual(A._subclasses, ('A',)) + self.assertEqual(A._subclasses, ('A', 'A.B')) + + self.assertEqual(B._subclasses, ('A.B',)) + with no_sub_classes(B): + self.assertEqual(B._subclasses, ('A.B',)) + self.assertEqual(B._subclasses, ('A.B',)) + + def test_no_subclass_context_manager_does_not_swallow_exception(self): + class User(Document): + name = StringField() + + with self.assertRaises(TypeError): + with no_sub_classes(User): + raise TypeError() + def test_query_counter(self): connect('mongoenginetest') db = get_db() From 8ff82996fb76314f799e9a153f32a4adc37485d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Mon, 3 Sep 2018 11:11:15 +0200 Subject: [PATCH 27/85] Fix few things related to query_counter context manager: - Improve doc - Fix the fact that the context was modifying the initial profiling_level in case it was != 0 - Ignores 'killcursors' to fix flaky test that were impacted by killcursors queries (#1869) --- mongoengine/context_managers.py | 56 +++++++++++++------- tests/queryset/queryset.py | 29 +++++++---- tests/test_context_managers.py | 91 +++++++++++++++++++++++++++++++-- 3 files changed, 143 insertions(+), 33 deletions(-) diff --git a/mongoengine/context_managers.py b/mongoengine/context_managers.py index 67c83581..ee1f5e01 100644 --- a/mongoengine/context_managers.py +++ b/mongoengine/context_managers.py @@ -159,51 +159,69 @@ class no_sub_classes(object): class query_counter(object): - """Query_counter context manager to get the number of queries.""" + """Query_counter context manager to get the number of queries. + This works by updating the `profiling_level` of the database so that all queries get logged, + resetting the db.system.profile collection at the beginnig of the context and counting the new entries. + + This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes + can interfere with it + + Be aware that: + - Iterating over large amount of documents (>101) makes pymongo issue `getmore` queries to fetch the next batch of + documents (https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches) + - Some queries are ignored by default by the counter (killcursors, db.system.indexes) + """ def __init__(self): - """Construct the query_counter.""" - self.counter = 0 + """Construct the query_counter + """ self.db = get_db() + self.initial_profiling_level = None + self._ctx_query_counter = 0 # number of queries issued by the context - def __enter__(self): - """On every with block we need to drop the profile collection.""" + self._ignored_query = { + 'ns': + {'$ne': '%s.system.indexes' % self.db.name}, + 'op': + {'$ne': 'killcursors'} + } + + def _turn_on_profiling(self): + self.initial_profiling_level = self.db.profiling_level() self.db.set_profiling_level(0) self.db.system.profile.drop() self.db.set_profiling_level(2) + + def _resets_profiling(self): + self.db.set_profiling_level(self.initial_profiling_level) + + def __enter__(self): + self._turn_on_profiling() return self def __exit__(self, t, value, traceback): - """Reset the profiling level.""" - self.db.set_profiling_level(0) + self._resets_profiling() def __eq__(self, value): - """== Compare querycounter.""" counter = self._get_count() return value == counter def __ne__(self, value): - """!= Compare querycounter.""" return not self.__eq__(value) def __lt__(self, value): - """< Compare querycounter.""" return self._get_count() < value def __le__(self, value): - """<= Compare querycounter.""" return self._get_count() <= value def __gt__(self, value): - """> Compare querycounter.""" return self._get_count() > value def __ge__(self, value): - """>= Compare querycounter.""" return self._get_count() >= value def __int__(self): - """int representation.""" return self._get_count() def __repr__(self): @@ -211,10 +229,12 @@ class query_counter(object): return u"%s" % self._get_count() def _get_count(self): - """Get the number of queries.""" - ignore_query = {'ns': {'$ne': '%s.system.indexes' % self.db.name}} - count = self.db.system.profile.find(ignore_query).count() - self.counter - self.counter += 1 # Account for the query we just fired + """Get the number of queries by counting the current number of entries in db.system.profile + and substracting the queries issued by this context. In fact everytime this is called, 1 query is + issued so we need to balance that + """ + count = self.db.system.profile.find(self._ignored_query).count() - self._ctx_query_counter + self._ctx_query_counter += 1 # Account for the query we just issued to gather the information return count diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index a405e892..ff5407df 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -4714,18 +4714,27 @@ class QuerySetTest(unittest.TestCase): for i in range(100): Person(name="No: %s" % i).save() - with query_counter() as q: - self.assertEqual(q, 0) - people = Person.objects.no_cache() + with query_counter() as q: + try: + self.assertEqual(q, 0) + people = Person.objects.no_cache() - [x for x in people] - self.assertEqual(q, 1) + [x for x in people] + self.assertEqual(q, 1) - list(people) - self.assertEqual(q, 2) + list(people) + self.assertEqual(q, 2) + + people.count() + self.assertEqual(q, 3) + except AssertionError as exc: + db = get_db() + msg = '' + for q in list(db.system.profile.find())[-50:]: + msg += str([q['ts'], q['ns'], q.get('query'), q['op']])+'\n' + msg += str(q) + raise AssertionError(str(exc) + '\n'+msg) - people.count() - self.assertEqual(q, 3) def test_cache_not_cloned(self): @@ -5053,7 +5062,7 @@ class QuerySetTest(unittest.TestCase): {"$ne": "%s.system.indexes" % q.db.name}})[0] self.assertFalse('$orderby' in op['query'], - 'BaseQuerySet must remove orderby from meta in boolen test') + 'BaseQuerySet must remove orderby from meta in boolean test') self.assertEqual(Person.objects.first().name, 'A') self.assertTrue(Person.objects._has_data(), diff --git a/tests/test_context_managers.py b/tests/test_context_managers.py index 8c96016c..2b265cbd 100644 --- a/tests/test_context_managers.py +++ b/tests/test_context_managers.py @@ -209,18 +209,99 @@ class ContextManagersTest(unittest.TestCase): with no_sub_classes(User): raise TypeError() + def test_query_counter_does_not_swallow_exception(self): + + with self.assertRaises(TypeError): + with query_counter() as q: + raise TypeError() + + def test_query_counter_temporarily_modifies_profiling_level(self): + connect('mongoenginetest') + db = get_db() + + initial_profiling_level = db.profiling_level() + + try: + NEW_LEVEL = 1 + db.set_profiling_level(NEW_LEVEL) + self.assertEqual(db.profiling_level(), NEW_LEVEL) + with query_counter() as q: + self.assertEqual(db.profiling_level(), 2) + self.assertEqual(db.profiling_level(), NEW_LEVEL) + except Exception: + db.set_profiling_level(initial_profiling_level) # Ensures it gets reseted no matter the outcome of the test + raise + def test_query_counter(self): connect('mongoenginetest') db = get_db() - db.test.find({}) + + collection = db.query_counter + collection.drop() + + def issue_1_count_query(): + collection.find({}).count() + + def issue_1_insert_query(): + collection.insert_one({'test': 'garbage'}) + + def issue_1_find_query(): + collection.find_one() + + counter = 0 + with query_counter() as q: + self.assertEqual(q, counter) + self.assertEqual(q, counter) # Ensures previous count query did not get counted + + for _ in range(10): + issue_1_insert_query() + counter += 1 + self.assertEqual(q, counter) + + for _ in range(4): + issue_1_find_query() + counter += 1 + self.assertEqual(q, counter) + + for _ in range(3): + issue_1_count_query() + counter += 1 + self.assertEqual(q, counter) + + def test_query_counter_counts_getmore_queries(self): + connect('mongoenginetest') + db = get_db() + + collection = db.query_counter + collection.drop() + + many_docs = [{'test': 'garbage %s' % i} for i in range(150)] + collection.insert_many(many_docs) # first batch of documents contains 101 documents with query_counter() as q: - self.assertEqual(0, q) + self.assertEqual(q, 0) + list(collection.find()) + self.assertEqual(q, 2) # 1st select + 1 getmore - for i in range(1, 51): - db.test.find({}).count() + def test_query_counter_ignores_particular_queries(self): + connect('mongoenginetest') + db = get_db() - self.assertEqual(50, q) + collection = db.query_counter + collection.insert_many([{'test': 'garbage %s' % i} for i in range(10)]) + + with query_counter() as q: + self.assertEqual(q, 0) + cursor = collection.find() + self.assertEqual(q, 0) # cursor wasn't opened yet + _ = next(cursor) # opens the cursor and fires the find query + self.assertEqual(q, 1) + + cursor.close() # issues a `killcursors` query that is ignored by the context + self.assertEqual(q, 1) + + _ = db.system.indexes.find_one() # queries on db.system.indexes are ignored as well + self.assertEqual(q, 1) if __name__ == '__main__': unittest.main() From 408274152baf75485f17eee9cc0550fd7bb82960 Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Tue, 4 Sep 2018 20:24:34 +0800 Subject: [PATCH 28/85] reduce cycle complexity using logic map --- mongoengine/queryset/transform.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py index 5f777f41..555be6f9 100644 --- a/mongoengine/queryset/transform.py +++ b/mongoengine/queryset/transform.py @@ -214,17 +214,20 @@ def update(_doc_cls=None, **update): if parts[0] in UPDATE_OPERATORS: op = parts.pop(0) # Convert Pythonic names to Mongo equivalents - if op in ('push_all', 'pull_all'): - op = op.replace('_all', 'All') - elif op == 'dec': + operator_map = { + 'push_all': 'pushAll', + 'pull_all': 'pullAll', + 'dec': 'inc', + 'add_to_set': 'addToSet', + 'set_on_insert': 'setOnInsert' + } + # If operator doesn't found from operator map, op value will stay + # unchanged + op = operator_map.get(op, op) + if op == 'dec': # Support decrement by flipping a positive value's sign # and using 'inc' - op = 'inc' value = -value - elif op == 'add_to_set': - op = 'addToSet' - elif op == 'set_on_insert': - op = 'setOnInsert' match = None if parts[-1] in COMPARISON_OPERATORS: From e83b529f1c8cc032968797057d7791169fc9bba5 Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Tue, 4 Sep 2018 20:38:42 +0800 Subject: [PATCH 29/85] flip value before changing op to inc --- mongoengine/queryset/transform.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py index 555be6f9..a8670543 100644 --- a/mongoengine/queryset/transform.py +++ b/mongoengine/queryset/transform.py @@ -221,13 +221,13 @@ def update(_doc_cls=None, **update): 'add_to_set': 'addToSet', 'set_on_insert': 'setOnInsert' } - # If operator doesn't found from operator map, op value will stay - # unchanged - op = operator_map.get(op, op) if op == 'dec': # Support decrement by flipping a positive value's sign # and using 'inc' value = -value + # If operator doesn't found from operator map, op value will stay + # unchanged + op = operator_map.get(op, op) match = None if parts[-1] in COMPARISON_OPERATORS: From b65478e7d9c8f09d915a102a367e8da52ad6bdf4 Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Tue, 4 Sep 2018 20:44:44 +0800 Subject: [PATCH 30/85] trigger ci --- mongoengine/queryset/transform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py index a8670543..6021d464 100644 --- a/mongoengine/queryset/transform.py +++ b/mongoengine/queryset/transform.py @@ -225,8 +225,8 @@ def update(_doc_cls=None, **update): # Support decrement by flipping a positive value's sign # and using 'inc' value = -value - # If operator doesn't found from operator map, op value will stay - # unchanged + # If the operator doesn't found from operator map, the op value + # will stay unchanged op = operator_map.get(op, op) match = None From ab08e67eaf3f6b809a58740c0cfbbb24e1a3ef0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Thu, 30 Aug 2018 23:57:16 +0200 Subject: [PATCH 31/85] fix inc/dec operator with decimal --- mongoengine/fields.py | 7 ++- mongoengine/queryset/transform.py | 6 +++ tests/queryset/queryset.py | 79 ++++++++++++++++++++++++++----- 3 files changed, 78 insertions(+), 14 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 89b901e7..d8eaec4e 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -364,7 +364,8 @@ class FloatField(BaseField): class DecimalField(BaseField): - """Fixed-point decimal number field. + """Fixed-point decimal number field. Stores the value as a float by default unless `force_string` is used. + If using floats, beware of Decimal to float conversion (potential precision loss) .. versionchanged:: 0.8 .. versionadded:: 0.3 @@ -375,7 +376,9 @@ class DecimalField(BaseField): """ :param min_value: Validation rule for the minimum acceptable value. :param max_value: Validation rule for the maximum acceptable value. - :param force_string: Store as a string. + :param force_string: Store the value as a string (instead of a float). + Be aware that this affects query sorting and operation like lte, gte (as string comparison is applied) + and some query operator won't work (e.g: inc, dec) :param precision: Number of decimal places to store. :param rounding: The rounding rule from the python decimal library: diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py index 6021d464..25bd68e0 100644 --- a/mongoengine/queryset/transform.py +++ b/mongoengine/queryset/transform.py @@ -201,14 +201,18 @@ def update(_doc_cls=None, **update): format. """ mongo_update = {} + for key, value in update.items(): if key == '__raw__': mongo_update.update(value) continue + parts = key.split('__') + # if there is no operator, default to 'set' if len(parts) < 3 and parts[0] not in UPDATE_OPERATORS: parts.insert(0, 'set') + # Check for an operator and transform to mongo-style if there is op = None if parts[0] in UPDATE_OPERATORS: @@ -294,6 +298,8 @@ def update(_doc_cls=None, **update): value = field.prepare_query_value(op, value) elif op == 'unset': value = 1 + elif op == 'inc': + value = field.prepare_query_value(op, value) if match: match = '$' + match diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index a405e892..b0dd354d 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -3,6 +3,7 @@ import datetime import unittest import uuid +from decimal import Decimal from bson import DBRef, ObjectId from nose.plugins.skip import SkipTest @@ -1851,21 +1852,16 @@ class QuerySetTest(unittest.TestCase): self.assertEqual( 1, BlogPost.objects(author__in=["%s" % me.pk]).count()) - def test_update(self): - """Ensure that atomic updates work properly. - """ + def test_update_intfield_operator(self): class BlogPost(Document): - name = StringField() - title = StringField() hits = IntField() - tags = ListField(StringField()) BlogPost.drop_collection() - post = BlogPost(name="Test Post", hits=5, tags=['test']) + post = BlogPost(hits=5) post.save() - BlogPost.objects.update(set__hits=10) + BlogPost.objects.update_one(set__hits=10) post.reload() self.assertEqual(post.hits, 10) @@ -1882,6 +1878,55 @@ class QuerySetTest(unittest.TestCase): post.reload() self.assertEqual(post.hits, 11) + def test_update_decimalfield_operator(self): + class BlogPost(Document): + review = DecimalField() + + BlogPost.drop_collection() + + post = BlogPost(review=3.5) + post.save() + + BlogPost.objects.update_one(inc__review=0.1) # test with floats + post.reload() + self.assertEqual(float(post.review), 3.6) + + BlogPost.objects.update_one(dec__review=0.1) + post.reload() + self.assertEqual(float(post.review), 3.5) + + BlogPost.objects.update_one(inc__review=Decimal(0.12)) # test with Decimal + post.reload() + self.assertEqual(float(post.review), 3.62) + + BlogPost.objects.update_one(dec__review=Decimal(0.12)) + post.reload() + self.assertEqual(float(post.review), 3.5) + + def test_update_decimalfield_operator_not_working_with_force_string(self): + class BlogPost(Document): + review = DecimalField(force_string=True) + + BlogPost.drop_collection() + + post = BlogPost(review=3.5) + post.save() + + with self.assertRaises(OperationError): + BlogPost.objects.update_one(inc__review=0.1) # test with floats + + def test_update_listfield_operator(self): + """Ensure that atomic updates work properly. + """ + class BlogPost(Document): + tags = ListField(StringField()) + + BlogPost.drop_collection() + + post = BlogPost(tags=['test']) + post.save() + + # ListField operator BlogPost.objects.update(push__tags='mongo') post.reload() self.assertTrue('mongo' in post.tags) @@ -1900,13 +1945,23 @@ class QuerySetTest(unittest.TestCase): post.reload() self.assertEqual(post.tags.count('unique'), 1) - self.assertNotEqual(post.hits, None) - BlogPost.objects.update_one(unset__hits=1) - post.reload() - self.assertEqual(post.hits, None) + BlogPost.drop_collection() + + def test_update_unset(self): + class BlogPost(Document): + title = StringField() BlogPost.drop_collection() + post = BlogPost(title='garbage').save() + + self.assertNotEqual(post.title, None) + BlogPost.objects.update_one(unset__title=1) + post.reload() + self.assertEqual(post.title, None) + pymongo_doc = BlogPost.objects.as_pymongo().first() + self.assertNotIn('title', pymongo_doc) + @needs_mongodb_v26 def test_update_push_with_position(self): """Ensure that the 'push' update with position works properly. From cf9df548ca1280a34f6bf5bbce1971400bb07656 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 4 Sep 2018 19:18:40 +0200 Subject: [PATCH 32/85] reverted back to the StopIteration in queryset.next that one didnt have to be changed (test stalled) --- mongoengine/queryset/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index a4e64018..2c80218c 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -1481,7 +1481,7 @@ class BaseQuerySet(object): """Wrap the result in a :class:`~mongoengine.Document` object. """ if self._limit == 0 or self._none: - return + raise StopIteration raw_doc = self._cursor.next() From a25d127f368b7dfba1f697d34d27ee0b54a29df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 4 Sep 2018 20:51:06 +0200 Subject: [PATCH 33/85] Remove DictField.basecls related code, it is useless --- mongoengine/fields.py | 9 ++------- tests/queryset/queryset.py | 27 --------------------------- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 6994b315..91604a16 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -945,14 +945,9 @@ class DictField(ComplexBaseField): .. versionchanged:: 0.5 - Can now handle complex / varying types of data """ - def __init__(self, basecls=None, field=None, *args, **kwargs): + def __init__(self, field=None, *args, **kwargs): self.field = field self._auto_dereference = False - self.basecls = basecls or BaseField - - # XXX ValidationError raised outside of the "validate" method. - if not issubclass(self.basecls, BaseField): - self.error('DictField only accepts dict values') kwargs.setdefault('default', lambda: {}) super(DictField, self).__init__(*args, **kwargs) @@ -972,7 +967,7 @@ class DictField(ComplexBaseField): super(DictField, self).validate(value) def lookup_member(self, member_name): - return DictField(basecls=self.basecls, db_field=member_name) + return DictField(db_field=member_name) def prepare_query_value(self, op, value): match_operators = ['contains', 'icontains', 'startswith', diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 77308c26..c6aa3594 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -3618,33 +3618,6 @@ class QuerySetTest(unittest.TestCase): Group.drop_collection() - def test_dict_with_custom_baseclass(self): - """Ensure DictField working with custom base clases. - """ - class Test(Document): - testdict = DictField() - - Test.drop_collection() - - t = Test(testdict={'f': 'Value'}) - t.save() - - self.assertEqual( - Test.objects(testdict__f__startswith='Val').count(), 1) - self.assertEqual(Test.objects(testdict__f='Value').count(), 1) - Test.drop_collection() - - class Test(Document): - testdict = DictField(basecls=StringField) - - t = Test(testdict={'f': 'Value'}) - t.save() - - self.assertEqual(Test.objects(testdict__f='Value').count(), 1) - self.assertEqual( - Test.objects(testdict__f__startswith='Val').count(), 1) - Test.drop_collection() - def test_bulk(self): """Ensure bulk querying by object id returns a proper dict. """ From 193656e71be15c6b84c4d22bccf6fcf184893da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 4 Sep 2018 22:25:53 +0200 Subject: [PATCH 34/85] detect when EmbeddedDocumentListField receives an EmbeddedDocument instance instead of a list. It is a common mistake and the error wasn't meaningful (was fired from _from_son) (relates to #1464) --- mongoengine/base/fields.py | 7 ++++++- tests/fields/fields.py | 40 +++++++++++++++++++++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py index 69034d5d..906f7ff2 100644 --- a/mongoengine/base/fields.py +++ b/mongoengine/base/fields.py @@ -313,11 +313,16 @@ class ComplexBaseField(BaseField): if hasattr(value, 'to_python'): return value.to_python() + BaseDocument = _import_class('BaseDocument') + if isinstance(value, BaseDocument): + # Something is wrong, return the value as it is + return value + is_list = False if not hasattr(value, 'items'): try: is_list = True - value = {k: v for k, v in enumerate(value)} + value = {idx: v for idx, v in enumerate(value)} except TypeError: # Not iterable return the value return value diff --git a/tests/fields/fields.py b/tests/fields/fields.py index 362acec4..84877be3 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -3868,7 +3868,7 @@ class FieldTest(MongoDBTestCase): assert isinstance(doc.field, ToEmbedChild) assert doc.field == to_embed_child - def test_invalid_dict_value(self): + def test_dict_field_invalid_dict_value(self): class DictFieldTest(Document): dictionary = DictField(required=True) @@ -3882,6 +3882,22 @@ class FieldTest(MongoDBTestCase): test.dictionary # Just access to test getter self.assertRaises(ValidationError, test.validate) + def test_dict_field_raises_validation_error_if_wrongly_assign_embedded_doc(self): + class DictFieldTest(Document): + dictionary = DictField(required=True) + + DictFieldTest.drop_collection() + + class Embedded(EmbeddedDocument): + name = StringField() + + embed = Embedded(name='garbage') + doc = DictFieldTest(dictionary=embed) + with self.assertRaises(ValidationError) as ctx_err: + doc.validate() + self.assertIn("'dictionary'", str(ctx_err.exception)) + self.assertIn('Only dictionaries may be used in a DictField', str(ctx_err.exception)) + def test_cls_field(self): class Animal(Document): meta = {'allow_inheritance': True} @@ -3980,6 +3996,28 @@ class EmbeddedDocumentListFieldTestCase(MongoDBTestCase): self.Comments(author='user3', message='message1') ]).save() + def test_fails_upon_validate_if_provide_a_doc_instead_of_a_list_of_doc(self): + # Relates to Issue #1464 + comment = self.Comments(author='John') + + class Title(Document): + content = StringField() + + # Test with an embeddedDocument instead of a list(embeddedDocument) + # It's an edge case but it used to fail with a vague error, making it difficult to troubleshoot it + post = self.BlogPost(comments=comment) + with self.assertRaises(ValidationError) as ctx_err: + post.validate() + self.assertIn("'comments'", str(ctx_err.exception)) + self.assertIn('Only lists and tuples may be used in a list field', str(ctx_err.exception)) + + # Test with a Document + post = self.BlogPost(comments=Title(content='garbage')) + with self.assertRaises(ValidationError) as e: + post.validate() + self.assertIn("'comments'", str(ctx_err.exception)) + self.assertIn('Only lists and tuples may be used in a list field', str(ctx_err.exception)) + def test_no_keyword_filter(self): """ Tests the filter method of a List of Embedded Documents From 282b83ac08a3b4cf680b6f81627187b7a08a2889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 4 Sep 2018 23:48:07 +0200 Subject: [PATCH 35/85] Fix default value of ComplexDateTime + fixed descriptor .__get__ for class attribute --- mongoengine/base/fields.py | 3 +- mongoengine/fields.py | 20 ++- tests/fields/fields.py | 327 +++++++++++++++++++++---------------- 3 files changed, 199 insertions(+), 151 deletions(-) diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py index 69034d5d..d25d4305 100644 --- a/mongoengine/base/fields.py +++ b/mongoengine/base/fields.py @@ -55,7 +55,7 @@ class BaseField(object): field. Generally this is deprecated in favour of the `FIELD.validate` method :param choices: (optional) The valid choices - :param null: (optional) Is the field value can be null. If no and there is a default value + :param null: (optional) If the field value can be null. If no and there is a default value then the default value is set :param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False` means that uniqueness won't be enforced for `None` values @@ -130,7 +130,6 @@ class BaseField(object): def __set__(self, instance, value): """Descriptor for assigning a value to a field in a document. """ - # If setting to None and there is a default # Then set the value to the default value if value is None: diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 6994b315..bc632ac9 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -562,11 +562,15 @@ class ComplexDateTimeField(StringField): The `,` as the separator can be easily modified by passing the `separator` keyword when initializing the field. + Note: To default the field to the current datetime, use: DateTimeField(default=datetime.utcnow) + .. versionadded:: 0.5 """ def __init__(self, separator=',', **kwargs): - self.names = ['year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond'] + """ + :param separator: Allows to customize the separator used for storage (default ``,``) + """ self.separator = separator self.format = separator.join(['%Y', '%m', '%d', '%H', '%M', '%S', '%f']) super(ComplexDateTimeField, self).__init__(**kwargs) @@ -597,16 +601,20 @@ class ComplexDateTimeField(StringField): return datetime.datetime(*values) def __get__(self, instance, owner): + if instance is None: + return self + data = super(ComplexDateTimeField, self).__get__(instance, owner) - if data is None: - return None if self.null else datetime.datetime.now() - if isinstance(data, datetime.datetime): + + if isinstance(data, datetime.datetime) or data is None: return data return self._convert_from_string(data) def __set__(self, instance, value): - value = self._convert_from_datetime(value) if value else value - return super(ComplexDateTimeField, self).__set__(instance, value) + super(ComplexDateTimeField, self).__set__(instance, value) + value = instance._data[self.name] + if value is not None: + instance._data[self.name] = self._convert_from_datetime(value) def validate(self, value): value = self.to_python(value) diff --git a/tests/fields/fields.py b/tests/fields/fields.py index 362acec4..4bfd7c74 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -264,12 +264,11 @@ class FieldTest(MongoDBTestCase): # Retrive data from db and verify it. ret = HandleNoneFields.objects.all()[0] - self.assertEqual(ret.str_fld, None) - self.assertEqual(ret.int_fld, None) - self.assertEqual(ret.flt_fld, None) + self.assertIsNone(ret.str_fld) + self.assertIsNone(ret.int_fld) + self.assertIsNone(ret.flt_fld) - # Return current time if retrived value is None. - self.assertTrue(isinstance(ret.comp_dt_fld, datetime.datetime)) + self.assertIsNone(ret.comp_dt_fld) def test_not_required_handles_none_from_database(self): """Ensure that every field can handle null values from the @@ -287,7 +286,7 @@ class FieldTest(MongoDBTestCase): doc.str_fld = u'spam ham egg' doc.int_fld = 42 doc.flt_fld = 4.2 - doc.com_dt_fld = datetime.datetime.utcnow() + doc.comp_dt_fld = datetime.datetime.utcnow() doc.save() # Unset all the fields @@ -302,12 +301,10 @@ class FieldTest(MongoDBTestCase): # Retrive data from db and verify it. ret = HandleNoneFields.objects.first() - self.assertEqual(ret.str_fld, None) - self.assertEqual(ret.int_fld, None) - self.assertEqual(ret.flt_fld, None) - - # ComplexDateTimeField returns current time if retrived value is None. - self.assertTrue(isinstance(ret.comp_dt_fld, datetime.datetime)) + self.assertIsNone(ret.str_fld) + self.assertIsNone(ret.int_fld) + self.assertIsNone(ret.flt_fld) + self.assertIsNone(ret.comp_dt_fld) # Retrieved object shouldn't pass validation when a re-save is # attempted. @@ -928,137 +925,6 @@ class FieldTest(MongoDBTestCase): logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1)) self.assertEqual(logs.count(), 10) - def test_complexdatetime_storage(self): - """Tests for complex datetime fields - which can handle - microseconds without rounding. - """ - class LogEntry(Document): - date = ComplexDateTimeField() - date_with_dots = ComplexDateTimeField(separator='.') - - LogEntry.drop_collection() - - # Post UTC - microseconds are rounded (down) nearest millisecond and - # dropped - with default datetimefields - d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 999) - log = LogEntry() - log.date = d1 - log.save() - log.reload() - self.assertEqual(log.date, d1) - - # Post UTC - microseconds are rounded (down) nearest millisecond - with - # default datetimefields - d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9999) - log.date = d1 - log.save() - log.reload() - self.assertEqual(log.date, d1) - - # Pre UTC dates microseconds below 1000 are dropped - with default - # datetimefields - d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, 999) - log.date = d1 - log.save() - log.reload() - self.assertEqual(log.date, d1) - - # Pre UTC microseconds above 1000 is wonky - with default datetimefields - # log.date has an invalid microsecond value so I can't construct - # a date to compare. - for i in range(1001, 3113, 33): - d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, i) - log.date = d1 - log.save() - log.reload() - self.assertEqual(log.date, d1) - log1 = LogEntry.objects.get(date=d1) - self.assertEqual(log, log1) - - # Test string padding - microsecond = map(int, [math.pow(10, x) for x in range(6)]) - mm = dd = hh = ii = ss = [1, 10] - - for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond): - stored = LogEntry(date=datetime.datetime(*values)).to_mongo()['date'] - self.assertTrue(re.match('^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$', stored) is not None) - - # Test separator - stored = LogEntry(date_with_dots=datetime.datetime(2014, 1, 1)).to_mongo()['date_with_dots'] - self.assertTrue(re.match('^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$', stored) is not None) - - def test_complexdatetime_usage(self): - """Tests for complex datetime fields - which can handle - microseconds without rounding. - """ - class LogEntry(Document): - date = ComplexDateTimeField() - - LogEntry.drop_collection() - - d1 = datetime.datetime(1950, 1, 1, 0, 0, 1, 999) - log = LogEntry() - log.date = d1 - log.save() - - log1 = LogEntry.objects.get(date=d1) - self.assertEqual(log, log1) - - # create extra 59 log entries for a total of 60 - for i in range(1951, 2010): - d = datetime.datetime(i, 1, 1, 0, 0, 1, 999) - LogEntry(date=d).save() - - self.assertEqual(LogEntry.objects.count(), 60) - - # Test ordering - logs = LogEntry.objects.order_by("date") - i = 0 - while i < 59: - self.assertTrue(logs[i].date <= logs[i + 1].date) - i += 1 - - logs = LogEntry.objects.order_by("-date") - i = 0 - while i < 59: - self.assertTrue(logs[i].date >= logs[i + 1].date) - i += 1 - - # Test searching - logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1)) - self.assertEqual(logs.count(), 30) - - logs = LogEntry.objects.filter(date__lte=datetime.datetime(1980, 1, 1)) - self.assertEqual(logs.count(), 30) - - logs = LogEntry.objects.filter( - date__lte=datetime.datetime(2011, 1, 1), - date__gte=datetime.datetime(2000, 1, 1), - ) - self.assertEqual(logs.count(), 10) - - LogEntry.drop_collection() - - # Test microsecond-level ordering/filtering - for microsecond in (99, 999, 9999, 10000): - LogEntry( - date=datetime.datetime(2015, 1, 1, 0, 0, 0, microsecond) - ).save() - - logs = list(LogEntry.objects.order_by('date')) - for next_idx, log in enumerate(logs[:-1], start=1): - next_log = logs[next_idx] - self.assertTrue(log.date < next_log.date) - - logs = list(LogEntry.objects.order_by('-date')) - for next_idx, log in enumerate(logs[:-1], start=1): - next_log = logs[next_idx] - self.assertTrue(log.date > next_log.date) - - logs = LogEntry.objects.filter( - date__lte=datetime.datetime(2015, 1, 1, 0, 0, 0, 10000)) - self.assertEqual(logs.count(), 4) - def test_list_validation(self): """Ensure that a list field only accepts lists with valid elements.""" AccessLevelChoices = ( @@ -5389,5 +5255,180 @@ class GenericLazyReferenceFieldTest(MongoDBTestCase): check_fields_type(occ) +class ComplexDateTimeFieldTest(MongoDBTestCase): + def test_complexdatetime_storage(self): + """Tests for complex datetime fields - which can handle + microseconds without rounding. + """ + class LogEntry(Document): + date = ComplexDateTimeField() + date_with_dots = ComplexDateTimeField(separator='.') + + LogEntry.drop_collection() + + # Post UTC - microseconds are rounded (down) nearest millisecond and + # dropped - with default datetimefields + d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 999) + log = LogEntry() + log.date = d1 + log.save() + log.reload() + self.assertEqual(log.date, d1) + + # Post UTC - microseconds are rounded (down) nearest millisecond - with + # default datetimefields + d1 = datetime.datetime(1970, 1, 1, 0, 0, 1, 9999) + log.date = d1 + log.save() + log.reload() + self.assertEqual(log.date, d1) + + # Pre UTC dates microseconds below 1000 are dropped - with default + # datetimefields + d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, 999) + log.date = d1 + log.save() + log.reload() + self.assertEqual(log.date, d1) + + # Pre UTC microseconds above 1000 is wonky - with default datetimefields + # log.date has an invalid microsecond value so I can't construct + # a date to compare. + for i in range(1001, 3113, 33): + d1 = datetime.datetime(1969, 12, 31, 23, 59, 59, i) + log.date = d1 + log.save() + log.reload() + self.assertEqual(log.date, d1) + log1 = LogEntry.objects.get(date=d1) + self.assertEqual(log, log1) + + # Test string padding + microsecond = map(int, [math.pow(10, x) for x in range(6)]) + mm = dd = hh = ii = ss = [1, 10] + + for values in itertools.product([2014], mm, dd, hh, ii, ss, microsecond): + stored = LogEntry(date=datetime.datetime(*values)).to_mongo()['date'] + self.assertTrue(re.match('^\d{4},\d{2},\d{2},\d{2},\d{2},\d{2},\d{6}$', stored) is not None) + + # Test separator + stored = LogEntry(date_with_dots=datetime.datetime(2014, 1, 1)).to_mongo()['date_with_dots'] + self.assertTrue(re.match('^\d{4}.\d{2}.\d{2}.\d{2}.\d{2}.\d{2}.\d{6}$', stored) is not None) + + def test_complexdatetime_usage(self): + """Tests for complex datetime fields - which can handle + microseconds without rounding. + """ + class LogEntry(Document): + date = ComplexDateTimeField() + + LogEntry.drop_collection() + + d1 = datetime.datetime(1950, 1, 1, 0, 0, 1, 999) + log = LogEntry() + log.date = d1 + log.save() + + log1 = LogEntry.objects.get(date=d1) + self.assertEqual(log, log1) + + # create extra 59 log entries for a total of 60 + for i in range(1951, 2010): + d = datetime.datetime(i, 1, 1, 0, 0, 1, 999) + LogEntry(date=d).save() + + self.assertEqual(LogEntry.objects.count(), 60) + + # Test ordering + logs = LogEntry.objects.order_by("date") + i = 0 + while i < 59: + self.assertTrue(logs[i].date <= logs[i + 1].date) + i += 1 + + logs = LogEntry.objects.order_by("-date") + i = 0 + while i < 59: + self.assertTrue(logs[i].date >= logs[i + 1].date) + i += 1 + + # Test searching + logs = LogEntry.objects.filter(date__gte=datetime.datetime(1980, 1, 1)) + self.assertEqual(logs.count(), 30) + + logs = LogEntry.objects.filter(date__lte=datetime.datetime(1980, 1, 1)) + self.assertEqual(logs.count(), 30) + + logs = LogEntry.objects.filter( + date__lte=datetime.datetime(2011, 1, 1), + date__gte=datetime.datetime(2000, 1, 1), + ) + self.assertEqual(logs.count(), 10) + + LogEntry.drop_collection() + + # Test microsecond-level ordering/filtering + for microsecond in (99, 999, 9999, 10000): + LogEntry( + date=datetime.datetime(2015, 1, 1, 0, 0, 0, microsecond) + ).save() + + logs = list(LogEntry.objects.order_by('date')) + for next_idx, log in enumerate(logs[:-1], start=1): + next_log = logs[next_idx] + self.assertTrue(log.date < next_log.date) + + logs = list(LogEntry.objects.order_by('-date')) + for next_idx, log in enumerate(logs[:-1], start=1): + next_log = logs[next_idx] + self.assertTrue(log.date > next_log.date) + + logs = LogEntry.objects.filter( + date__lte=datetime.datetime(2015, 1, 1, 0, 0, 0, 10000)) + self.assertEqual(logs.count(), 4) + + def test_no_default_value(self): + class Log(Document): + timestamp = ComplexDateTimeField() + + Log.drop_collection() + + log = Log() + self.assertIsNone(log.timestamp) + log.save() + + fetched_log = Log.objects.with_id(log.id) + self.assertIsNone(fetched_log.timestamp) + + def test_default_static_value(self): + NOW = datetime.datetime.utcnow() + class Log(Document): + timestamp = ComplexDateTimeField(default=NOW) + + Log.drop_collection() + + log = Log() + self.assertEqual(log.timestamp, NOW) + log.save() + + fetched_log = Log.objects.with_id(log.id) + self.assertEqual(fetched_log.timestamp, NOW) + + def test_default_callable(self): + NOW = datetime.datetime.utcnow() + + class Log(Document): + timestamp = ComplexDateTimeField(default=datetime.datetime.utcnow) + + Log.drop_collection() + + log = Log() + self.assertGreaterEqual(log.timestamp, NOW) + log.save() + + fetched_log = Log.objects.with_id(log.id) + self.assertGreaterEqual(fetched_log.timestamp, NOW) + + if __name__ == '__main__': unittest.main() From b15673c52566dbf1fb1f29005901fda35bd97373 Mon Sep 17 00:00:00 2001 From: Sergey Tereschenko Date: Wed, 5 Sep 2018 11:53:15 +0300 Subject: [PATCH 36/85] fixed TypeError on translated choices join expect strings, but if we use django ugettext_lazy like this: choices=[(1, _("One")), (2, _("Two"))] there will be TypeError: sequence item 0: expected string, __proxy__ found this commif fixes error by converting label to string --- mongoengine/base/document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 85906a3e..aaf99ace 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -1091,6 +1091,6 @@ class BaseDocument(object): sep = getattr(field, 'display_sep', ' ') values = value if field.__class__.__name__ in ('ListField', 'SortedListField') else [value] return sep.join([ - dict(field.choices).get(val, val) + six.text_type(dict(field.choices).get(val, val)) for val in values or []]) return value From 003827e91600dc49c4504eba4cf4e419b406a820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Thu, 6 Sep 2018 21:47:06 +0200 Subject: [PATCH 37/85] rewrote some poorly written assertions like: assertTrue(isinstance(a, b)) assertTrue(a==b) assertTrue(a!=b) assertTrue(a in b) --- tests/document/class_methods.py | 8 +- tests/document/delta.py | 4 +- tests/document/dynamic.py | 6 +- tests/document/indexes.py | 36 ++++----- tests/document/inheritance.py | 12 +-- tests/document/instance.py | 95 ++++++++++++----------- tests/document/validation.py | 28 +++---- tests/fields/fields.py | 67 ++++++++-------- tests/fields/file_tests.py | 18 ++--- tests/fields/geo.py | 16 ++-- tests/queryset/field_list.py | 2 +- tests/queryset/geo.py | 12 +-- tests/queryset/queryset.py | 131 ++++++++++++++++---------------- tests/queryset/transform.py | 34 ++++----- tests/queryset/visitor.py | 2 +- tests/test_connection.py | 60 +++++++-------- tests/test_context_managers.py | 24 +++--- tests/test_dereference.py | 48 ++++++------ 18 files changed, 298 insertions(+), 305 deletions(-) diff --git a/tests/document/class_methods.py b/tests/document/class_methods.py index 2fab1f72..5289e483 100644 --- a/tests/document/class_methods.py +++ b/tests/document/class_methods.py @@ -66,10 +66,10 @@ class ClassMethodsTest(unittest.TestCase): """ collection_name = 'person' self.Person(name='Test').save() - self.assertTrue(collection_name in self.db.collection_names()) + self.assertIn(collection_name, self.db.collection_names()) self.Person.drop_collection() - self.assertFalse(collection_name in self.db.collection_names()) + self.assertNotIn(collection_name, self.db.collection_names()) def test_register_delete_rule(self): """Ensure that register delete rule adds a delete rule to the document @@ -340,7 +340,7 @@ class ClassMethodsTest(unittest.TestCase): meta = {'collection': collection_name} Person(name="Test User").save() - self.assertTrue(collection_name in self.db.collection_names()) + self.assertIn(collection_name, self.db.collection_names()) user_obj = self.db[collection_name].find_one() self.assertEqual(user_obj['name'], "Test User") @@ -349,7 +349,7 @@ class ClassMethodsTest(unittest.TestCase): self.assertEqual(user_obj.name, "Test User") Person.drop_collection() - self.assertFalse(collection_name in self.db.collection_names()) + self.assertNotIn(collection_name, self.db.collection_names()) def test_collection_name_and_primary(self): """Ensure that a collection with a specified name may be used. diff --git a/tests/document/delta.py b/tests/document/delta.py index add4fe8d..30296956 100644 --- a/tests/document/delta.py +++ b/tests/document/delta.py @@ -694,7 +694,7 @@ class DeltaTest(unittest.TestCase): organization.employees.append(person) updates, removals = organization._delta() self.assertEqual({}, removals) - self.assertTrue('employees' in updates) + self.assertIn('employees', updates) def test_delta_with_dbref_false(self): person, organization, employee = self.circular_reference_deltas_2(Document, Document, False) @@ -709,7 +709,7 @@ class DeltaTest(unittest.TestCase): organization.employees.append(person) updates, removals = organization._delta() self.assertEqual({}, removals) - self.assertTrue('employees' in updates) + self.assertIn('employees', updates) def test_nested_nested_fields_mark_as_changed(self): class EmbeddedDoc(EmbeddedDocument): diff --git a/tests/document/dynamic.py b/tests/document/dynamic.py index a478df42..94cea134 100644 --- a/tests/document/dynamic.py +++ b/tests/document/dynamic.py @@ -174,8 +174,8 @@ class DynamicTest(unittest.TestCase): Employee.drop_collection() - self.assertTrue('name' in Employee._fields) - self.assertTrue('salary' in Employee._fields) + self.assertIn('name', Employee._fields) + self.assertIn('salary', Employee._fields) self.assertEqual(Employee._get_collection_name(), self.Person._get_collection_name()) @@ -189,7 +189,7 @@ class DynamicTest(unittest.TestCase): self.assertEqual(1, Employee.objects(age=20).count()) joe_bloggs = self.Person.objects.first() - self.assertTrue(isinstance(joe_bloggs, Employee)) + self.assertIsInstance(joe_bloggs, Employee) def test_embedded_dynamic_document(self): """Test dynamic embedded documents""" diff --git a/tests/document/indexes.py b/tests/document/indexes.py index 58e09199..1cbb4ec3 100644 --- a/tests/document/indexes.py +++ b/tests/document/indexes.py @@ -70,7 +70,7 @@ class IndexesTest(unittest.TestCase): self.assertEqual(len(info), 4) info = [value['key'] for key, value in info.iteritems()] for expected in expected_specs: - self.assertTrue(expected['fields'] in info) + self.assertIn(expected['fields'], info) def _index_test_inheritance(self, InheritFrom): @@ -102,7 +102,7 @@ class IndexesTest(unittest.TestCase): self.assertEqual(len(info), 4) info = [value['key'] for key, value in info.iteritems()] for expected in expected_specs: - self.assertTrue(expected['fields'] in info) + self.assertIn(expected['fields'], info) class ExtendedBlogPost(BlogPost): title = StringField() @@ -117,7 +117,7 @@ class IndexesTest(unittest.TestCase): info = ExtendedBlogPost.objects._collection.index_information() info = [value['key'] for key, value in info.iteritems()] for expected in expected_specs: - self.assertTrue(expected['fields'] in info) + self.assertIn(expected['fields'], info) def test_indexes_document_inheritance(self): """Ensure that indexes are used when meta[indexes] is specified for @@ -226,7 +226,7 @@ class IndexesTest(unittest.TestCase): list(Person.objects) info = Person.objects._collection.index_information() info = [value['key'] for key, value in info.iteritems()] - self.assertTrue([('rank.title', 1)] in info) + self.assertIn([('rank.title', 1)], info) def test_explicit_geo2d_index(self): """Ensure that geo2d indexes work when created via meta[indexes] @@ -246,7 +246,7 @@ class IndexesTest(unittest.TestCase): Place.ensure_indexes() info = Place._get_collection().index_information() info = [value['key'] for key, value in info.iteritems()] - self.assertTrue([('location.point', '2d')] in info) + self.assertIn([('location.point', '2d')], info) def test_explicit_geo2d_index_embedded(self): """Ensure that geo2d indexes work when created via meta[indexes] @@ -269,7 +269,7 @@ class IndexesTest(unittest.TestCase): Place.ensure_indexes() info = Place._get_collection().index_information() info = [value['key'] for key, value in info.iteritems()] - self.assertTrue([('current.location.point', '2d')] in info) + self.assertIn([('current.location.point', '2d')], info) def test_explicit_geosphere_index(self): """Ensure that geosphere indexes work when created via meta[indexes] @@ -289,7 +289,7 @@ class IndexesTest(unittest.TestCase): Place.ensure_indexes() info = Place._get_collection().index_information() info = [value['key'] for key, value in info.iteritems()] - self.assertTrue([('location.point', '2dsphere')] in info) + self.assertIn([('location.point', '2dsphere')], info) def test_explicit_geohaystack_index(self): """Ensure that geohaystack indexes work when created via meta[indexes] @@ -311,7 +311,7 @@ class IndexesTest(unittest.TestCase): Place.ensure_indexes() info = Place._get_collection().index_information() info = [value['key'] for key, value in info.iteritems()] - self.assertTrue([('location.point', 'geoHaystack')] in info) + self.assertIn([('location.point', 'geoHaystack')], info) def test_create_geohaystack_index(self): """Ensure that geohaystack indexes can be created @@ -323,7 +323,7 @@ class IndexesTest(unittest.TestCase): Place.create_index({'fields': (')location.point', 'name')}, bucketSize=10) info = Place._get_collection().index_information() info = [value['key'] for key, value in info.iteritems()] - self.assertTrue([('location.point', 'geoHaystack'), ('name', 1)] in info) + self.assertIn([('location.point', 'geoHaystack'), ('name', 1)], info) def test_dictionary_indexes(self): """Ensure that indexes are used when meta[indexes] contains @@ -356,7 +356,7 @@ class IndexesTest(unittest.TestCase): value.get('unique', False), value.get('sparse', False)) for key, value in info.iteritems()] - self.assertTrue(([('addDate', -1)], True, True) in info) + self.assertIn(([('addDate', -1)], True, True), info) BlogPost.drop_collection() @@ -803,7 +803,7 @@ class IndexesTest(unittest.TestCase): info = BlogPost.objects._collection.index_information() info = [value['key'] for key, value in info.iteritems()] index_item = [('_id', 1), ('comments.comment_id', 1)] - self.assertTrue(index_item in info) + self.assertIn(index_item, info) def test_compound_key_embedded(self): @@ -850,8 +850,8 @@ class IndexesTest(unittest.TestCase): info = MyDoc.objects._collection.index_information() info = [value['key'] for key, value in info.iteritems()] - self.assertTrue([('provider_ids.foo', 1)] in info) - self.assertTrue([('provider_ids.bar', 1)] in info) + self.assertIn([('provider_ids.foo', 1)], info) + self.assertIn([('provider_ids.bar', 1)], info) def test_sparse_compound_indexes(self): @@ -876,9 +876,9 @@ class IndexesTest(unittest.TestCase): } indexes = Book.objects._collection.index_information() - self.assertTrue("title_text" in indexes) + self.assertIn("title_text", indexes) key = indexes["title_text"]["key"] - self.assertTrue(('_fts', 'text') in key) + self.assertIn(('_fts', 'text'), key) def test_hashed_indexes(self): @@ -889,8 +889,8 @@ class IndexesTest(unittest.TestCase): } indexes = Book.objects._collection.index_information() - self.assertTrue("ref_id_hashed" in indexes) - self.assertTrue(('ref_id', 'hashed') in indexes["ref_id_hashed"]["key"]) + self.assertIn("ref_id_hashed", indexes) + self.assertIn(('ref_id', 'hashed'), indexes["ref_id_hashed"]["key"]) def test_indexes_after_database_drop(self): """ @@ -1013,7 +1013,7 @@ class IndexesTest(unittest.TestCase): TestDoc.ensure_indexes() index_info = TestDoc._get_collection().index_information() - self.assertTrue('shard_1_1__cls_1_txt_1_1' in index_info) + self.assertIn('shard_1_1__cls_1_txt_1_1', index_info) if __name__ == '__main__': diff --git a/tests/document/inheritance.py b/tests/document/inheritance.py index 2897e1d1..b2ab1b52 100644 --- a/tests/document/inheritance.py +++ b/tests/document/inheritance.py @@ -268,7 +268,7 @@ class InheritanceTest(unittest.TestCase): collection = self.db[Animal._get_collection_name()] obj = collection.find_one() - self.assertFalse('_cls' in obj) + self.assertNotIn('_cls', obj) def test_cant_turn_off_inheritance_on_subclass(self): """Ensure if inheritance is on in a subclass you cant turn it off. @@ -298,7 +298,7 @@ class InheritanceTest(unittest.TestCase): # Check that _cls isn't present in simple documents doc = Animal(name='dog') - self.assertFalse('_cls' in doc.to_mongo()) + self.assertNotIn('_cls', doc.to_mongo()) def test_abstract_handle_ids_in_metaclass_properly(self): @@ -374,14 +374,14 @@ class InheritanceTest(unittest.TestCase): pass doc = Comment(content='test') - self.assertFalse('_cls' in doc.to_mongo()) + self.assertNotIn('_cls', doc.to_mongo()) class Comment(EmbeddedDocument): content = StringField() meta = {'allow_inheritance': True} doc = Comment(content='test') - self.assertTrue('_cls' in doc.to_mongo()) + self.assertIn('_cls', doc.to_mongo()) def test_document_inheritance(self): """Ensure mutliple inheritance of abstract documents @@ -434,8 +434,8 @@ class InheritanceTest(unittest.TestCase): for cls in [Animal, Fish, Guppy]: self.assertEqual(cls._meta[k], v) - self.assertFalse('collection' in Animal._meta) - self.assertFalse('collection' in Mammal._meta) + self.assertNotIn('collection', Animal._meta) + self.assertNotIn('collection', Mammal._meta) self.assertEqual(Animal._get_collection_name(), None) self.assertEqual(Mammal._get_collection_name(), None) diff --git a/tests/document/instance.py b/tests/document/instance.py index cffe4f30..e637b3e6 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -357,7 +357,7 @@ class InstanceTest(MongoDBTestCase): user_son = User.objects._collection.find_one() self.assertEqual(user_son['_id'], 'test') - self.assertTrue('username' not in user_son['_id']) + self.assertNotIn('username', user_son['_id']) User.drop_collection() @@ -370,7 +370,7 @@ class InstanceTest(MongoDBTestCase): user_son = User.objects._collection.find_one() self.assertEqual(user_son['_id'], 'mongo') - self.assertTrue('username' not in user_son['_id']) + self.assertNotIn('username', user_son['_id']) def test_document_not_registered(self): class Place(Document): @@ -594,10 +594,10 @@ class InstanceTest(MongoDBTestCase): # Length = length(assigned fields + id) self.assertEqual(len(person), 5) - self.assertTrue('age' in person) + self.assertIn('age', person) person.age = None - self.assertFalse('age' in person) - self.assertFalse('nationality' in person) + self.assertNotIn('age', person) + self.assertNotIn('nationality', person) def test_embedded_document_to_mongo(self): class Person(EmbeddedDocument): @@ -627,8 +627,8 @@ class InstanceTest(MongoDBTestCase): class Comment(EmbeddedDocument): content = StringField() - self.assertTrue('content' in Comment._fields) - self.assertFalse('id' in Comment._fields) + self.assertIn('content', Comment._fields) + self.assertNotIn('id', Comment._fields) def test_embedded_document_instance(self): """Ensure that embedded documents can reference parent instance.""" @@ -1455,9 +1455,9 @@ class InstanceTest(MongoDBTestCase): user = User.objects.first() # Even if stored as ObjectId's internally mongoengine uses DBRefs # As ObjectId's aren't automatically derefenced - self.assertTrue(isinstance(user._data['orgs'][0], DBRef)) - self.assertTrue(isinstance(user.orgs[0], Organization)) - self.assertTrue(isinstance(user._data['orgs'][0], Organization)) + self.assertIsInstance(user._data['orgs'][0], DBRef) + self.assertIsInstance(user.orgs[0], Organization) + self.assertIsInstance(user._data['orgs'][0], Organization) # Changing a value with query_counter() as q: @@ -1837,9 +1837,8 @@ class InstanceTest(MongoDBTestCase): post_obj = BlogPost.objects.first() # Test laziness - self.assertTrue(isinstance(post_obj._data['author'], - bson.DBRef)) - self.assertTrue(isinstance(post_obj.author, self.Person)) + self.assertIsInstance(post_obj._data['author'], bson.DBRef) + self.assertIsInstance(post_obj.author, self.Person) self.assertEqual(post_obj.author.name, 'Test User') # Ensure that the dereferenced object may be changed and saved @@ -2245,12 +2244,12 @@ class InstanceTest(MongoDBTestCase): # Make sure docs are properly identified in a list (__eq__ is used # for the comparison). all_user_list = list(User.objects.all()) - self.assertTrue(u1 in all_user_list) - self.assertTrue(u2 in all_user_list) - self.assertTrue(u3 in all_user_list) - self.assertTrue(u4 not in all_user_list) # New object - self.assertTrue(b1 not in all_user_list) # Other object - self.assertTrue(b2 not in all_user_list) # Other object + self.assertIn(u1, all_user_list) + self.assertIn(u2, all_user_list) + self.assertIn(u3, all_user_list) + self.assertNotIn(u4, all_user_list) # New object + self.assertNotIn(b1, all_user_list) # Other object + self.assertNotIn(b2, all_user_list) # Other object # Make sure docs can be used as keys in a dict (__hash__ is used # for hashing the docs). @@ -2268,10 +2267,10 @@ class InstanceTest(MongoDBTestCase): # Make sure docs are properly identified in a set (__hash__ is used # for hashing the docs). all_user_set = set(User.objects.all()) - self.assertTrue(u1 in all_user_set) - self.assertTrue(u4 not in all_user_set) - self.assertTrue(b1 not in all_user_list) - self.assertTrue(b2 not in all_user_list) + self.assertIn(u1, all_user_set) + self.assertNotIn(u4, all_user_set) + self.assertNotIn(b1, all_user_list) + self.assertNotIn(b2, all_user_list) # Make sure duplicate docs aren't accepted in the set self.assertEqual(len(all_user_set), 3) @@ -2972,7 +2971,7 @@ class InstanceTest(MongoDBTestCase): Person(name="Harry Potter").save() person = Person.objects.first() - self.assertTrue('id' in person._data.keys()) + self.assertIn('id', person._data.keys()) self.assertEqual(person._data.get('id'), person.id) def test_complex_nesting_document_and_embedded_document(self): @@ -3064,36 +3063,36 @@ class InstanceTest(MongoDBTestCase): dbref2 = f._data['test2'] obj2 = f.test2 - self.assertTrue(isinstance(dbref2, DBRef)) - self.assertTrue(isinstance(obj2, Test2)) - self.assertTrue(obj2.id == dbref2.id) - self.assertTrue(obj2 == dbref2) - self.assertTrue(dbref2 == obj2) + self.assertIsInstance(dbref2, DBRef) + self.assertIsInstance(obj2, Test2) + self.assertEqual(obj2.id, dbref2.id) + self.assertEqual(obj2, dbref2) + self.assertEqual(dbref2, obj2) dbref3 = f._data['test3'] obj3 = f.test3 - self.assertTrue(isinstance(dbref3, DBRef)) - self.assertTrue(isinstance(obj3, Test3)) - self.assertTrue(obj3.id == dbref3.id) - self.assertTrue(obj3 == dbref3) - self.assertTrue(dbref3 == obj3) + self.assertIsInstance(dbref3, DBRef) + self.assertIsInstance(obj3, Test3) + self.assertEqual(obj3.id, dbref3.id) + self.assertEqual(obj3, dbref3) + self.assertEqual(dbref3, obj3) - self.assertTrue(obj2.id == obj3.id) - self.assertTrue(dbref2.id == dbref3.id) - self.assertFalse(dbref2 == dbref3) - self.assertFalse(dbref3 == dbref2) - self.assertTrue(dbref2 != dbref3) - self.assertTrue(dbref3 != dbref2) + self.assertEqual(obj2.id, obj3.id) + self.assertEqual(dbref2.id, dbref3.id) + self.assertNotEqual(dbref2, dbref3) + self.assertNotEqual(dbref3, dbref2) + self.assertNotEqual(dbref2, dbref3) + self.assertNotEqual(dbref3, dbref2) - self.assertFalse(obj2 == dbref3) - self.assertFalse(dbref3 == obj2) - self.assertTrue(obj2 != dbref3) - self.assertTrue(dbref3 != obj2) + self.assertNotEqual(obj2, dbref3) + self.assertNotEqual(dbref3, obj2) + self.assertNotEqual(obj2, dbref3) + self.assertNotEqual(dbref3, obj2) - self.assertFalse(obj3 == dbref2) - self.assertFalse(dbref2 == obj3) - self.assertTrue(obj3 != dbref2) - self.assertTrue(dbref2 != obj3) + self.assertNotEqual(obj3, dbref2) + self.assertNotEqual(dbref2, obj3) + self.assertNotEqual(obj3, dbref2) + self.assertNotEqual(dbref2, obj3) def test_default_values(self): class Person(Document): diff --git a/tests/document/validation.py b/tests/document/validation.py index 105bc8b0..30a285b2 100644 --- a/tests/document/validation.py +++ b/tests/document/validation.py @@ -20,16 +20,16 @@ class ValidatorErrorTest(unittest.TestCase): # 1st level error schema error.errors = {'1st': ValidationError('bad 1st'), } - self.assertTrue('1st' in error.to_dict()) + self.assertIn('1st', error.to_dict()) self.assertEqual(error.to_dict()['1st'], 'bad 1st') # 2nd level error schema error.errors = {'1st': ValidationError('bad 1st', errors={ '2nd': ValidationError('bad 2nd'), })} - self.assertTrue('1st' in error.to_dict()) - self.assertTrue(isinstance(error.to_dict()['1st'], dict)) - self.assertTrue('2nd' in error.to_dict()['1st']) + self.assertIn('1st', error.to_dict()) + self.assertIsInstance(error.to_dict()['1st'], dict) + self.assertIn('2nd', error.to_dict()['1st']) self.assertEqual(error.to_dict()['1st']['2nd'], 'bad 2nd') # moar levels @@ -40,10 +40,10 @@ class ValidatorErrorTest(unittest.TestCase): }), }), })} - self.assertTrue('1st' in error.to_dict()) - self.assertTrue('2nd' in error.to_dict()['1st']) - self.assertTrue('3rd' in error.to_dict()['1st']['2nd']) - self.assertTrue('4th' in error.to_dict()['1st']['2nd']['3rd']) + self.assertIn('1st', error.to_dict()) + self.assertIn('2nd', error.to_dict()['1st']) + self.assertIn('3rd', error.to_dict()['1st']['2nd']) + self.assertIn('4th', error.to_dict()['1st']['2nd']['3rd']) self.assertEqual(error.to_dict()['1st']['2nd']['3rd']['4th'], 'Inception') @@ -58,7 +58,7 @@ class ValidatorErrorTest(unittest.TestCase): try: User().validate() except ValidationError as e: - self.assertTrue("User:None" in e.message) + self.assertIn("User:None", e.message) self.assertEqual(e.to_dict(), { 'username': 'Field is required', 'name': 'Field is required'}) @@ -68,7 +68,7 @@ class ValidatorErrorTest(unittest.TestCase): try: user.save() except ValidationError as e: - self.assertTrue("User:RossC0" in e.message) + self.assertIn("User:RossC0", e.message) self.assertEqual(e.to_dict(), { 'name': 'Field is required'}) @@ -116,7 +116,7 @@ class ValidatorErrorTest(unittest.TestCase): try: Doc(id="bad").validate() except ValidationError as e: - self.assertTrue("SubDoc:None" in e.message) + self.assertIn("SubDoc:None", e.message) self.assertEqual(e.to_dict(), { "e": {'val': 'OK could not be converted to int'}}) @@ -127,14 +127,14 @@ class ValidatorErrorTest(unittest.TestCase): doc = Doc.objects.first() keys = doc._data.keys() self.assertEqual(2, len(keys)) - self.assertTrue('e' in keys) - self.assertTrue('id' in keys) + self.assertIn('e', keys) + self.assertIn('id', keys) doc.e.val = "OK" try: doc.save() except ValidationError as e: - self.assertTrue("Doc:test" in e.message) + self.assertIn("Doc:test", e.message) self.assertEqual(e.to_dict(), { "e": {'val': 'OK could not be converted to int'}}) diff --git a/tests/fields/fields.py b/tests/fields/fields.py index 4bfd7c74..d6df061d 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -175,7 +175,7 @@ class FieldTest(MongoDBTestCase): self.assertEqual(person.name, None) self.assertEqual(person.age, 30) self.assertEqual(person.userid, 'test') - self.assertTrue(isinstance(person.created, datetime.datetime)) + self.assertIsInstance(person.created, datetime.datetime) self.assertEqual(person._data['name'], person.name) self.assertEqual(person._data['age'], person.age) @@ -211,7 +211,7 @@ class FieldTest(MongoDBTestCase): self.assertEqual(person.name, None) self.assertEqual(person.age, 30) self.assertEqual(person.userid, 'test') - self.assertTrue(isinstance(person.created, datetime.datetime)) + self.assertIsInstance(person.created, datetime.datetime) self.assertNotEqual(person.created, datetime.datetime(2014, 6, 12)) self.assertEqual(person._data['name'], person.name) @@ -1602,8 +1602,8 @@ class FieldTest(MongoDBTestCase): e.save() e2 = Simple.objects.get(id=e.id) - self.assertTrue(isinstance(e2.mapping[0], StringSetting)) - self.assertTrue(isinstance(e2.mapping[1], IntegerSetting)) + self.assertIsInstance(e2.mapping[0], StringSetting) + self.assertIsInstance(e2.mapping[1], IntegerSetting) # Test querying self.assertEqual( @@ -1772,8 +1772,8 @@ class FieldTest(MongoDBTestCase): e.save() e2 = Simple.objects.get(id=e.id) - self.assertTrue(isinstance(e2.mapping['somestring'], StringSetting)) - self.assertTrue(isinstance(e2.mapping['someint'], IntegerSetting)) + self.assertIsInstance(e2.mapping['somestring'], StringSetting) + self.assertIsInstance(e2.mapping['someint'], IntegerSetting) # Test querying self.assertEqual( @@ -1857,8 +1857,8 @@ class FieldTest(MongoDBTestCase): e.save() e2 = Extensible.objects.get(id=e.id) - self.assertTrue(isinstance(e2.mapping['somestring'], StringSetting)) - self.assertTrue(isinstance(e2.mapping['someint'], IntegerSetting)) + self.assertIsInstance(e2.mapping['somestring'], StringSetting) + self.assertIsInstance(e2.mapping['someint'], IntegerSetting) with self.assertRaises(ValidationError): e.mapping['someint'] = 123 @@ -2563,7 +2563,7 @@ class FieldTest(MongoDBTestCase): bm = Bookmark.objects(bookmark_object=post_1).first() self.assertEqual(bm.bookmark_object, post_1) - self.assertTrue(isinstance(bm.bookmark_object, Post)) + self.assertIsInstance(bm.bookmark_object, Post) bm.bookmark_object = link_1 bm.save() @@ -2571,7 +2571,7 @@ class FieldTest(MongoDBTestCase): bm = Bookmark.objects(bookmark_object=link_1).first() self.assertEqual(bm.bookmark_object, link_1) - self.assertTrue(isinstance(bm.bookmark_object, Link)) + self.assertIsInstance(bm.bookmark_object, Link) def test_generic_reference_list(self): """Ensure that a ListField properly dereferences generic references. @@ -2818,7 +2818,7 @@ class FieldTest(MongoDBTestCase): doc1 = Doc.objects.create() doc2 = Doc.objects.create(ref=doc1) - self.assertTrue(isinstance(doc1.pk, ObjectId)) + self.assertIsInstance(doc1.pk, ObjectId) doc = Doc.objects.get(ref=doc1.pk) self.assertEqual(doc, doc2) @@ -3421,13 +3421,13 @@ class FieldTest(MongoDBTestCase): person.save() person = Person.objects.first() - self.assertTrue(isinstance(person.like, Car)) + self.assertIsInstance(person.like, Car) person.like = Dish(food="arroz", number=15) person.save() person = Person.objects.first() - self.assertTrue(isinstance(person.like, Dish)) + self.assertIsInstance(person.like, Dish) def test_generic_embedded_document_choices(self): """Ensure you can limit GenericEmbeddedDocument choices.""" @@ -3452,7 +3452,7 @@ class FieldTest(MongoDBTestCase): person.save() person = Person.objects.first() - self.assertTrue(isinstance(person.like, Dish)) + self.assertIsInstance(person.like, Dish) def test_generic_list_embedded_document_choices(self): """Ensure you can limit GenericEmbeddedDocument choices inside @@ -3479,7 +3479,7 @@ class FieldTest(MongoDBTestCase): person.save() person = Person.objects.first() - self.assertTrue(isinstance(person.likes[0], Dish)) + self.assertIsInstance(person.likes[0], Dish) def test_recursive_validation(self): """Ensure that a validation result to_dict is available.""" @@ -3505,18 +3505,17 @@ class FieldTest(MongoDBTestCase): except ValidationError as error: # ValidationError.errors property self.assertTrue(hasattr(error, 'errors')) - self.assertTrue(isinstance(error.errors, dict)) - self.assertTrue('comments' in error.errors) - self.assertTrue(1 in error.errors['comments']) - self.assertTrue(isinstance(error.errors['comments'][1]['content'], - ValidationError)) + self.assertIsInstance(error.errors, dict) + self.assertIn('comments', error.errors) + self.assertIn(1, error.errors['comments']) + self.assertIsInstance(error.errors['comments'][1]['content'], ValidationError) # ValidationError.schema property error_dict = error.to_dict() - self.assertTrue(isinstance(error_dict, dict)) - self.assertTrue('comments' in error_dict) - self.assertTrue(1 in error_dict['comments']) - self.assertTrue('content' in error_dict['comments'][1]) + self.assertIsInstance(error_dict, dict) + self.assertIn('comments', error_dict) + self.assertIn(1, error_dict['comments']) + self.assertIn('content', error_dict['comments'][1]) self.assertEqual(error_dict['comments'][1]['content'], u'Field is required') @@ -3632,7 +3631,7 @@ class FieldTest(MongoDBTestCase): # Passes regex validation user = User(email='me@example.com') - self.assertTrue(user.validate() is None) + self.assertIsNone(user.validate()) def test_tuples_as_tuples(self): """Ensure that tuples remain tuples when they are inside @@ -3659,10 +3658,10 @@ class FieldTest(MongoDBTestCase): doc.items = tuples doc.save() x = TestDoc.objects().get() - self.assertTrue(x is not None) - self.assertTrue(len(x.items) == 1) - self.assertTrue(tuple(x.items[0]) in tuples) - self.assertTrue(x.items[0] in tuples) + self.assertIsNotNone(x) + self.assertEqual(len(x.items), 1) + self.assertIn(tuple(x.items[0]), tuples) + self.assertIn(x.items[0], tuples) def test_dynamic_fields_class(self): class Doc2(Document): @@ -3812,8 +3811,8 @@ class FieldTest(MongoDBTestCase): doc = TestLongFieldConsideredAsInt64(some_long=42).save() db = get_db() - self.assertTrue(isinstance(db.test_long_field_considered_as_int64.find()[0]['some_long'], Int64)) - self.assertTrue(isinstance(doc.some_long, six.integer_types)) + self.assertIsInstance(db.test_long_field_considered_as_int64.find()[0]['some_long'], Int64) + self.assertIsInstance(doc.some_long, six.integer_types) class EmbeddedDocumentListFieldTestCase(MongoDBTestCase): @@ -4364,7 +4363,7 @@ class CachedReferenceFieldTest(MongoDBTestCase): ocorrence = Ocorrence.objects(animal__tag='heavy').first() self.assertEqual(ocorrence.person, "teste") - self.assertTrue(isinstance(ocorrence.animal, Animal)) + self.assertIsInstance(ocorrence.animal, Animal) def test_cached_reference_field_decimal(self): class PersonAuto(Document): @@ -4681,7 +4680,7 @@ class CachedReferenceFieldTest(MongoDBTestCase): animal__tag='heavy', animal__owner__tp='u').first() self.assertEqual(ocorrence.person, "teste") - self.assertTrue(isinstance(ocorrence.animal, Animal)) + self.assertIsInstance(ocorrence.animal, Animal) def test_cached_reference_embedded_list_fields(self): class Owner(EmbeddedDocument): @@ -4735,7 +4734,7 @@ class CachedReferenceFieldTest(MongoDBTestCase): animal__tag='heavy', animal__owner__tags='cool').first() self.assertEqual(ocorrence.person, "teste 2") - self.assertTrue(isinstance(ocorrence.animal, Animal)) + self.assertIsInstance(ocorrence.animal, Animal) class LazyReferenceFieldTest(MongoDBTestCase): diff --git a/tests/fields/file_tests.py b/tests/fields/file_tests.py index 841e7c7d..213e889c 100644 --- a/tests/fields/file_tests.py +++ b/tests/fields/file_tests.py @@ -53,7 +53,7 @@ class FileTest(MongoDBTestCase): putfile.save() result = PutFile.objects.first() - self.assertTrue(putfile == result) + self.assertEqual(putfile, result) self.assertEqual("%s" % result.the_file, "" % result.the_file.grid_id) self.assertEqual(result.the_file.read(), text) self.assertEqual(result.the_file.content_type, content_type) @@ -71,7 +71,7 @@ class FileTest(MongoDBTestCase): putfile.save() result = PutFile.objects.first() - self.assertTrue(putfile == result) + self.assertEqual(putfile, result) self.assertEqual(result.the_file.read(), text) self.assertEqual(result.the_file.content_type, content_type) result.the_file.delete() @@ -96,7 +96,7 @@ class FileTest(MongoDBTestCase): streamfile.save() result = StreamFile.objects.first() - self.assertTrue(streamfile == result) + self.assertEqual(streamfile, result) self.assertEqual(result.the_file.read(), text + more_text) self.assertEqual(result.the_file.content_type, content_type) result.the_file.seek(0) @@ -132,7 +132,7 @@ class FileTest(MongoDBTestCase): streamfile.save() result = StreamFile.objects.first() - self.assertTrue(streamfile == result) + self.assertEqual(streamfile, result) self.assertEqual(result.the_file.read(), text + more_text) # self.assertEqual(result.the_file.content_type, content_type) result.the_file.seek(0) @@ -161,7 +161,7 @@ class FileTest(MongoDBTestCase): setfile.save() result = SetFile.objects.first() - self.assertTrue(setfile == result) + self.assertEqual(setfile, result) self.assertEqual(result.the_file.read(), text) # Try replacing file with new one @@ -169,7 +169,7 @@ class FileTest(MongoDBTestCase): result.save() result = SetFile.objects.first() - self.assertTrue(setfile == result) + self.assertEqual(setfile, result) self.assertEqual(result.the_file.read(), more_text) result.the_file.delete() @@ -231,8 +231,8 @@ class FileTest(MongoDBTestCase): test_file_dupe = TestFile() data = test_file_dupe.the_file.read() # Should be None - self.assertTrue(test_file.name != test_file_dupe.name) - self.assertTrue(test_file.the_file.read() != data) + self.assertNotEqual(test_file.name, test_file_dupe.name) + self.assertNotEqual(test_file.the_file.read(), data) TestFile.drop_collection() @@ -291,7 +291,7 @@ class FileTest(MongoDBTestCase): the_file = FileField() test_file = TestFile() - self.assertFalse(test_file.the_file in [{"test": 1}]) + self.assertNotIn(test_file.the_file, [{"test": 1}]) def test_file_disk_space(self): """ Test disk space usage when we delete/replace a file """ diff --git a/tests/fields/geo.py b/tests/fields/geo.py index 1c5bccc0..754f4203 100644 --- a/tests/fields/geo.py +++ b/tests/fields/geo.py @@ -298,9 +298,9 @@ class GeoFieldTest(unittest.TestCase): polygon = PolygonField() geo_indicies = Event._geo_indices() - self.assertTrue({'fields': [('line', '2dsphere')]} in geo_indicies) - self.assertTrue({'fields': [('polygon', '2dsphere')]} in geo_indicies) - self.assertTrue({'fields': [('point', '2dsphere')]} in geo_indicies) + self.assertIn({'fields': [('line', '2dsphere')]}, geo_indicies) + self.assertIn({'fields': [('polygon', '2dsphere')]}, geo_indicies) + self.assertIn({'fields': [('point', '2dsphere')]}, geo_indicies) def test_indexes_2dsphere_embedded(self): """Ensure that indexes are created automatically for GeoPointFields. @@ -316,9 +316,9 @@ class GeoFieldTest(unittest.TestCase): venue = EmbeddedDocumentField(Venue) geo_indicies = Event._geo_indices() - self.assertTrue({'fields': [('venue.line', '2dsphere')]} in geo_indicies) - self.assertTrue({'fields': [('venue.polygon', '2dsphere')]} in geo_indicies) - self.assertTrue({'fields': [('venue.point', '2dsphere')]} in geo_indicies) + self.assertIn({'fields': [('venue.line', '2dsphere')]}, geo_indicies) + self.assertIn({'fields': [('venue.polygon', '2dsphere')]}, geo_indicies) + self.assertIn({'fields': [('venue.point', '2dsphere')]}, geo_indicies) def test_geo_indexes_recursion(self): @@ -335,9 +335,9 @@ class GeoFieldTest(unittest.TestCase): Parent(name='Berlin').save() info = Parent._get_collection().index_information() - self.assertFalse('location_2d' in info) + self.assertNotIn('location_2d', info) info = Location._get_collection().index_information() - self.assertTrue('location_2d' in info) + self.assertIn('location_2d', info) self.assertEqual(len(Parent._geo_indices()), 0) self.assertEqual(len(Location._geo_indices()), 1) diff --git a/tests/queryset/field_list.py b/tests/queryset/field_list.py index c07cec3e..b111238a 100644 --- a/tests/queryset/field_list.py +++ b/tests/queryset/field_list.py @@ -181,7 +181,7 @@ class OnlyExcludeAllTest(unittest.TestCase): employee.save() obj = self.Person.objects(id=employee.id).only('age').get() - self.assertTrue(isinstance(obj, Employee)) + self.assertIsInstance(obj, Employee) # Check field names are looked up properly obj = Employee.objects(id=employee.id).only('salary').get() diff --git a/tests/queryset/geo.py b/tests/queryset/geo.py index 38c0377e..fea225b2 100644 --- a/tests/queryset/geo.py +++ b/tests/queryset/geo.py @@ -95,9 +95,9 @@ class GeoQueriesTest(MongoDBTestCase): location__within_distance=point_and_distance) self.assertEqual(events.count(), 2) events = list(events) - self.assertTrue(event2 not in events) - self.assertTrue(event1 in events) - self.assertTrue(event3 in events) + self.assertNotIn(event2, events) + self.assertIn(event1, events) + self.assertIn(event3, events) # find events within 10 degrees of san francisco point_and_distance = [[-122.415579, 37.7566023], 10] @@ -285,9 +285,9 @@ class GeoQueriesTest(MongoDBTestCase): location__geo_within_center=point_and_distance) self.assertEqual(events.count(), 2) events = list(events) - self.assertTrue(event2 not in events) - self.assertTrue(event1 in events) - self.assertTrue(event3 in events) + self.assertNotIn(event2, events) + self.assertIn(event1, events) + self.assertIn(event3, events) def _test_embedded(self, point_field_class): """Helper test method ensuring given point field class works diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index c6aa3594..16268cbf 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -59,11 +59,10 @@ class QuerySetTest(unittest.TestCase): def test_initialisation(self): """Ensure that a QuerySet is correctly initialised by QuerySetManager. """ - self.assertTrue(isinstance(self.Person.objects, QuerySet)) + self.assertIsInstance(self.Person.objects, QuerySet) self.assertEqual(self.Person.objects._collection.name, self.Person._get_collection_name()) - self.assertTrue(isinstance(self.Person.objects._collection, - pymongo.collection.Collection)) + self.assertIsInstance(self.Person.objects._collection, pymongo.collection.Collection) def test_cannot_perform_joins_references(self): @@ -89,8 +88,8 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(people.count(), 2) results = list(people) - self.assertTrue(isinstance(results[0], self.Person)) - self.assertTrue(isinstance(results[0].id, (ObjectId, str, unicode))) + self.assertIsInstance(results[0], self.Person) + self.assertIsInstance(results[0].id, (ObjectId, str, unicode)) self.assertEqual(results[0], user_a) self.assertEqual(results[0].name, 'User A') @@ -225,7 +224,7 @@ class QuerySetTest(unittest.TestCase): # Retrieve the first person from the database person = self.Person.objects.first() - self.assertTrue(isinstance(person, self.Person)) + self.assertIsInstance(person, self.Person) self.assertEqual(person.name, "User A") self.assertEqual(person.age, 20) @@ -672,13 +671,13 @@ class QuerySetTest(unittest.TestCase): result = self.Person(name="Bob", age=25).update( upsert=True, full_result=True) - self.assertTrue(isinstance(result, UpdateResult)) - self.assertTrue("upserted" in result.raw_result) + self.assertIsInstance(result, UpdateResult) + self.assertIn("upserted", result.raw_result) self.assertFalse(result.raw_result["updatedExisting"]) bob = self.Person.objects.first() result = bob.update(set__age=30, full_result=True) - self.assertTrue(isinstance(result, UpdateResult)) + self.assertIsInstance(result, UpdateResult) self.assertTrue(result.raw_result["updatedExisting"]) self.Person(name="Bob", age=20).save() @@ -994,7 +993,7 @@ class QuerySetTest(unittest.TestCase): # Retrieve the first person from the database person = self.Person.objects.slave_okay(True).first() - self.assertTrue(isinstance(person, self.Person)) + self.assertIsInstance(person, self.Person) self.assertEqual(person.name, "User A") self.assertEqual(person.age, 20) @@ -1061,10 +1060,10 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(docs.count(), 1000) docs_string = "%s" % docs - self.assertTrue("Doc: 0" in docs_string) + self.assertIn("Doc: 0", docs_string) self.assertEqual(docs.count(), 1000) - self.assertTrue('(remaining elements truncated)' in "%s" % docs) + self.assertIn('(remaining elements truncated)', "%s" % docs) # Limit and skip docs = docs[1:4] @@ -1281,7 +1280,7 @@ class QuerySetTest(unittest.TestCase): with db_ops_tracker() as q: BlogPost.objects.filter(title='whatever').order_by().first() self.assertEqual(len(q.get_ops()), 1) - self.assertFalse('$orderby' in q.get_ops()[0]['query']) + self.assertNotIn('$orderby', q.get_ops()[0]['query']) # calling an explicit order_by should use a specified sort with db_ops_tracker() as q: @@ -1297,7 +1296,7 @@ class QuerySetTest(unittest.TestCase): qs = BlogPost.objects.filter(title='whatever').order_by('published_date') qs.order_by().first() self.assertEqual(len(q.get_ops()), 1) - self.assertFalse('$orderby' in q.get_ops()[0]['query']) + self.assertNotIn('$orderby', q.get_ops()[0]['query']) def test_no_ordering_for_get(self): """ Ensure that Doc.objects.get doesn't use any ordering. @@ -1316,13 +1315,13 @@ class QuerySetTest(unittest.TestCase): with db_ops_tracker() as q: BlogPost.objects.get(title='whatever') self.assertEqual(len(q.get_ops()), 1) - self.assertFalse('$orderby' in q.get_ops()[0]['query']) + self.assertNotIn('$orderby', q.get_ops()[0]['query']) # Ordering should be ignored for .get even if we set it explicitly with db_ops_tracker() as q: BlogPost.objects.order_by('-title').get(title='whatever') self.assertEqual(len(q.get_ops()), 1) - self.assertFalse('$orderby' in q.get_ops()[0]['query']) + self.assertNotIn('$orderby', q.get_ops()[0]['query']) def test_find_embedded(self): """Ensure that an embedded document is properly returned from @@ -1344,15 +1343,15 @@ class QuerySetTest(unittest.TestCase): ) result = BlogPost.objects.first() - self.assertTrue(isinstance(result.author, User)) + self.assertIsInstance(result.author, User) self.assertEqual(result.author.name, 'Test User') result = BlogPost.objects.get(author__name=user.name) - self.assertTrue(isinstance(result.author, User)) + self.assertIsInstance(result.author, User) self.assertEqual(result.author.name, 'Test User') result = BlogPost.objects.get(author={'name': user.name}) - self.assertTrue(isinstance(result.author, User)) + self.assertIsInstance(result.author, User) self.assertEqual(result.author.name, 'Test User') # Fails, since the string is not a type that is able to represent the @@ -1470,7 +1469,7 @@ class QuerySetTest(unittest.TestCase): code_chunks = ['doc["cmnts"];', 'doc["doc-name"],', 'doc["cmnts"][i]["body"]'] for chunk in code_chunks: - self.assertTrue(chunk in sub_code) + self.assertIn(chunk, sub_code) results = BlogPost.objects.exec_js(code) expected_results = [ @@ -1937,11 +1936,12 @@ class QuerySetTest(unittest.TestCase): # ListField operator BlogPost.objects.update(push__tags='mongo') post.reload() - self.assertTrue('mongo' in post.tags) + self.assertIn('mongo', post.tags) BlogPost.objects.update_one(push_all__tags=['db', 'nosql']) post.reload() - self.assertTrue('db' in post.tags and 'nosql' in post.tags) + self.assertIn('db', post.tags) + self.assertIn('nosql', post.tags) tags = post.tags[:-1] BlogPost.objects.update(pop__tags=1) @@ -3274,8 +3274,8 @@ class QuerySetTest(unittest.TestCase): News.drop_collection() info = News.objects._collection.index_information() - self.assertTrue('title_text_content_text' in info) - self.assertTrue('textIndexVersion' in info['title_text_content_text']) + self.assertIn('title_text_content_text', info) + self.assertIn('textIndexVersion', info['title_text_content_text']) News(title="Neymar quebrou a vertebra", content="O Brasil sofre com a perda de Neymar").save() @@ -3309,15 +3309,15 @@ class QuerySetTest(unittest.TestCase): '$search': 'dilma', '$language': 'pt'}, 'is_active': False}) - self.assertEqual(new.is_active, False) - self.assertTrue('dilma' in new.content) - self.assertTrue('planejamento' in new.title) + self.assertFalse(new.is_active) + self.assertIn('dilma', new.content) + self.assertIn('planejamento', new.title) query = News.objects.search_text("candidata") self.assertEqual(query._search_text, "candidata") new = query.first() - self.assertTrue(isinstance(new.get_text_score(), float)) + self.assertIsInstance(new.get_text_score(), float) # count query = News.objects.search_text('brasil').order_by('$text_score') @@ -3612,7 +3612,7 @@ class QuerySetTest(unittest.TestCase): Group.objects(id=group.id).update(set__members=[user1, user2]) group.reload() - self.assertTrue(len(group.members) == 2) + self.assertEqual(len(group.members), 2) self.assertEqual(group.members[0].name, user1.name) self.assertEqual(group.members[1].name, user2.name) @@ -3643,13 +3643,13 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(len(objects), 3) - self.assertTrue(post_1.id in objects) - self.assertTrue(post_2.id in objects) - self.assertTrue(post_5.id in objects) + self.assertIn(post_1.id, objects) + self.assertIn(post_2.id, objects) + self.assertIn(post_5.id, objects) - self.assertTrue(objects[post_1.id].title == post_1.title) - self.assertTrue(objects[post_2.id].title == post_2.title) - self.assertTrue(objects[post_5.id].title == post_5.title) + self.assertEqual(objects[post_1.id].title, post_1.title) + self.assertEqual(objects[post_2.id].title, post_2.title) + self.assertEqual(objects[post_5.id].title, post_5.title) BlogPost.drop_collection() @@ -3669,7 +3669,7 @@ class QuerySetTest(unittest.TestCase): Post.drop_collection() - self.assertTrue(isinstance(Post.objects, CustomQuerySet)) + self.assertIsInstance(Post.objects, CustomQuerySet) self.assertFalse(Post.objects.not_empty()) Post().save() @@ -3694,7 +3694,7 @@ class QuerySetTest(unittest.TestCase): Post.drop_collection() - self.assertTrue(isinstance(Post.objects, CustomQuerySet)) + self.assertIsInstance(Post.objects, CustomQuerySet) self.assertFalse(Post.objects.not_empty()) Post().save() @@ -3741,7 +3741,7 @@ class QuerySetTest(unittest.TestCase): pass Post.drop_collection() - self.assertTrue(isinstance(Post.objects, CustomQuerySet)) + self.assertIsInstance(Post.objects, CustomQuerySet) self.assertFalse(Post.objects.not_empty()) Post().save() @@ -3769,7 +3769,7 @@ class QuerySetTest(unittest.TestCase): pass Post.drop_collection() - self.assertTrue(isinstance(Post.objects, CustomQuerySet)) + self.assertIsInstance(Post.objects, CustomQuerySet) self.assertFalse(Post.objects.not_empty()) Post().save() @@ -3860,17 +3860,17 @@ class QuerySetTest(unittest.TestCase): test = Number.objects test2 = test.clone() - self.assertFalse(test == test2) + self.assertNotEqual(test, test2) self.assertEqual(test.count(), test2.count()) test = test.filter(n__gt=11) test2 = test.clone() - self.assertFalse(test == test2) + self.assertNotEqual(test, test2) self.assertEqual(test.count(), test2.count()) test = test.limit(10) test2 = test.clone() - self.assertFalse(test == test2) + self.assertNotEqual(test, test2) self.assertEqual(test.count(), test2.count()) Number.drop_collection() @@ -3960,7 +3960,7 @@ class QuerySetTest(unittest.TestCase): value.get('unique', False), value.get('sparse', False)) for key, value in info.iteritems()] - self.assertTrue(([('_cls', 1), ('message', 1)], False, False) in info) + self.assertIn(([('_cls', 1), ('message', 1)], False, False), info) def test_where(self): """Ensure that where clauses work. @@ -3984,13 +3984,13 @@ class QuerySetTest(unittest.TestCase): 'this["fielda"] >= this["fieldb"]', query._where_clause) results = list(query) self.assertEqual(2, len(results)) - self.assertTrue(a in results) - self.assertTrue(c in results) + self.assertIn(a, results) + self.assertIn(c, results) query = IntPair.objects.where('this[~fielda] == this[~fieldb]') results = list(query) self.assertEqual(1, len(results)) - self.assertTrue(a in results) + self.assertIn(a, results) query = IntPair.objects.where( 'function() { return this[~fielda] >= this[~fieldb] }') @@ -3998,8 +3998,8 @@ class QuerySetTest(unittest.TestCase): 'function() { return this["fielda"] >= this["fieldb"] }', query._where_clause) results = list(query) self.assertEqual(2, len(results)) - self.assertTrue(a in results) - self.assertTrue(c in results) + self.assertIn(a, results) + self.assertIn(c, results) with self.assertRaises(TypeError): list(IntPair.objects.where(fielda__gte=3)) @@ -4381,7 +4381,7 @@ class QuerySetTest(unittest.TestCase): Test.drop_collection() Test.objects(test='foo').update_one(upsert=True, set__test='foo') - self.assertFalse('_cls' in Test._collection.find_one()) + self.assertNotIn('_cls', Test._collection.find_one()) class Test(Document): meta = {'allow_inheritance': True} @@ -4390,7 +4390,7 @@ class QuerySetTest(unittest.TestCase): Test.drop_collection() Test.objects(test='foo').update_one(upsert=True, set__test='foo') - self.assertTrue('_cls' in Test._collection.find_one()) + self.assertIn('_cls', Test._collection.find_one()) def test_update_upsert_looks_like_a_digit(self): class MyDoc(DynamicDocument): @@ -4602,8 +4602,8 @@ class QuerySetTest(unittest.TestCase): users = User.objects.only('name', 'price').as_pymongo() results = list(users) - self.assertTrue(isinstance(results[0], dict)) - self.assertTrue(isinstance(results[1], dict)) + self.assertIsInstance(results[0], dict) + self.assertIsInstance(results[1], dict) self.assertEqual(results[0]['name'], 'Bob Dole') self.assertEqual(results[0]['price'], 1.11) self.assertEqual(results[1]['name'], 'Barack Obama') @@ -4611,8 +4611,8 @@ class QuerySetTest(unittest.TestCase): users = User.objects.only('name', 'last_login').as_pymongo() results = list(users) - self.assertTrue(isinstance(results[0], dict)) - self.assertTrue(isinstance(results[1], dict)) + self.assertIsInstance(results[0], dict) + self.assertIsInstance(results[1], dict) self.assertEqual(results[0], { 'name': 'Bob Dole' }) @@ -4669,12 +4669,10 @@ class QuerySetTest(unittest.TestCase): User(name="Bob Dole", organization=whitehouse).save() qs = User.objects() - self.assertTrue(isinstance(qs.first().organization, Organization)) - self.assertFalse(isinstance(qs.no_dereference().first().organization, - Organization)) - self.assertFalse(isinstance(qs.no_dereference().get().organization, - Organization)) - self.assertTrue(isinstance(qs.first().organization, Organization)) + self.assertIsInstance(qs.first().organization, Organization) + self.assertNotIsInstance(qs.no_dereference().first().organization, Organization) + self.assertNotIsInstance(qs.no_dereference().get().organization, Organization) + self.assertIsInstance(qs.first().organization, Organization) def test_no_dereference_embedded_doc(self): @@ -4707,9 +4705,9 @@ class QuerySetTest(unittest.TestCase): result = Organization.objects().no_dereference().first() - self.assertTrue(isinstance(result.admin[0], (DBRef, ObjectId))) - self.assertTrue(isinstance(result.member.user, (DBRef, ObjectId))) - self.assertTrue(isinstance(result.members[0].user, (DBRef, ObjectId))) + self.assertIsInstance(result.admin[0], (DBRef, ObjectId)) + self.assertIsInstance(result.member.user, (DBRef, ObjectId)) + self.assertIsInstance(result.members[0].user, (DBRef, ObjectId)) def test_cached_queryset(self): class Person(Document): @@ -5052,7 +5050,7 @@ class QuerySetTest(unittest.TestCase): op = q.db.system.profile.find({"ns": {"$ne": "%s.system.indexes" % q.db.name}})[0] - self.assertFalse('$orderby' in op['query'], + self.assertNotIn('$orderby', op['query'], 'BaseQuerySet cannot use orderby in if stmt') with query_counter() as p: @@ -5063,8 +5061,7 @@ class QuerySetTest(unittest.TestCase): op = p.db.system.profile.find({"ns": {"$ne": "%s.system.indexes" % q.db.name}})[0] - self.assertTrue('$orderby' in op['query'], - 'BaseQuerySet cannot remove orderby in for loop') + self.assertIn('$orderby', op['query'], 'BaseQuerySet cannot remove orderby in for loop') def test_bool_with_ordering_from_meta_dict(self): @@ -5088,7 +5085,7 @@ class QuerySetTest(unittest.TestCase): op = q.db.system.profile.find({"ns": {"$ne": "%s.system.indexes" % q.db.name}})[0] - self.assertFalse('$orderby' in op['query'], + self.assertNotIn('$orderby', op['query'], 'BaseQuerySet must remove orderby from meta in boolen test') self.assertEqual(Person.objects.first().name, 'A') diff --git a/tests/queryset/transform.py b/tests/queryset/transform.py index 38098432..8064f09c 100644 --- a/tests/queryset/transform.py +++ b/tests/queryset/transform.py @@ -48,15 +48,15 @@ class TransformTest(unittest.TestCase): for k, v in (("set", "$set"), ("set_on_insert", "$setOnInsert"), ("push", "$push")): update = transform.update(DicDoc, **{"%s__dictField__test" % k: doc}) - self.assertTrue(isinstance(update[v]["dictField.test"], dict)) + self.assertIsInstance(update[v]["dictField.test"], dict) # Update special cases update = transform.update(DicDoc, unset__dictField__test=doc) self.assertEqual(update["$unset"]["dictField.test"], 1) update = transform.update(DicDoc, pull__dictField__test=doc) - self.assertTrue(isinstance(update["$pull"]["dictField"]["test"], dict)) - + self.assertIsInstance(update["$pull"]["dictField"]["test"], dict) + update = transform.update(LisDoc, pull__foo__in=['a']) self.assertEqual(update, {'$pull': {'foo': {'$in': ['a']}}}) @@ -88,17 +88,15 @@ class TransformTest(unittest.TestCase): post = BlogPost(**data) post.save() - self.assertTrue('postTitle' in - BlogPost.objects(title=data['title'])._query) + self.assertIn('postTitle', BlogPost.objects(title=data['title'])._query) self.assertFalse('title' in BlogPost.objects(title=data['title'])._query) self.assertEqual(BlogPost.objects(title=data['title']).count(), 1) - self.assertTrue('_id' in BlogPost.objects(pk=post.id)._query) + self.assertIn('_id', BlogPost.objects(pk=post.id)._query) self.assertEqual(BlogPost.objects(pk=post.id).count(), 1) - self.assertTrue('postComments.commentContent' in - BlogPost.objects(comments__content='test')._query) + self.assertIn('postComments.commentContent', BlogPost.objects(comments__content='test')._query) self.assertEqual(BlogPost.objects(comments__content='test').count(), 1) BlogPost.drop_collection() @@ -116,8 +114,8 @@ class TransformTest(unittest.TestCase): post = BlogPost(**data) post.save() - self.assertTrue('_id' in BlogPost.objects(pk=data['title'])._query) - self.assertTrue('_id' in BlogPost.objects(title=data['title'])._query) + self.assertIn('_id', BlogPost.objects(pk=data['title'])._query) + self.assertIn('_id', BlogPost.objects(title=data['title'])._query) self.assertEqual(BlogPost.objects(pk=data['title']).count(), 1) BlogPost.drop_collection() @@ -260,31 +258,31 @@ class TransformTest(unittest.TestCase): events = Event.objects(location__within=box) with self.assertRaises(InvalidQueryError): events.count() - + def test_update_pull_for_list_fields(self): - """ - Test added to check pull operation in update for + """ + Test added to check pull operation in update for EmbeddedDocumentListField which is inside a EmbeddedDocumentField """ class Word(EmbeddedDocument): word = StringField() index = IntField() - + class SubDoc(EmbeddedDocument): heading = ListField(StringField()) text = EmbeddedDocumentListField(Word) - + class MainDoc(Document): title = StringField() content = EmbeddedDocumentField(SubDoc) - + word = Word(word='abc', index=1) update = transform.update(MainDoc, pull__content__text=word) self.assertEqual(update, {'$pull': {'content.text': SON([('word', u'abc'), ('index', 1)])}}) update = transform.update(MainDoc, pull__content__heading='xyz') self.assertEqual(update, {'$pull': {'content.heading': 'xyz'}}) - - + + if __name__ == '__main__': unittest.main() diff --git a/tests/queryset/visitor.py b/tests/queryset/visitor.py index 6f020e88..7b68cfb0 100644 --- a/tests/queryset/visitor.py +++ b/tests/queryset/visitor.py @@ -196,7 +196,7 @@ class QTest(unittest.TestCase): test2 = test.clone() self.assertEqual(test2.count(), 3) - self.assertFalse(test2 == test) + self.assertNotEqual(test2, test) test3 = test2.filter(x=6) self.assertEqual(test3.count(), 1) diff --git a/tests/test_connection.py b/tests/test_connection.py index f58b1a3e..88d63cdb 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -39,15 +39,15 @@ class ConnectionTest(unittest.TestCase): connect('mongoenginetest') conn = get_connection() - self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient)) + self.assertIsInstance(conn, pymongo.mongo_client.MongoClient) db = get_db() - self.assertTrue(isinstance(db, pymongo.database.Database)) + self.assertIsInstance(db, pymongo.database.Database) self.assertEqual(db.name, 'mongoenginetest') connect('mongoenginetest2', alias='testdb') conn = get_connection('testdb') - self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient)) + self.assertIsInstance(conn, pymongo.mongo_client.MongoClient) def test_connect_in_mocking(self): """Ensure that the connect() method works properly in mocking. @@ -59,31 +59,31 @@ class ConnectionTest(unittest.TestCase): connect('mongoenginetest', host='mongomock://localhost') conn = get_connection() - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect('mongoenginetest2', host='mongomock://localhost', alias='testdb2') conn = get_connection('testdb2') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect('mongoenginetest3', host='mongodb://localhost', is_mock=True, alias='testdb3') conn = get_connection('testdb3') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect('mongoenginetest4', is_mock=True, alias='testdb4') conn = get_connection('testdb4') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect(host='mongodb://localhost:27017/mongoenginetest5', is_mock=True, alias='testdb5') conn = get_connection('testdb5') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect(host='mongomock://localhost:27017/mongoenginetest6', alias='testdb6') conn = get_connection('testdb6') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect(host='mongomock://localhost:27017/mongoenginetest7', is_mock=True, alias='testdb7') conn = get_connection('testdb7') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) def test_connect_with_host_list(self): """Ensure that the connect() method works when host is a list @@ -97,27 +97,27 @@ class ConnectionTest(unittest.TestCase): connect(host=['mongomock://localhost']) conn = get_connection() - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect(host=['mongodb://localhost'], is_mock=True, alias='testdb2') conn = get_connection('testdb2') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect(host=['localhost'], is_mock=True, alias='testdb3') conn = get_connection('testdb3') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect(host=['mongomock://localhost:27017', 'mongomock://localhost:27018'], alias='testdb4') conn = get_connection('testdb4') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect(host=['mongodb://localhost:27017', 'mongodb://localhost:27018'], is_mock=True, alias='testdb5') conn = get_connection('testdb5') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) connect(host=['localhost:27017', 'localhost:27018'], is_mock=True, alias='testdb6') conn = get_connection('testdb6') - self.assertTrue(isinstance(conn, mongomock.MongoClient)) + self.assertIsInstance(conn, mongomock.MongoClient) def test_disconnect(self): """Ensure that the disconnect() method works properly @@ -163,10 +163,10 @@ class ConnectionTest(unittest.TestCase): connect("testdb_uri", host='mongodb://username:password@localhost/mongoenginetest') conn = get_connection() - self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient)) + self.assertIsInstance(conn, pymongo.mongo_client.MongoClient) db = get_db() - self.assertTrue(isinstance(db, pymongo.database.Database)) + self.assertIsInstance(db, pymongo.database.Database) self.assertEqual(db.name, 'mongoenginetest') c.admin.system.users.remove({}) @@ -179,10 +179,10 @@ class ConnectionTest(unittest.TestCase): connect("mongoenginetest", host='mongodb://localhost/') conn = get_connection() - self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient)) + self.assertIsInstance(conn, pymongo.mongo_client.MongoClient) db = get_db() - self.assertTrue(isinstance(db, pymongo.database.Database)) + self.assertIsInstance(db, pymongo.database.Database) self.assertEqual(db.name, 'mongoenginetest') def test_connect_uri_default_db(self): @@ -192,10 +192,10 @@ class ConnectionTest(unittest.TestCase): connect(host='mongodb://localhost/') conn = get_connection() - self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient)) + self.assertIsInstance(conn, pymongo.mongo_client.MongoClient) db = get_db() - self.assertTrue(isinstance(db, pymongo.database.Database)) + self.assertIsInstance(db, pymongo.database.Database) self.assertEqual(db.name, 'test') def test_uri_without_credentials_doesnt_override_conn_settings(self): @@ -242,7 +242,7 @@ class ConnectionTest(unittest.TestCase): 'mongoenginetest?authSource=admin') ) db = get_db('test2') - self.assertTrue(isinstance(db, pymongo.database.Database)) + self.assertIsInstance(db, pymongo.database.Database) self.assertEqual(db.name, 'mongoenginetest') # Clear all users @@ -255,10 +255,10 @@ class ConnectionTest(unittest.TestCase): self.assertRaises(MongoEngineConnectionError, get_connection) conn = get_connection('testdb') - self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient)) + self.assertIsInstance(conn, pymongo.mongo_client.MongoClient) db = get_db('testdb') - self.assertTrue(isinstance(db, pymongo.database.Database)) + self.assertIsInstance(db, pymongo.database.Database) self.assertEqual(db.name, 'mongoenginetest2') def test_register_connection_defaults(self): @@ -267,7 +267,7 @@ class ConnectionTest(unittest.TestCase): register_connection('testdb', 'mongoenginetest', host=None, port=None) conn = get_connection('testdb') - self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient)) + self.assertIsInstance(conn, pymongo.mongo_client.MongoClient) def test_connection_kwargs(self): """Ensure that connection kwargs get passed to pymongo.""" @@ -326,7 +326,7 @@ class ConnectionTest(unittest.TestCase): if IS_PYMONGO_3: c = connect(host='mongodb://localhost/test?replicaSet=local-rs') db = get_db() - self.assertTrue(isinstance(db, pymongo.database.Database)) + self.assertIsInstance(db, pymongo.database.Database) self.assertEqual(db.name, 'test') else: # PyMongo < v3.x raises an exception: @@ -343,7 +343,7 @@ class ConnectionTest(unittest.TestCase): self.assertEqual(c._MongoClient__options.replica_set_name, 'local-rs') db = get_db() - self.assertTrue(isinstance(db, pymongo.database.Database)) + self.assertIsInstance(db, pymongo.database.Database) self.assertEqual(db.name, 'test') else: # PyMongo < v3.x raises an exception: @@ -377,8 +377,8 @@ class ConnectionTest(unittest.TestCase): mongo_connections = mongoengine.connection._connections self.assertEqual(len(mongo_connections.items()), 2) - self.assertTrue('t1' in mongo_connections.keys()) - self.assertTrue('t2' in mongo_connections.keys()) + self.assertIn('t1', mongo_connections.keys()) + self.assertIn('t2', mongo_connections.keys()) if not IS_PYMONGO_3: self.assertEqual(mongo_connections['t1'].host, 'localhost') self.assertEqual(mongo_connections['t2'].host, '127.0.0.1') diff --git a/tests/test_context_managers.py b/tests/test_context_managers.py index 8c96016c..3c93b96f 100644 --- a/tests/test_context_managers.py +++ b/tests/test_context_managers.py @@ -89,15 +89,15 @@ class ContextManagersTest(unittest.TestCase): with no_dereference(Group) as Group: group = Group.objects.first() - self.assertTrue(all([not isinstance(m, User) - for m in group.members])) - self.assertFalse(isinstance(group.ref, User)) - self.assertFalse(isinstance(group.generic, User)) + for m in group.members: + self.assertNotIsInstance(m, User) + self.assertNotIsInstance(group.ref, User) + self.assertNotIsInstance(group.generic, User) - self.assertTrue(all([isinstance(m, User) - for m in group.members])) - self.assertTrue(isinstance(group.ref, User)) - self.assertTrue(isinstance(group.generic, User)) + for m in group.members: + self.assertIsInstance(m, User) + self.assertIsInstance(group.ref, User) + self.assertIsInstance(group.generic, User) def test_no_dereference_context_manager_dbref(self): """Ensure that DBRef items in ListFields aren't dereferenced. @@ -129,13 +129,13 @@ class ContextManagersTest(unittest.TestCase): group = Group.objects.first() self.assertTrue(all([not isinstance(m, User) for m in group.members])) - self.assertFalse(isinstance(group.ref, User)) - self.assertFalse(isinstance(group.generic, User)) + self.assertNotIsInstance(group.ref, User) + self.assertNotIsInstance(group.generic, User) self.assertTrue(all([isinstance(m, User) for m in group.members])) - self.assertTrue(isinstance(group.ref, User)) - self.assertTrue(isinstance(group.generic, User)) + self.assertIsInstance(group.ref, User) + self.assertIsInstance(group.generic, User) def test_no_sub_classes(self): class A(Document): diff --git a/tests/test_dereference.py b/tests/test_dereference.py index 7f58a85b..8b8bcfb2 100644 --- a/tests/test_dereference.py +++ b/tests/test_dereference.py @@ -200,8 +200,8 @@ class FieldTest(unittest.TestCase): group = Group(author=user, members=[user]).save() raw_data = Group._get_collection().find_one() - self.assertTrue(isinstance(raw_data['author'], DBRef)) - self.assertTrue(isinstance(raw_data['members'][0], DBRef)) + self.assertIsInstance(raw_data['author'], DBRef) + self.assertIsInstance(raw_data['members'][0], DBRef) group = Group.objects.first() self.assertEqual(group.author, user) @@ -224,8 +224,8 @@ class FieldTest(unittest.TestCase): self.assertEqual(group.members, [user]) raw_data = Group._get_collection().find_one() - self.assertTrue(isinstance(raw_data['author'], ObjectId)) - self.assertTrue(isinstance(raw_data['members'][0], ObjectId)) + self.assertIsInstance(raw_data['author'], ObjectId) + self.assertIsInstance(raw_data['members'][0], ObjectId) def test_recursive_reference(self): """Ensure that ReferenceFields can reference their own documents. @@ -469,7 +469,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for m in group_obj.members: - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) # Document select_related with query_counter() as q: @@ -485,7 +485,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for m in group_obj.members: - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) # Queryset select_related with query_counter() as q: @@ -502,7 +502,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for m in group_obj.members: - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) UserA.drop_collection() UserB.drop_collection() @@ -560,7 +560,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for m in group_obj.members: - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) # Document select_related with query_counter() as q: @@ -576,7 +576,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for m in group_obj.members: - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) # Queryset select_related with query_counter() as q: @@ -593,7 +593,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for m in group_obj.members: - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) UserA.drop_collection() UserB.drop_collection() @@ -633,7 +633,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 2) for k, m in group_obj.members.iteritems(): - self.assertTrue(isinstance(m, User)) + self.assertIsInstance(m, User) # Document select_related with query_counter() as q: @@ -646,7 +646,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 2) for k, m in group_obj.members.iteritems(): - self.assertTrue(isinstance(m, User)) + self.assertIsInstance(m, User) # Queryset select_related with query_counter() as q: @@ -660,7 +660,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 2) for k, m in group_obj.members.iteritems(): - self.assertTrue(isinstance(m, User)) + self.assertIsInstance(m, User) User.drop_collection() Group.drop_collection() @@ -715,7 +715,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for k, m in group_obj.members.iteritems(): - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) # Document select_related with query_counter() as q: @@ -731,7 +731,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for k, m in group_obj.members.iteritems(): - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) # Queryset select_related with query_counter() as q: @@ -748,7 +748,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for k, m in group_obj.members.iteritems(): - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) Group.objects.delete() Group().save() @@ -806,7 +806,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 2) for k, m in group_obj.members.iteritems(): - self.assertTrue(isinstance(m, UserA)) + self.assertIsInstance(m, UserA) # Document select_related with query_counter() as q: @@ -822,7 +822,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 2) for k, m in group_obj.members.iteritems(): - self.assertTrue(isinstance(m, UserA)) + self.assertIsInstance(m, UserA) # Queryset select_related with query_counter() as q: @@ -839,7 +839,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 2) for k, m in group_obj.members.iteritems(): - self.assertTrue(isinstance(m, UserA)) + self.assertIsInstance(m, UserA) UserA.drop_collection() Group.drop_collection() @@ -894,7 +894,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for k, m in group_obj.members.iteritems(): - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) # Document select_related with query_counter() as q: @@ -910,7 +910,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for k, m in group_obj.members.iteritems(): - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) # Queryset select_related with query_counter() as q: @@ -927,7 +927,7 @@ class FieldTest(unittest.TestCase): self.assertEqual(q, 4) for k, m in group_obj.members.iteritems(): - self.assertTrue('User' in m.__class__.__name__) + self.assertIn('User', m.__class__.__name__) Group.objects.delete() Group().save() @@ -1209,10 +1209,10 @@ class FieldTest(unittest.TestCase): # Can't use query_counter across databases - so test the _data object book = Book.objects.first() - self.assertFalse(isinstance(book._data['author'], User)) + self.assertNotIsInstance(book._data['author'], User) book.select_related() - self.assertTrue(isinstance(book._data['author'], User)) + self.assertIsInstance(book._data['author'], User) def test_non_ascii_pk(self): """ From a86092fb6450df0911aea761230451d88027ae60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Thu, 6 Sep 2018 22:33:24 +0200 Subject: [PATCH 38/85] fix doc for meta index_options --- docs/guide/defining-documents.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index 366d12c7..62cbcd67 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -529,7 +529,7 @@ There are a few top level defaults for all indexes that can be set:: title = StringField() rating = StringField() meta = { - 'index_options': {}, + 'index_opts': {}, 'index_background': True, 'index_cls': False, 'auto_create_index': True, @@ -537,7 +537,7 @@ There are a few top level defaults for all indexes that can be set:: } -:attr:`index_options` (Optional) +:attr:`index_opts` (Optional) Set any default index options - see the `full options list `_ :attr:`index_background` (Optional) From 7fc5ced3af3380ffec60787a508a7a049180d194 Mon Sep 17 00:00:00 2001 From: Stefan VanBuren Date: Thu, 6 Sep 2018 16:50:31 -0400 Subject: [PATCH 39/85] Update link to index options --- docs/guide/defining-documents.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index 366d12c7..06666022 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -538,7 +538,7 @@ There are a few top level defaults for all indexes that can be set:: :attr:`index_options` (Optional) - Set any default index options - see the `full options list `_ + Set any default index options - see the `full options list `_ :attr:`index_background` (Optional) Set the default value for if an index should be indexed in the background From aa49283fa9394ede764c26a0329ccae3c62b5375 Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Fri, 7 Sep 2018 15:39:55 +0800 Subject: [PATCH 40/85] fix trailing whitespace --- mongoengine/queryset/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index eb0fd669..6fddc381 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -775,7 +775,7 @@ class BaseQuerySet(object): """Limit the number of returned documents to `n`. This may also be achieved using array-slicing syntax (e.g. ``User.objects[:5]``). - :param n: the maximum number of objects to return if n is greater than 0. + :param n: the maximum number of objects to return if n is greater than 0. When 0 is passed, returns all the documents in the cursor """ queryset = self.clone() From f89214f9cf39d2a06fd3611ffb6e1647e6fce4b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sat, 8 Sep 2018 22:20:49 +0200 Subject: [PATCH 41/85] Fixes bug where an EmbeddedDocument that shares the same id of its parent Document could be missing updates when .save was called Fixes #1768, Fixes #1685 --- mongoengine/base/document.py | 53 +++++++++++++++++------------------ tests/document/instance.py | 54 ++++++++++++++++++++++++++++++++++++ tests/test_dereference.py | 1 - 3 files changed, 79 insertions(+), 29 deletions(-) diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 85906a3e..7a3e22f9 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -503,7 +503,13 @@ class BaseDocument(object): self._changed_fields = [] - def _nestable_types_changed_fields(self, changed_fields, key, data, inspected): + def _nestable_types_changed_fields(self, changed_fields, base_key, data): + """Inspect nested data for changed fields + + :param changed_fields: Previously collected changed fields + :param base_key: The base key that must be used to prepend changes to this data + :param data: data to inspect for changes + """ # Loop list / dict fields as they contain documents # Determine the iterator to use if not hasattr(data, 'items'): @@ -511,57 +517,48 @@ class BaseDocument(object): else: iterator = data.iteritems() - for index, value in iterator: - list_key = '%s%s.' % (key, index) + for index_or_key, value in iterator: + item_key = '%s%s.' % (base_key, index_or_key) # don't check anything lower if this key is already marked # as changed. - if list_key[:-1] in changed_fields: + if item_key[:-1] in changed_fields: continue + if hasattr(value, '_get_changed_fields'): - changed = value._get_changed_fields(inspected) - changed_fields += ['%s%s' % (list_key, k) - for k in changed if k] + changed = value._get_changed_fields() + changed_fields += ['%s%s' % (item_key, k) for k in changed if k] elif isinstance(value, (list, tuple, dict)): self._nestable_types_changed_fields( - changed_fields, list_key, value, inspected) + changed_fields, item_key, value) - def _get_changed_fields(self, inspected=None): + def _get_changed_fields(self): """Return a list of all fields that have explicitly been changed. """ EmbeddedDocument = _import_class('EmbeddedDocument') - DynamicEmbeddedDocument = _import_class('DynamicEmbeddedDocument') ReferenceField = _import_class('ReferenceField') SortedListField = _import_class('SortedListField') changed_fields = [] changed_fields += getattr(self, '_changed_fields', []) - inspected = inspected or set() - if hasattr(self, 'id') and isinstance(self.id, Hashable): - if self.id in inspected: - return changed_fields - inspected.add(self.id) - for field_name in self._fields_ordered: db_field_name = self._db_field_map.get(field_name, field_name) key = '%s.' % db_field_name data = self._data.get(field_name, None) field = self._fields.get(field_name) - if hasattr(data, 'id'): - if data.id in inspected: - continue - if isinstance(field, ReferenceField): + if db_field_name in changed_fields: + # Whole field already marked as changed, no need to go further continue - elif ( - isinstance(data, (EmbeddedDocument, DynamicEmbeddedDocument)) and - db_field_name not in changed_fields - ): + + if isinstance(field, ReferenceField): # Don't follow referenced documents + continue + + if isinstance(data, EmbeddedDocument): # Find all embedded fields that have been changed - changed = data._get_changed_fields(inspected) + changed = data._get_changed_fields() changed_fields += ['%s%s' % (key, k) for k in changed if k] - elif (isinstance(data, (list, tuple, dict)) and - db_field_name not in changed_fields): + elif isinstance(data, (list, tuple, dict)): if (hasattr(field, 'field') and isinstance(field.field, ReferenceField)): continue @@ -572,7 +569,7 @@ class BaseDocument(object): continue self._nestable_types_changed_fields( - changed_fields, key, data, inspected) + changed_fields, key, data) return changed_fields def _delta(self): diff --git a/tests/document/instance.py b/tests/document/instance.py index e637b3e6..d8ff8b43 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -1422,6 +1422,60 @@ class InstanceTest(MongoDBTestCase): self.assertEqual(person.age, 21) self.assertEqual(person.active, False) + def test__get_changed_fields_same_ids_reference_field_does_not_enters_infinite_loop(self): + # Refers to Issue #1685 + class EmbeddedChildModel(EmbeddedDocument): + id = DictField(primary_key=True) + + class ParentModel(Document): + child = EmbeddedDocumentField( + EmbeddedChildModel) + + emb = EmbeddedChildModel(id={'1': [1]}) + ParentModel(children=emb)._get_changed_fields() + + def test__get_changed_fields_same_ids_reference_field_does_not_enters_infinite_loop(self): + class User(Document): + id = IntField(primary_key=True) + name = StringField() + + class Message(Document): + id = IntField(primary_key=True) + author = ReferenceField(User) + + Message.drop_collection() + + # All objects share the same id, but each in a different collection + user = User(id=1, name='user-name').save() + message = Message(id=1, author=user).save() + + message.author.name = 'tutu' + self.assertEqual(message._get_changed_fields(), []) + self.assertEqual(user._get_changed_fields(), ['name']) + + def test__get_changed_fields_same_ids_embedded(self): + # Refers to Issue #1768 + class User(EmbeddedDocument): + id = IntField() + name = StringField() + + class Message(Document): + id = IntField(primary_key=True) + author = EmbeddedDocumentField(User) + + Message.drop_collection() + + # All objects share the same id, but each in a different collection + user = User(id=1, name='user-name')#.save() + message = Message(id=1, author=user).save() + + message.author.name = 'tutu' + self.assertEqual(message._get_changed_fields(), ['author.name']) + message.save() + + message_fetched = Message.objects.with_id(message.id) + self.assertEqual(message_fetched.author.name, 'tutu') + def test_query_count_when_saving(self): """Ensure references don't cause extra fetches when saving""" class Organization(Document): diff --git a/tests/test_dereference.py b/tests/test_dereference.py index 8b8bcfb2..5cf089f4 100644 --- a/tests/test_dereference.py +++ b/tests/test_dereference.py @@ -1029,7 +1029,6 @@ class FieldTest(unittest.TestCase): self.assertEqual(type(foo.bar), Bar) self.assertEqual(type(foo.baz), Baz) - def test_document_reload_reference_integrity(self): """ Ensure reloading a document with multiple similar id From d6e39b362b7837aa4ca3f84f5b9eccb3adf3e808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Fri, 7 Sep 2018 22:51:17 +0200 Subject: [PATCH 42/85] Updating inheritance doc Fixes #429 --- docs/guide/defining-documents.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index 62cbcd67..bf74ad8c 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -736,6 +736,9 @@ document.:: .. note:: From 0.8 onwards :attr:`allow_inheritance` defaults to False, meaning you must set it to True to use inheritance. + + Setting :attr:`allow_inheritance` to True should also be used in + :class:`~mongoengine.EmbeddedDocument` class in case you need to subclass it Working with existing data -------------------------- From 4314fa883fffbbfee056028a4846f98333748e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sun, 9 Sep 2018 15:50:48 +0200 Subject: [PATCH 43/85] improve 2-3 codebase compatibility --- docs/code/tumblelog.py | 26 +++++++++++++------------- mongoengine/base/common.py | 8 ++++---- mongoengine/base/datastructures.py | 2 +- mongoengine/base/document.py | 4 ++-- mongoengine/base/fields.py | 2 +- mongoengine/dereference.py | 2 +- mongoengine/document.py | 12 ++++-------- mongoengine/fields.py | 13 +++++++++++-- mongoengine/queryset/base.py | 17 ++++++++--------- mongoengine/queryset/field_list.py | 4 +++- mongoengine/queryset/manager.py | 2 +- mongoengine/queryset/queryset.py | 4 ++-- setup.py | 3 +-- tests/__init__.py | 8 ++++---- tests/document/__init__.py | 16 ++++++++-------- tests/fields/__init__.py | 6 +++--- tests/queryset/__init__.py | 12 ++++++------ 17 files changed, 73 insertions(+), 68 deletions(-) diff --git a/docs/code/tumblelog.py b/docs/code/tumblelog.py index c10160ea..796336e6 100644 --- a/docs/code/tumblelog.py +++ b/docs/code/tumblelog.py @@ -45,27 +45,27 @@ post2.link_url = 'http://tractiondigital.com/labs/mongoengine/docs' post2.tags = ['mongoengine'] post2.save() -print 'ALL POSTS' -print +print('ALL POSTS') +print() for post in Post.objects: - print post.title + print(post.title) #print '=' * post.title.count() - print "=" * 20 + print("=" * 20) if isinstance(post, TextPost): - print post.content + print(post.content) if isinstance(post, LinkPost): - print 'Link:', post.link_url + print('Link:', post.link_url) - print -print + print() +print() -print 'POSTS TAGGED \'MONGODB\'' -print +print('POSTS TAGGED \'MONGODB\'') +print() for post in Post.objects(tags='mongodb'): - print post.title -print + print(post.title) +print() num_posts = Post.objects(tags='mongodb').count() -print 'Found %d posts with tag "mongodb"' % num_posts +print('Found %d posts with tag "mongodb"' % num_posts) diff --git a/mongoengine/base/common.py b/mongoengine/base/common.py index f80471ef..dd177920 100644 --- a/mongoengine/base/common.py +++ b/mongoengine/base/common.py @@ -3,10 +3,10 @@ from mongoengine.errors import NotRegistered __all__ = ('UPDATE_OPERATORS', 'get_document', '_document_registry') -UPDATE_OPERATORS = set(['set', 'unset', 'inc', 'dec', 'mul', - 'pop', 'push', 'push_all', 'pull', - 'pull_all', 'add_to_set', 'set_on_insert', - 'min', 'max', 'rename']) +UPDATE_OPERATORS = {'set', 'unset', 'inc', 'dec', 'mul', + 'pop', 'push', 'push_all', 'pull', + 'pull_all', 'add_to_set', 'set_on_insert', + 'min', 'max', 'rename'} _document_registry = {} diff --git a/mongoengine/base/datastructures.py b/mongoengine/base/datastructures.py index db292f14..0197ad10 100644 --- a/mongoengine/base/datastructures.py +++ b/mongoengine/base/datastructures.py @@ -377,7 +377,7 @@ class EmbeddedDocumentList(BaseList): class StrictDict(object): __slots__ = () - _special_fields = set(['get', 'pop', 'iteritems', 'items', 'keys', 'create']) + _special_fields = {'get', 'pop', 'iteritems', 'items', 'keys', 'create'} _classes = {} def __init__(self, **kwargs): diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 85906a3e..fdebab23 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -302,7 +302,7 @@ class BaseDocument(object): data['_cls'] = self._class_name # only root fields ['test1.a', 'test2'] => ['test1', 'test2'] - root_fields = set([f.split('.')[0] for f in fields]) + root_fields = {f.split('.')[0] for f in fields} for field_name in self: if root_fields and field_name not in root_fields: @@ -567,7 +567,7 @@ class BaseDocument(object): continue elif isinstance(field, SortedListField) and field._ordering: # if ordering is affected whole list is changed - if any(map(lambda d: field._ordering in d._changed_fields, data)): + if any(field._ordering in d._changed_fields for d in data): changed_fields.append(db_field_name) continue diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py index d25d4305..a0726aa6 100644 --- a/mongoengine/base/fields.py +++ b/mongoengine/base/fields.py @@ -501,7 +501,7 @@ class GeoJsonBaseField(BaseField): def validate(self, value): """Validate the GeoJson object based on its type.""" if isinstance(value, dict): - if set(value.keys()) == set(['type', 'coordinates']): + if set(value.keys()) == {'type', 'coordinates'}: if value['type'] != self._type: self.error('%s type must be "%s"' % (self._name, self._type)) diff --git a/mongoengine/dereference.py b/mongoengine/dereference.py index 40bc72b2..6c993223 100644 --- a/mongoengine/dereference.py +++ b/mongoengine/dereference.py @@ -146,7 +146,7 @@ class DeReference(object): for key, doc in references.iteritems(): object_map[(col_name, key)] = doc else: # Generic reference: use the refs data to convert to document - if isinstance(doc_type, (ListField, DictField, MapField,)): + if isinstance(doc_type, (ListField, DictField, MapField)): continue refs = [dbref for dbref in dbrefs diff --git a/mongoengine/document.py b/mongoengine/document.py index 25af273d..cdeed4c6 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -39,7 +39,7 @@ class InvalidCollectionError(Exception): pass -class EmbeddedDocument(BaseDocument): +class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)): """A :class:`~mongoengine.Document` that isn't stored in its own collection. :class:`~mongoengine.EmbeddedDocument`\ s should be used as fields on :class:`~mongoengine.Document`\ s through the @@ -58,7 +58,6 @@ class EmbeddedDocument(BaseDocument): # The __metaclass__ attribute is removed by 2to3 when running with Python3 # my_metaclass is defined so that metaclass can be queried in Python 2 & 3 my_metaclass = DocumentMetaclass - __metaclass__ = DocumentMetaclass # A generic embedded document doesn't have any immutable properties # that describe it uniquely, hence it shouldn't be hashable. You can @@ -95,7 +94,7 @@ class EmbeddedDocument(BaseDocument): self._instance.reload(*args, **kwargs) -class Document(BaseDocument): +class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): """The base class used for defining the structure and properties of collections of documents stored in MongoDB. Inherit from this class, and add fields as class attributes to define a document's structure. @@ -150,7 +149,6 @@ class Document(BaseDocument): # The __metaclass__ attribute is removed by 2to3 when running with Python3 # my_metaclass is defined so that metaclass can be queried in Python 2 & 3 my_metaclass = TopLevelDocumentMetaclass - __metaclass__ = TopLevelDocumentMetaclass __slots__ = ('__objects',) @@ -996,7 +994,7 @@ class Document(BaseDocument): return {'missing': missing, 'extra': extra} -class DynamicDocument(Document): +class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)): """A Dynamic Document class allowing flexible, expandable and uncontrolled schemas. As a :class:`~mongoengine.Document` subclass, acts in the same way as an ordinary document but has expanded style properties. Any data @@ -1013,7 +1011,6 @@ class DynamicDocument(Document): # The __metaclass__ attribute is removed by 2to3 when running with Python3 # my_metaclass is defined so that metaclass can be queried in Python 2 & 3 my_metaclass = TopLevelDocumentMetaclass - __metaclass__ = TopLevelDocumentMetaclass _dynamic = True @@ -1029,7 +1026,7 @@ class DynamicDocument(Document): super(DynamicDocument, self).__delattr__(*args, **kwargs) -class DynamicEmbeddedDocument(EmbeddedDocument): +class DynamicEmbeddedDocument(six.with_metaclass(DocumentMetaclass, EmbeddedDocument)): """A Dynamic Embedded Document class allowing flexible, expandable and uncontrolled schemas. See :class:`~mongoengine.DynamicDocument` for more information about dynamic documents. @@ -1038,7 +1035,6 @@ class DynamicEmbeddedDocument(EmbeddedDocument): # The __metaclass__ attribute is removed by 2to3 when running with Python3 # my_metaclass is defined so that metaclass can be queried in Python 2 & 3 my_metaclass = DocumentMetaclass - __metaclass__ = DocumentMetaclass _dynamic = True diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 244551d2..f794edff 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -24,6 +24,7 @@ try: except ImportError: Int64 = long + from mongoengine.base import (BaseDocument, BaseField, ComplexBaseField, GeoJsonBaseField, LazyReference, ObjectIdField, get_document) @@ -41,6 +42,12 @@ except ImportError: Image = None ImageOps = None +if six.PY3: + # Useless as long as 2to3 gets executed + # as it turns `long` into `int` blindly + long = int + + __all__ = ( 'StringField', 'URLField', 'EmailField', 'IntField', 'LongField', 'FloatField', 'DecimalField', 'BooleanField', 'DateTimeField', 'DateField', @@ -597,7 +604,7 @@ class ComplexDateTimeField(StringField): >>> ComplexDateTimeField()._convert_from_string(a) datetime.datetime(2011, 6, 8, 20, 26, 24, 92284) """ - values = map(int, data.split(self.separator)) + values = [int(d) for d in data.split(self.separator)] return datetime.datetime(*values) def __get__(self, instance, owner): @@ -1525,9 +1532,11 @@ class GridFSProxy(object): def __get__(self, instance, value): return self - def __nonzero__(self): + def __bool__(self): return bool(self.grid_id) + __nonzero__ = __bool__ # For Py2 support + def __getstate__(self): self_dict = self.__dict__ self_dict['_fs'] = None diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index 6fddc381..0be48654 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -2,7 +2,6 @@ from __future__ import absolute_import import copy import itertools -import operator import pprint import re import warnings @@ -209,14 +208,12 @@ class BaseQuerySet(object): queryset = self.order_by() return False if queryset.first() is None else True - def __nonzero__(self): - """Avoid to open all records in an if stmt in Py2.""" - return self._has_data() - def __bool__(self): """Avoid to open all records in an if stmt in Py3.""" return self._has_data() + __nonzero__ = __bool__ # For Py2 support + # Core functions def all(self): @@ -269,13 +266,13 @@ class BaseQuerySet(object): queryset = queryset.filter(*q_objs, **query) try: - result = queryset.next() + result = six.next(queryset) except StopIteration: msg = ('%s matching query does not exist.' % queryset._document._class_name) raise queryset._document.DoesNotExist(msg) try: - queryset.next() + six.next(queryset) except StopIteration: return result @@ -1478,13 +1475,13 @@ class BaseQuerySet(object): # Iterator helpers - def next(self): + def __next__(self): """Wrap the result in a :class:`~mongoengine.Document` object. """ if self._limit == 0 or self._none: raise StopIteration - raw_doc = self._cursor.next() + raw_doc = six.next(self._cursor) if self._as_pymongo: return self._get_as_pymongo(raw_doc) @@ -1498,6 +1495,8 @@ class BaseQuerySet(object): return doc + next = __next__ # For Python2 support + def rewind(self): """Rewind the cursor to its unevaluated state. diff --git a/mongoengine/queryset/field_list.py b/mongoengine/queryset/field_list.py index 0524c3bb..dba724af 100644 --- a/mongoengine/queryset/field_list.py +++ b/mongoengine/queryset/field_list.py @@ -63,9 +63,11 @@ class QueryFieldList(object): self._only_called = True return self - def __nonzero__(self): + def __bool__(self): return bool(self.fields) + __nonzero__ = __bool__ # For Py2 support + def as_dict(self): field_list = {field: self.value for field in self.fields} if self.slice: diff --git a/mongoengine/queryset/manager.py b/mongoengine/queryset/manager.py index 199205e9..f93dbb43 100644 --- a/mongoengine/queryset/manager.py +++ b/mongoengine/queryset/manager.py @@ -36,7 +36,7 @@ class QuerySetManager(object): queryset_class = owner._meta.get('queryset_class', self.default) queryset = queryset_class(owner, owner._get_collection()) if self.get_queryset: - arg_count = self.get_queryset.func_code.co_argcount + arg_count = self.get_queryset.__code__.co_argcount if arg_count == 1: queryset = self.get_queryset(queryset) elif arg_count == 2: diff --git a/mongoengine/queryset/queryset.py b/mongoengine/queryset/queryset.py index 1aadfb76..f9fed7b7 100644 --- a/mongoengine/queryset/queryset.py +++ b/mongoengine/queryset/queryset.py @@ -115,7 +115,7 @@ class QuerySet(BaseQuerySet): # the result cache. try: for _ in six.moves.range(ITER_CHUNK_SIZE): - self._result_cache.append(self.next()) + self._result_cache.append(six.next(self)) except StopIteration: # Getting this exception means there are no more docs in the # db cursor. Set _has_more to False so that we can use that @@ -170,7 +170,7 @@ class QuerySetNoCache(BaseQuerySet): data = [] for _ in six.moves.range(REPR_OUTPUT_SIZE + 1): try: - data.append(self.next()) + data.append(six.next(self)) except StopIteration: break diff --git a/setup.py b/setup.py index 98964d19..c7632ce3 100644 --- a/setup.py +++ b/setup.py @@ -44,9 +44,8 @@ CLASSIFIERS = [ "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", 'Topic :: Database', diff --git a/tests/__init__.py b/tests/__init__.py index eab0ddc7..08db7186 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -from all_warnings import AllWarnings -from document import * -from queryset import * -from fields import * +from .all_warnings import AllWarnings +from .document import * +from .queryset import * +from .fields import * diff --git a/tests/document/__init__.py b/tests/document/__init__.py index f71376ea..dc35c969 100644 --- a/tests/document/__init__.py +++ b/tests/document/__init__.py @@ -1,13 +1,13 @@ import unittest -from class_methods import * -from delta import * -from dynamic import * -from indexes import * -from inheritance import * -from instance import * -from json_serialisation import * -from validation import * +from .class_methods import * +from .delta import * +from .dynamic import * +from .indexes import * +from .inheritance import * +from .instance import * +from .json_serialisation import * +from .validation import * if __name__ == '__main__': unittest.main() diff --git a/tests/fields/__init__.py b/tests/fields/__init__.py index 8e0640db..4994d0c6 100644 --- a/tests/fields/__init__.py +++ b/tests/fields/__init__.py @@ -1,3 +1,3 @@ -from fields import * -from file_tests import * -from geo import * +from .fields import * +from .file_tests import * +from .geo import * diff --git a/tests/queryset/__init__.py b/tests/queryset/__init__.py index c36b2684..31016966 100644 --- a/tests/queryset/__init__.py +++ b/tests/queryset/__init__.py @@ -1,6 +1,6 @@ -from transform import * -from field_list import * -from queryset import * -from visitor import * -from geo import * -from modify import * \ No newline at end of file +from .transform import * +from .field_list import * +from .queryset import * +from .visitor import * +from .geo import * +from .modify import * From f83ae5789b26b09192b4bdbefedd5cfffa6cc04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sun, 16 Sep 2018 21:27:09 +0200 Subject: [PATCH 44/85] fix side effect from queryset's no_dereference #1677 --- mongoengine/base/document.py | 2 +- mongoengine/base/fields.py | 8 ++-- mongoengine/fields.py | 5 ++- tests/queryset/queryset.py | 84 +++++++++++++++++++++++++++++++----- 4 files changed, 83 insertions(+), 16 deletions(-) diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 84acb5a2..c82d670e 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -700,7 +700,7 @@ class BaseDocument(object): fields = cls._fields if not _auto_dereference: - fields = copy.copy(fields) + fields = copy.deepcopy(fields) for field_name, field in fields.iteritems(): field._auto_dereference = _auto_dereference diff --git a/mongoengine/base/fields.py b/mongoengine/base/fields.py index a0726aa6..6c1bf4f1 100644 --- a/mongoengine/base/fields.py +++ b/mongoengine/base/fields.py @@ -266,13 +266,15 @@ class ComplexBaseField(BaseField): ReferenceField = _import_class('ReferenceField') GenericReferenceField = _import_class('GenericReferenceField') EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField') - dereference = (self._auto_dereference and + + auto_dereference = instance._fields[self.name]._auto_dereference + + dereference = (auto_dereference and (self.field is None or isinstance(self.field, (GenericReferenceField, ReferenceField)))) _dereference = _import_class('DeReference')() - self._auto_dereference = instance._fields[self.name]._auto_dereference if instance._initialised and dereference and instance._data.get(self.name): instance._data[self.name] = _dereference( instance._data.get(self.name), max_depth=1, instance=instance, @@ -293,7 +295,7 @@ class ComplexBaseField(BaseField): value = BaseDict(value, instance, self.name) instance._data[self.name] = value - if (self._auto_dereference and instance._initialised and + if (auto_dereference and instance._initialised and isinstance(value, (BaseList, BaseDict)) and not value._dereferenced): value = _dereference( diff --git a/mongoengine/fields.py b/mongoengine/fields.py index f794edff..731ab06b 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -1104,9 +1104,9 @@ class ReferenceField(BaseField): # Get value from document instance if available value = instance._data.get(self.name) - self._auto_dereference = instance._fields[self.name]._auto_dereference + auto_dereference = instance._fields[self.name]._auto_dereference # Dereference DBRefs - if self._auto_dereference and isinstance(value, DBRef): + if auto_dereference and isinstance(value, DBRef): if hasattr(value, 'cls'): # Dereference using the class type specified in the reference cls = get_document(value.cls) @@ -1267,6 +1267,7 @@ class CachedReferenceField(BaseField): # Get value from document instance if available value = instance._data.get(self.name) self._auto_dereference = instance._fields[self.name]._auto_dereference + # Dereference DBRefs if self._auto_dereference and isinstance(value, DBRef): dereferenced = self.document_type._get_db().dereference(value) diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 0f2364f7..35ebe24d 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -4674,11 +4674,69 @@ class QuerySetTest(unittest.TestCase): User(name="Bob Dole", organization=whitehouse).save() qs = User.objects() + qs_user = qs.first() + self.assertIsInstance(qs.first().organization, Organization) - self.assertNotIsInstance(qs.no_dereference().first().organization, Organization) - self.assertNotIsInstance(qs.no_dereference().get().organization, Organization) + + self.assertIsInstance(qs.no_dereference().first().organization, DBRef) + + self.assertIsInstance(qs_user.organization, Organization) self.assertIsInstance(qs.first().organization, Organization) + def test_no_dereference_internals(self): + # Test the internals on which queryset.no_dereference relies on + class Organization(Document): + name = StringField() + + class User(Document): + organization = ReferenceField(Organization) + + User.drop_collection() + Organization.drop_collection() + + cls_organization_field = User.organization + self.assertTrue(cls_organization_field._auto_dereference, True) # default + + org = Organization(name="whatever").save() + User(organization=org).save() + + qs_no_deref = User.objects().no_dereference() + user_no_deref = qs_no_deref.first() + self.assertFalse(qs_no_deref._auto_dereference) + + # Make sure the instance field is different from the class field + instance_org_field = user_no_deref._fields['organization'] + self.assertIsNot(instance_org_field, cls_organization_field) + self.assertFalse(instance_org_field._auto_dereference) + + self.assertIsInstance(user_no_deref.organization, DBRef) + self.assertTrue(cls_organization_field._auto_dereference, True) # Make sure the class Field wasn't altered + + def test_no_dereference_no_side_effect_on_existing_instance(self): + # Relates to issue #1677 - ensures no regression of the bug + + class Organization(Document): + name = StringField() + + class User(Document): + organization = ReferenceField(Organization) + + User.drop_collection() + Organization.drop_collection() + + org = Organization(name="whatever").save() + User(organization=org).save() + + qs = User.objects() + user = qs.first() + + qs_no_deref = User.objects().no_dereference() + user_no_deref = qs_no_deref.first() + + no_derf_org = user_no_deref.organization # was triggering the bug + self.assertIsInstance(no_derf_org, DBRef) + self.assertIsInstance(user.organization, Organization) + def test_no_dereference_embedded_doc(self): class User(Document): @@ -4693,7 +4751,7 @@ class QuerySetTest(unittest.TestCase): members = ListField(EmbeddedDocumentField(Member)) ceo = ReferenceField(User) member = EmbeddedDocumentField(Member) - admin = ListField(ReferenceField(User)) + admins = ListField(ReferenceField(User)) Organization.drop_collection() User.drop_collection() @@ -4703,16 +4761,22 @@ class QuerySetTest(unittest.TestCase): member = Member(name="Flash", user=user) - company = Organization(name="Mongo Inc", ceo=user, member=member) - company.admin.append(user) - company.members.append(member) + company = Organization(name="Mongo Inc", + ceo=user, + member=member, + admins=[user], + members=[member]) company.save() - result = Organization.objects().no_dereference().first() + org = Organization.objects().no_dereference().first() - self.assertIsInstance(result.admin[0], (DBRef, ObjectId)) - self.assertIsInstance(result.member.user, (DBRef, ObjectId)) - self.assertIsInstance(result.members[0].user, (DBRef, ObjectId)) + self.assertNotEqual(id(org._fields['admins']), id(Organization.admins)) + self.assertFalse(org._fields['admins']._auto_dereference) + + admin = org.admins[0] + self.assertIsInstance(admin, DBRef) + self.assertIsInstance(org.member.user, DBRef) + self.assertIsInstance(org.members[0].user, DBRef) def test_cached_queryset(self): class Person(Document): From 45c2151d0fe26d4d17ff671650f0028ffe5a1921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 25 Sep 2018 22:56:57 +0200 Subject: [PATCH 45/85] Fix side effect of no_dereference on GenericReferenceField --- mongoengine/fields.py | 8 ++++---- tests/queryset/queryset.py | 10 +++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 648940d2..85602215 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -1266,10 +1266,10 @@ class CachedReferenceField(BaseField): # Get value from document instance if available value = instance._data.get(self.name) - self._auto_dereference = instance._fields[self.name]._auto_dereference + auto_dereference = instance._fields[self.name]._auto_dereference # Dereference DBRefs - if self._auto_dereference and isinstance(value, DBRef): + if auto_dereference and isinstance(value, DBRef): dereferenced = self.document_type._get_db().dereference(value) if dereferenced is None: raise DoesNotExist('Trying to dereference unknown document %s' % value) @@ -1402,8 +1402,8 @@ class GenericReferenceField(BaseField): value = instance._data.get(self.name) - self._auto_dereference = instance._fields[self.name]._auto_dereference - if self._auto_dereference and isinstance(value, (dict, SON)): + auto_dereference = instance._fields[self.name]._auto_dereference + if auto_dereference and isinstance(value, (dict, SON)): dereferenced = self.dereference(value) if dereferenced is None: raise DoesNotExist('Trying to dereference unknown document %s' % value) diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 35ebe24d..4706305f 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -4720,12 +4720,14 @@ class QuerySetTest(unittest.TestCase): class User(Document): organization = ReferenceField(Organization) + organization_gen = GenericReferenceField() User.drop_collection() Organization.drop_collection() org = Organization(name="whatever").save() - User(organization=org).save() + User(organization=org, + organization_gen=org).save() qs = User.objects() user = qs.first() @@ -4733,10 +4735,16 @@ class QuerySetTest(unittest.TestCase): qs_no_deref = User.objects().no_dereference() user_no_deref = qs_no_deref.first() + # ReferenceField no_derf_org = user_no_deref.organization # was triggering the bug self.assertIsInstance(no_derf_org, DBRef) self.assertIsInstance(user.organization, Organization) + # GenericReferenceField + no_derf_org_gen = user_no_deref.organization_gen + self.assertIsInstance(no_derf_org_gen, dict) + self.assertIsInstance(user.organization_gen, Organization) + def test_no_dereference_embedded_doc(self): class User(Document): From 96f09196337718699e73d7e55679affec48304a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Fri, 28 Sep 2018 22:39:08 +0200 Subject: [PATCH 46/85] - Fixed tests to allow support of MongoDB 3.2 - Replaced MongoDB 2.4 tests in CI by MongoDB 3.2 --- .install_mongodb_on_travis.sh | 6 ++ .travis.yml | 12 ++-- README.rst | 4 +- mongoengine/context_managers.py | 6 +- tests/document/indexes.py | 39 +++++----- tests/queryset/queryset.py | 122 +++++++++++++++++--------------- tests/test_context_managers.py | 1 - tests/utils.py | 31 +++++--- 8 files changed, 126 insertions(+), 95 deletions(-) diff --git a/.install_mongodb_on_travis.sh b/.install_mongodb_on_travis.sh index f2018411..a4ff085b 100644 --- a/.install_mongodb_on_travis.sh +++ b/.install_mongodb_on_travis.sh @@ -18,6 +18,12 @@ elif [ "$MONGODB" = "3.0" ]; then sudo apt-get update sudo apt-get install mongodb-org-server=3.0.14 # service should be started automatically +elif [ "$MONGODB" = "3.2" ]; then + sudo apt-key adv --keyserver keyserver.ubuntu.com --recv EA312927 + echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.2.list + sudo apt-get update + sudo apt-get install mongodb-org-server=3.2.20 + # service should be started automatically else echo "Invalid MongoDB version, expected 2.4, 2.6, or 3.0." exit 1 diff --git a/.travis.yml b/.travis.yml index 381f7385..f9c7db1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,17 +27,17 @@ matrix: include: - python: 2.7 - env: MONGODB=2.4 PYMONGO=3.5 + env: MONGODB=3.0 PYMONGO=3.5 - python: 2.7 - env: MONGODB=3.0 PYMONGO=3.x + env: MONGODB=3.2 PYMONGO=3.x - python: 3.5 - env: MONGODB=2.4 PYMONGO=3.5 + env: MONGODB=3.0 PYMONGO=3.5 - python: 3.5 - env: MONGODB=3.0 PYMONGO=3.x + env: MONGODB=3.2 PYMONGO=3.x - python: 3.6 - env: MONGODB=2.4 PYMONGO=3.5 + env: MONGODB=3.0 PYMONGO=3.5 - python: 3.6 - env: MONGODB=3.0 PYMONGO=3.x + env: MONGODB=3.2 PYMONGO=3.x before_install: - bash .install_mongodb_on_travis.sh diff --git a/README.rst b/README.rst index e1e2aef6..17cb3e33 100644 --- a/README.rst +++ b/README.rst @@ -26,10 +26,10 @@ an `API reference `_. Supported MongoDB Versions ========================== -MongoEngine is currently tested against MongoDB v2.4, v2.6, and v3.0. Future +MongoEngine is currently tested against MongoDB v2.6, v3.0 and v3.2. Future versions should be supported as well, but aren't actively tested at the moment. Make sure to open an issue or submit a pull request if you experience any -problems with MongoDB v3.2+. +problems with MongoDB v3.4+. Installation ============ diff --git a/mongoengine/context_managers.py b/mongoengine/context_managers.py index ee1f5e01..c26b0a79 100644 --- a/mongoengine/context_managers.py +++ b/mongoengine/context_managers.py @@ -182,8 +182,10 @@ class query_counter(object): self._ignored_query = { 'ns': {'$ne': '%s.system.indexes' % self.db.name}, - 'op': - {'$ne': 'killcursors'} + 'op': # MONGODB < 3.2 + {'$ne': 'killcursors'}, + 'command.killCursors': # MONGODB >= 3.2 + {'$exists': False} } def _turn_on_profiling(self): diff --git a/tests/document/indexes.py b/tests/document/indexes.py index 1cbb4ec3..b3129fc8 100644 --- a/tests/document/indexes.py +++ b/tests/document/indexes.py @@ -1,15 +1,14 @@ # -*- coding: utf-8 -*- import unittest -import sys +from datetime import datetime from nose.plugins.skip import SkipTest -from datetime import datetime +from pymongo.errors import OperationFailure import pymongo from mongoengine import * from mongoengine.connection import get_db - -from tests.utils import get_mongodb_version, needs_mongodb_v26 +from tests.utils import get_mongodb_version, needs_mongodb_v26, MONGODB_32, MONGODB_3 __all__ = ("IndexesTest", ) @@ -19,6 +18,7 @@ class IndexesTest(unittest.TestCase): def setUp(self): self.connection = connect(db='mongoenginetest') self.db = get_db() + self.mongodb_version = get_mongodb_version() class Person(Document): name = StringField() @@ -491,7 +491,7 @@ class IndexesTest(unittest.TestCase): obj = Test(a=1) obj.save() - IS_MONGODB_3 = get_mongodb_version()[0] >= 3 + IS_MONGODB_3 = get_mongodb_version() >= MONGODB_3 # Need to be explicit about covered indexes as mongoDB doesn't know if # the documents returned might have more keys in that here. @@ -541,19 +541,24 @@ class IndexesTest(unittest.TestCase): [('categories', 1), ('_id', 1)]) def test_hint(self): + MONGO_VER = self.mongodb_version + TAGS_INDEX_NAME = 'tags_1' class BlogPost(Document): tags = ListField(StringField()) meta = { 'indexes': [ - 'tags', + { + 'fields': ['tags'], + 'name': TAGS_INDEX_NAME + } ], } BlogPost.drop_collection() - for i in range(0, 10): - tags = [("tag %i" % n) for n in range(0, i % 2)] + for i in range(10): + tags = [("tag %i" % n) for n in range(i % 2)] BlogPost(tags=tags).save() self.assertEqual(BlogPost.objects.count(), 10) @@ -563,18 +568,18 @@ class IndexesTest(unittest.TestCase): if pymongo.version != '3.0': self.assertEqual(BlogPost.objects.hint([('tags', 1)]).count(), 10) + if MONGO_VER == MONGODB_32: + # Mongo32 throws an error if an index exists (i.e `tags` in our case) + # and you use hint on an index name that does not exist + with self.assertRaises(OperationFailure): + BlogPost.objects.hint([('ZZ', 1)]).count() + else: self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10) - if pymongo.version >= '2.8': - self.assertEqual(BlogPost.objects.hint('tags').count(), 10) - else: - def invalid_index(): - BlogPost.objects.hint('tags').next() - self.assertRaises(TypeError, invalid_index) + self.assertEqual(BlogPost.objects.hint(TAGS_INDEX_NAME ).count(), 10) - def invalid_index_2(): - return BlogPost.objects.hint(('tags', 1)).next() - self.assertRaises(Exception, invalid_index_2) + with self.assertRaises(Exception): + BlogPost.objects.hint(('tags', 1)).next() def test_unique(self): """Ensure that uniqueness constraints are applied to fields. diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 35ebe24d..050f65d9 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -21,8 +21,7 @@ from mongoengine.python_support import IS_PYMONGO_3 from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned, QuerySet, QuerySetManager, queryset_manager) -from tests.utils import needs_mongodb_v26, skip_pymongo3 - +from tests.utils import needs_mongodb_v26, skip_pymongo3, get_mongodb_version, MONGODB_32 __all__ = ("QuerySetTest",) @@ -30,10 +29,8 @@ __all__ = ("QuerySetTest",) class db_ops_tracker(query_counter): def get_ops(self): - ignore_query = { - 'ns': {'$ne': '%s.system.indexes' % self.db.name}, - 'command.count': {'$ne': 'system.profile'} - } + ignore_query = dict(self._ignored_query) + ignore_query['command.count'] = {'$ne': 'system.profile'} # Ignore the query issued by query_counter return list(self.db.system.profile.find(ignore_query)) @@ -56,6 +53,8 @@ class QuerySetTest(unittest.TestCase): self.PersonMeta = PersonMeta self.Person = Person + self.mongodb_version = get_mongodb_version() + def test_initialisation(self): """Ensure that a QuerySet is correctly initialised by QuerySetManager. """ @@ -813,8 +812,8 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(record.embed.field, 2) def test_bulk_insert(self): - """Ensure that bulk insert works - """ + """Ensure that bulk insert works""" + MONGO_VER = self.mongodb_version class Comment(EmbeddedDocument): name = StringField() @@ -832,35 +831,41 @@ class QuerySetTest(unittest.TestCase): # get MongoDB version info connection = get_connection() info = connection.test.command('buildInfo') - mongodb_version = tuple([int(i) for i in info['version'].split('.')]) # Recreates the collection self.assertEqual(0, Blog.objects.count()) + comment1 = Comment(name='testa') + comment2 = Comment(name='testb') + post1 = Post(comments=[comment1, comment2]) + post2 = Post(comments=[comment2, comment2]) + blogs = [Blog(title="post %s" % i, posts=[post1, post2]) + for i in range(99)] + + # Check bulk insert using load_bulk=False with query_counter() as q: self.assertEqual(q, 0) - - comment1 = Comment(name='testa') - comment2 = Comment(name='testb') - post1 = Post(comments=[comment1, comment2]) - post2 = Post(comments=[comment2, comment2]) - - blogs = [] - for i in range(1, 100): - blogs.append(Blog(title="post %s" % i, posts=[post1, post2])) - Blog.objects.insert(blogs, load_bulk=False) - # profiling logs each doc now in the bulk op - self.assertEqual(q, 99) + + if MONGO_VER == MONGODB_32: + self.assertEqual(q, 1) # 1 entry containing the list of inserts + else: + self.assertEqual(q, len(blogs)) # 1 entry per doc inserted + + self.assertEqual(Blog.objects.count(), len(blogs)) Blog.drop_collection() Blog.ensure_indexes() + # Check bulk insert using load_bulk=True with query_counter() as q: self.assertEqual(q, 0) - Blog.objects.insert(blogs) - self.assertEqual(q, 100) # 99 for insert 1 for fetch + + if MONGO_VER == MONGODB_32: + self.assertEqual(q, 2) # 1 for insert 1 for fetch + else: + self.assertEqual(q, len(blogs)+1) # + 1 to fetch all docs Blog.drop_collection() @@ -1262,6 +1267,9 @@ class QuerySetTest(unittest.TestCase): """Ensure that the default ordering can be cleared by calling order_by() w/o any arguments. """ + MONGO_VER = self.mongodb_version + ORDER_BY_KEY = 'sort' if MONGO_VER == MONGODB_32 else '$orderby' + class BlogPost(Document): title = StringField() published_date = DateTimeField() @@ -1277,7 +1285,7 @@ class QuerySetTest(unittest.TestCase): BlogPost.objects.filter(title='whatever').first() self.assertEqual(len(q.get_ops()), 1) self.assertEqual( - q.get_ops()[0]['query']['$orderby'], + q.get_ops()[0]['query'][ORDER_BY_KEY], {'published_date': -1} ) @@ -1285,14 +1293,14 @@ class QuerySetTest(unittest.TestCase): with db_ops_tracker() as q: BlogPost.objects.filter(title='whatever').order_by().first() self.assertEqual(len(q.get_ops()), 1) - self.assertNotIn('$orderby', q.get_ops()[0]['query']) + self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query']) # calling an explicit order_by should use a specified sort with db_ops_tracker() as q: BlogPost.objects.filter(title='whatever').order_by('published_date').first() self.assertEqual(len(q.get_ops()), 1) self.assertEqual( - q.get_ops()[0]['query']['$orderby'], + q.get_ops()[0]['query'][ORDER_BY_KEY], {'published_date': 1} ) @@ -1301,11 +1309,14 @@ class QuerySetTest(unittest.TestCase): qs = BlogPost.objects.filter(title='whatever').order_by('published_date') qs.order_by().first() self.assertEqual(len(q.get_ops()), 1) - self.assertNotIn('$orderby', q.get_ops()[0]['query']) + self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query']) def test_no_ordering_for_get(self): """ Ensure that Doc.objects.get doesn't use any ordering. """ + MONGO_VER = self.mongodb_version + ORDER_BY_KEY = 'sort' if MONGO_VER == MONGODB_32 else '$orderby' + class BlogPost(Document): title = StringField() published_date = DateTimeField() @@ -1320,13 +1331,13 @@ class QuerySetTest(unittest.TestCase): with db_ops_tracker() as q: BlogPost.objects.get(title='whatever') self.assertEqual(len(q.get_ops()), 1) - self.assertNotIn('$orderby', q.get_ops()[0]['query']) + self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query']) # Ordering should be ignored for .get even if we set it explicitly with db_ops_tracker() as q: BlogPost.objects.order_by('-title').get(title='whatever') self.assertEqual(len(q.get_ops()), 1) - self.assertNotIn('$orderby', q.get_ops()[0]['query']) + self.assertNotIn(ORDER_BY_KEY, q.get_ops()[0]['query']) def test_find_embedded(self): """Ensure that an embedded document is properly returned from @@ -2450,7 +2461,11 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(names, ['User A', 'User B', 'User C']) def test_comment(self): - """Make sure adding a comment to the query works.""" + """Make sure adding a comment to the query gets added to the query""" + MONGO_VER = self.mongodb_version + QUERY_KEY = 'filter' if MONGO_VER == MONGODB_32 else '$query' + COMMENT_KEY = 'comment' if MONGO_VER == MONGODB_32 else '$comment' + class User(Document): age = IntField() @@ -2466,8 +2481,8 @@ class QuerySetTest(unittest.TestCase): ops = q.get_ops() self.assertEqual(len(ops), 2) for op in ops: - self.assertEqual(op['query']['$query'], {'age': {'$gte': 18}}) - self.assertEqual(op['query']['$comment'], 'looking for an adult') + self.assertEqual(op['query'][QUERY_KEY], {'age': {'$gte': 18}}) + self.assertEqual(op['query'][COMMENT_KEY], 'looking for an adult') def test_map_reduce(self): """Ensure map/reduce is both mapping and reducing. @@ -4817,27 +4832,18 @@ class QuerySetTest(unittest.TestCase): for i in range(100): Person(name="No: %s" % i).save() - with query_counter() as q: - try: - self.assertEqual(q, 0) - people = Person.objects.no_cache() + with query_counter() as q: + self.assertEqual(q, 0) + people = Person.objects.no_cache() - [x for x in people] - self.assertEqual(q, 1) + [x for x in people] + self.assertEqual(q, 1) - list(people) - self.assertEqual(q, 2) - - people.count() - self.assertEqual(q, 3) - except AssertionError as exc: - db = get_db() - msg = '' - for q in list(db.system.profile.find())[-50:]: - msg += str([q['ts'], q['ns'], q.get('query'), q['op']])+'\n' - msg += str(q) - raise AssertionError(str(exc) + '\n'+msg) + list(people) + self.assertEqual(q, 2) + people.count() + self.assertEqual(q, 3) def test_cache_not_cloned(self): @@ -5111,35 +5117,39 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(op['nreturned'], 1) def test_bool_with_ordering(self): + MONGO_VER = self.mongodb_version + ORDER_BY_KEY = 'sort' if MONGO_VER == MONGODB_32 else '$orderby' class Person(Document): name = StringField() Person.drop_collection() + Person(name="Test").save() + # Check that bool(queryset) does not uses the orderby qs = Person.objects.order_by('name') - with query_counter() as q: - if qs: + if bool(qs): pass op = q.db.system.profile.find({"ns": {"$ne": "%s.system.indexes" % q.db.name}})[0] - self.assertNotIn('$orderby', op['query'], - 'BaseQuerySet cannot use orderby in if stmt') + self.assertNotIn(ORDER_BY_KEY, op['query']) + # Check that normal query uses orderby + qs2 = Person.objects.order_by('name') with query_counter() as p: - for x in qs: + for x in qs2: pass op = p.db.system.profile.find({"ns": {"$ne": "%s.system.indexes" % q.db.name}})[0] - self.assertIn('$orderby', op['query'], 'BaseQuerySet cannot remove orderby in for loop') + self.assertIn(ORDER_BY_KEY, op['query']) def test_bool_with_ordering_from_meta_dict(self): diff --git a/tests/test_context_managers.py b/tests/test_context_managers.py index df5e5212..8fb7bc78 100644 --- a/tests/test_context_managers.py +++ b/tests/test_context_managers.py @@ -299,7 +299,6 @@ class ContextManagersTest(unittest.TestCase): cursor.close() # issues a `killcursors` query that is ignored by the context self.assertEqual(q, 1) - _ = db.system.indexes.find_one() # queries on db.system.indexes are ignored as well self.assertEqual(q, 1) diff --git a/tests/utils.py b/tests/utils.py index acd318c5..39d3681e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -10,6 +10,13 @@ from mongoengine.python_support import IS_PYMONGO_3 MONGO_TEST_DB = 'mongoenginetest' # standard name for the test database +# Constant that can be used to compare the version retrieved with +# get_mongodb_version() +MONGODB_26 = (2, 6) +MONGODB_3 = (3,0) +MONGODB_32 = (3, 2) + + class MongoDBTestCase(unittest.TestCase): """Base class for tests that need a mongodb connection It ensures that the db is clean at the beginning and dropped at the end automatically @@ -27,24 +34,26 @@ class MongoDBTestCase(unittest.TestCase): def get_mongodb_version(): - """Return the version tuple of the MongoDB server that the default - connection is connected to. + """Return the version of the connected mongoDB (first 2 digits) + + :return: tuple(int, int) """ - return tuple(get_connection().server_info()['versionArray']) + version_list = get_connection().server_info()['versionArray'][:2] # e.g: (3, 2) + return tuple(version_list) -def _decorated_with_ver_requirement(func, ver_tuple): +def _decorated_with_ver_requirement(func, version): """Return a given function decorated with the version requirement for a particular MongoDB version tuple. + + :param version: The version required (tuple(int, int)) """ def _inner(*args, **kwargs): - mongodb_ver = get_mongodb_version() - if mongodb_ver >= ver_tuple: + MONGODB_V = get_mongodb_version() + if MONGODB_V >= version: return func(*args, **kwargs) - raise SkipTest('Needs MongoDB v{}+'.format( - '.'.join([str(v) for v in ver_tuple]) - )) + raise SkipTest('Needs MongoDB v{}+'.format('.'.join(str(n) for n in version))) _inner.__name__ = func.__name__ _inner.__doc__ = func.__doc__ @@ -56,14 +65,14 @@ def needs_mongodb_v26(func): """Raise a SkipTest exception if we're working with MongoDB version lower than v2.6. """ - return _decorated_with_ver_requirement(func, (2, 6)) + return _decorated_with_ver_requirement(func, MONGODB_26) def needs_mongodb_v3(func): """Raise a SkipTest exception if we're working with MongoDB version lower than v3.0. """ - return _decorated_with_ver_requirement(func, (3, 0)) + return _decorated_with_ver_requirement(func, MONGODB_3) def skip_pymongo3(f): From 9b6c972e0f177e1ce049034588fb4c43fa3c0fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 2 Oct 2018 22:03:55 +0200 Subject: [PATCH 47/85] renamed decorator needs_mongodb_x in test + fixes ref to mongo 2.4 from travis files --- .install_mongodb_on_travis.sh | 9 ++------- .travis.yml | 8 +++----- mongoengine/queryset/visitor.py | 2 +- tests/document/class_methods.py | 4 ++-- tests/document/indexes.py | 6 +++--- tests/document/instance.py | 6 +++--- tests/queryset/geo.py | 8 ++++---- tests/queryset/modify.py | 4 ++-- tests/queryset/queryset.py | 18 +++++++++--------- tests/utils.py | 4 ++-- 10 files changed, 31 insertions(+), 38 deletions(-) diff --git a/.install_mongodb_on_travis.sh b/.install_mongodb_on_travis.sh index a4ff085b..057ccf74 100644 --- a/.install_mongodb_on_travis.sh +++ b/.install_mongodb_on_travis.sh @@ -3,12 +3,7 @@ sudo apt-get remove mongodb-org-server sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10 -if [ "$MONGODB" = "2.4" ]; then - echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | sudo tee /etc/apt/sources.list.d/mongodb.list - sudo apt-get update - sudo apt-get install mongodb-10gen=2.4.14 - sudo service mongodb start -elif [ "$MONGODB" = "2.6" ]; then +if [ "$MONGODB" = "2.6" ]; then echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | sudo tee /etc/apt/sources.list.d/mongodb.list sudo apt-get update sudo apt-get install mongodb-org-server=2.6.12 @@ -25,7 +20,7 @@ elif [ "$MONGODB" = "3.2" ]; then sudo apt-get install mongodb-org-server=3.2.20 # service should be started automatically else - echo "Invalid MongoDB version, expected 2.4, 2.6, or 3.0." + echo "Invalid MongoDB version, expected 2.6, 3.0, or 3.2" exit 1 fi; diff --git a/.travis.yml b/.travis.yml index f9c7db1e..4f77f4e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,10 @@ # PyMongo combinations. However, that would result in an overly long build # with a very large number of jobs, hence we only test a subset of all the # combinations: -# * MongoDB v2.4 & v3.0 are only tested against Python v2.7 & v3.5. -# * MongoDB v2.4 is tested against PyMongo v2.7 & v3.x. -# * MongoDB v3.0 is tested against PyMongo v3.x. # * MongoDB v2.6 is currently the "main" version tested against Python v2.7, -# v3.5, PyPy & PyPy3, and PyMongo v2.7, v2.8 & v3.x. -# +# v3.5, v3.6, PyPy, and PyMongo v3.x. +# * MongoDB v3.0 & v3.2 are tested against Python v2.7, v3.5 & v3.6 +# and Pymongo v3.5 & v3.x # Reminder: Update README.rst if you change MongoDB versions we test. language: python diff --git a/mongoengine/queryset/visitor.py b/mongoengine/queryset/visitor.py index bcf93a13..dbe3bf2f 100644 --- a/mongoengine/queryset/visitor.py +++ b/mongoengine/queryset/visitor.py @@ -3,7 +3,7 @@ import copy from mongoengine.errors import InvalidQueryError from mongoengine.queryset import transform -__all__ = ('Q',) +__all__ = ('Q', 'QNode') class QNodeVisitor(object): diff --git a/tests/document/class_methods.py b/tests/document/class_methods.py index 5289e483..2632d38f 100644 --- a/tests/document/class_methods.py +++ b/tests/document/class_methods.py @@ -5,7 +5,7 @@ from mongoengine import * from mongoengine.queryset import NULLIFY, PULL from mongoengine.connection import get_db -from tests.utils import needs_mongodb_v26 +from tests.utils import requires_mongodb_gte_26 __all__ = ("ClassMethodsTest", ) @@ -188,7 +188,7 @@ class ClassMethodsTest(unittest.TestCase): self.assertEqual(BlogPostWithTags.compare_indexes(), { 'missing': [], 'extra': [] }) self.assertEqual(BlogPostWithCustomField.compare_indexes(), { 'missing': [], 'extra': [] }) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_compare_indexes_for_text_indexes(self): """ Ensure that compare_indexes behaves correctly for text indexes """ diff --git a/tests/document/indexes.py b/tests/document/indexes.py index b3129fc8..757d8037 100644 --- a/tests/document/indexes.py +++ b/tests/document/indexes.py @@ -8,7 +8,7 @@ import pymongo from mongoengine import * from mongoengine.connection import get_db -from tests.utils import get_mongodb_version, needs_mongodb_v26, MONGODB_32, MONGODB_3 +from tests.utils import get_mongodb_version, requires_mongodb_gte_26, MONGODB_32, MONGODB_3 __all__ = ("IndexesTest", ) @@ -754,7 +754,7 @@ class IndexesTest(unittest.TestCase): except NotUniqueError: pass - def test_unique_and_primary(self): + def test_primary_save_duplicate_update_existing_object(self): """If you set a field as primary, then unexpected behaviour can occur. You won't create a duplicate but you will update an existing document. """ @@ -872,7 +872,7 @@ class IndexesTest(unittest.TestCase): info['provider_ids.foo_1_provider_ids.bar_1']['key']) self.assertTrue(info['provider_ids.foo_1_provider_ids.bar_1']['sparse']) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_text_indexes(self): class Book(Document): title = DictField() diff --git a/tests/document/instance.py b/tests/document/instance.py index e637b3e6..f432fab1 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -25,7 +25,7 @@ from mongoengine.queryset import NULLIFY, Q from mongoengine.context_managers import switch_db, query_counter from mongoengine import signals -from tests.utils import needs_mongodb_v26 +from tests.utils import requires_mongodb_gte_26 TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), '../fields/mongoengine.png') @@ -840,7 +840,7 @@ class InstanceTest(MongoDBTestCase): self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())]) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_modify_with_positional_push(self): class BlogPost(Document): tags = ListField(StringField()) @@ -3272,7 +3272,7 @@ class InstanceTest(MongoDBTestCase): person.update(set__height=2.0) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_push_with_position(self): """Ensure that push with position works properly for an instance.""" class BlogPost(Document): diff --git a/tests/queryset/geo.py b/tests/queryset/geo.py index fea225b2..fd8c9b0f 100644 --- a/tests/queryset/geo.py +++ b/tests/queryset/geo.py @@ -3,7 +3,7 @@ import unittest from mongoengine import * -from tests.utils import MongoDBTestCase, needs_mongodb_v3 +from tests.utils import MongoDBTestCase, requires_mongodb_gte_3 __all__ = ("GeoQueriesTest",) @@ -72,7 +72,7 @@ class GeoQueriesTest(MongoDBTestCase): # $minDistance was added in MongoDB v2.6, but continued being buggy # until v3.0; skip for older versions - @needs_mongodb_v3 + @requires_mongodb_gte_3 def test_near_and_min_distance(self): """Ensure the "min_distance" operator works alongside the "near" operator. @@ -245,7 +245,7 @@ class GeoQueriesTest(MongoDBTestCase): # $minDistance was added in MongoDB v2.6, but continued being buggy # until v3.0; skip for older versions - @needs_mongodb_v3 + @requires_mongodb_gte_3 def test_2dsphere_near_and_min_max_distance(self): """Ensure "min_distace" and "max_distance" operators work well together with the "near" operator in a 2dsphere index. @@ -329,7 +329,7 @@ class GeoQueriesTest(MongoDBTestCase): self._test_embedded(point_field_class=PointField) # Needs MongoDB > 2.6.4 https://jira.mongodb.org/browse/SERVER-14039 - @needs_mongodb_v3 + @requires_mongodb_gte_3 def test_spherical_geospatial_operators(self): """Ensure that spherical geospatial queries are working.""" class Point(Document): diff --git a/tests/queryset/modify.py b/tests/queryset/modify.py index b37f9b73..4b7c3da2 100644 --- a/tests/queryset/modify.py +++ b/tests/queryset/modify.py @@ -2,7 +2,7 @@ import unittest from mongoengine import connect, Document, IntField, StringField, ListField -from tests.utils import needs_mongodb_v26 +from tests.utils import requires_mongodb_gte_26 __all__ = ("FindAndModifyTest",) @@ -96,7 +96,7 @@ class FindAndModifyTest(unittest.TestCase): self.assertEqual(old_doc.to_mongo(), {"_id": 1}) self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}]) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_modify_with_push(self): class BlogPost(Document): tags = ListField(StringField()) diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 1d062853..c5004ed2 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -21,7 +21,7 @@ from mongoengine.python_support import IS_PYMONGO_3 from mongoengine.queryset import (DoesNotExist, MultipleObjectsReturned, QuerySet, QuerySetManager, queryset_manager) -from tests.utils import needs_mongodb_v26, skip_pymongo3, get_mongodb_version, MONGODB_32 +from tests.utils import requires_mongodb_gte_26, skip_pymongo3, get_mongodb_version, MONGODB_32 __all__ = ("QuerySetTest",) @@ -576,7 +576,7 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(post.comments[0].by, 'joe') self.assertEqual(post.comments[0].votes.score, 4) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_update_min_max(self): class Scores(Document): high_score = IntField() @@ -594,7 +594,7 @@ class QuerySetTest(unittest.TestCase): Scores.objects(id=scores.id).update(max__high_score=500) self.assertEqual(Scores.objects.get(id=scores.id).high_score, 1000) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_update_multiple(self): class Product(Document): item = StringField() @@ -1007,7 +1007,7 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(person.name, "User A") self.assertEqual(person.age, 20) - @needs_mongodb_v26 + @requires_mongodb_gte_26 @skip_pymongo3 def test_cursor_args(self): """Ensures the cursor args can be set as expected @@ -1986,7 +1986,7 @@ class QuerySetTest(unittest.TestCase): pymongo_doc = BlogPost.objects.as_pymongo().first() self.assertNotIn('title', pymongo_doc) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_update_push_with_position(self): """Ensure that the 'push' update with position works properly. """ @@ -3278,7 +3278,7 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(Foo.objects.distinct("bar"), [bar]) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_text_indexes(self): class News(Document): title = StringField() @@ -3365,7 +3365,7 @@ class QuerySetTest(unittest.TestCase): 'brasil').order_by('$text_score').first() self.assertEqual(item.get_text_score(), max_text_score) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_distinct_handles_references_to_alias(self): register_connection('testdb', 'mongoenginetest2') @@ -4494,7 +4494,7 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(bars._cursor._Cursor__read_preference, ReadPreference.SECONDARY_PREFERRED) - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_read_preference_aggregation_framework(self): class Bar(Document): txt = StringField() @@ -5188,7 +5188,7 @@ class QuerySetTest(unittest.TestCase): self.assertTrue(Person.objects._has_data(), 'Cursor has data and returned False') - @needs_mongodb_v26 + @requires_mongodb_gte_26 def test_queryset_aggregation_framework(self): class Person(Document): name = StringField() diff --git a/tests/utils.py b/tests/utils.py index 39d3681e..5345f75e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -61,14 +61,14 @@ def _decorated_with_ver_requirement(func, version): return _inner -def needs_mongodb_v26(func): +def requires_mongodb_gte_26(func): """Raise a SkipTest exception if we're working with MongoDB version lower than v2.6. """ return _decorated_with_ver_requirement(func, MONGODB_26) -def needs_mongodb_v3(func): +def requires_mongodb_gte_3(func): """Raise a SkipTest exception if we're working with MongoDB version lower than v3.0. """ From 89416d9856a2233b8f0c309720d9d746f2ac1e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Fri, 5 Oct 2018 14:16:22 +0200 Subject: [PATCH 48/85] Fix deprecation warning about Hashable in py3.7 --- mongoengine/base/document.py | 2 +- mongoengine/python_support.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index c82d670e..ccbfa3d6 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -1,6 +1,5 @@ import copy import numbers -from collections import Hashable from functools import partial from bson import ObjectId, json_util @@ -19,6 +18,7 @@ from mongoengine.base.fields import ComplexBaseField from mongoengine.common import _import_class from mongoengine.errors import (FieldDoesNotExist, InvalidDocumentError, LookUpError, OperationError, ValidationError) +from mongoengine.python_support import Hashable __all__ = ('BaseDocument', 'NON_FIELD_ERRORS') diff --git a/mongoengine/python_support.py b/mongoengine/python_support.py index e884b4ea..7e8e108f 100644 --- a/mongoengine/python_support.py +++ b/mongoengine/python_support.py @@ -19,3 +19,10 @@ if not six.PY3: pass else: StringIO = cStringIO.StringIO + + +if six.PY3: + from collections.abc import Hashable +else: + # raises DeprecationWarnings in Python >=3.7 + from collections import Hashable From adfb039ba636202ce034e6104dc27f5bf74601c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sat, 6 Oct 2018 21:44:25 +0200 Subject: [PATCH 49/85] Improve overall code quality (based on pylint findings) --- docs/upgrade.rst | 5 ++++ mongoengine/base/common.py | 2 +- mongoengine/base/datastructures.py | 19 ++++++--------- mongoengine/base/document.py | 3 --- mongoengine/base/metaclasses.py | 39 ++++++++++++++---------------- mongoengine/base/utils.py | 2 +- mongoengine/document.py | 4 +-- mongoengine/errors.py | 1 + mongoengine/fields.py | 25 +++++++++---------- mongoengine/queryset/base.py | 15 +++++------- mongoengine/queryset/queryset.py | 7 ------ mongoengine/queryset/transform.py | 1 - 12 files changed, 53 insertions(+), 70 deletions(-) diff --git a/docs/upgrade.rst b/docs/upgrade.rst index 65d13359..082dbadc 100644 --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -6,6 +6,11 @@ Development *********** (Fill this out whenever you introduce breaking changes to MongoEngine) +URLField's constructor no longer takes `verify_exists` + +0.15.0 +****** + 0.14.0 ****** This release includes a few bug fixes and a significant code cleanup. The most diff --git a/mongoengine/base/common.py b/mongoengine/base/common.py index dd177920..d747c8cc 100644 --- a/mongoengine/base/common.py +++ b/mongoengine/base/common.py @@ -19,7 +19,7 @@ def get_document(name): # Possible old style name single_end = name.split('.')[-1] compound_end = '.%s' % single_end - possible_match = [k for k in _document_registry.keys() + possible_match = [k for k in _document_registry if k.endswith(compound_end) or k == single_end] if len(possible_match) == 1: doc = _document_registry.get(possible_match.pop(), None) diff --git a/mongoengine/base/datastructures.py b/mongoengine/base/datastructures.py index 0197ad10..17da7a2c 100644 --- a/mongoengine/base/datastructures.py +++ b/mongoengine/base/datastructures.py @@ -1,4 +1,3 @@ -import itertools import weakref from bson import DBRef @@ -18,10 +17,9 @@ class BaseDict(dict): _name = None def __init__(self, dict_items, instance, name): - Document = _import_class('Document') - EmbeddedDocument = _import_class('EmbeddedDocument') + BaseDocument = _import_class('BaseDocument') - if isinstance(instance, (Document, EmbeddedDocument)): + if isinstance(instance, BaseDocument): self._instance = weakref.proxy(instance) self._name = name super(BaseDict, self).__init__(dict_items) @@ -32,11 +30,11 @@ class BaseDict(dict): EmbeddedDocument = _import_class('EmbeddedDocument') if isinstance(value, EmbeddedDocument) and value._instance is None: value._instance = self._instance - elif not isinstance(value, BaseDict) and isinstance(value, dict): + elif isinstance(value, dict) and not isinstance(value, BaseDict): value = BaseDict(value, None, '%s.%s' % (self._name, key)) super(BaseDict, self).__setitem__(key, value) value._instance = self._instance - elif not isinstance(value, BaseList) and isinstance(value, list): + elif isinstance(value, list) and not isinstance(value, BaseList): value = BaseList(value, None, '%s.%s' % (self._name, key)) super(BaseDict, self).__setitem__(key, value) value._instance = self._instance @@ -103,10 +101,9 @@ class BaseList(list): _name = None def __init__(self, list_items, instance, name): - Document = _import_class('Document') - EmbeddedDocument = _import_class('EmbeddedDocument') + BaseDocument = _import_class('BaseDocument') - if isinstance(instance, (Document, EmbeddedDocument)): + if isinstance(instance, BaseDocument): self._instance = weakref.proxy(instance) self._name = name super(BaseList, self).__init__(list_items) @@ -117,11 +114,11 @@ class BaseList(list): EmbeddedDocument = _import_class('EmbeddedDocument') if isinstance(value, EmbeddedDocument) and value._instance is None: value._instance = self._instance - elif not isinstance(value, BaseDict) and isinstance(value, dict): + elif isinstance(value, dict) and not isinstance(value, BaseDict): value = BaseDict(value, None, '%s.%s' % (self._name, key)) super(BaseList, self).__setitem__(key, value) value._instance = self._instance - elif not isinstance(value, BaseList) and isinstance(value, list): + elif isinstance(value, list) and not isinstance(value, BaseList): value = BaseList(value, None, '%s.%s' % (self._name, key)) super(BaseList, self).__setitem__(key, value) value._instance = self._instance diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index c82d670e..b3b6c974 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -91,9 +91,6 @@ class BaseDocument(object): value = getattr(self, key, None) setattr(self, key, value) - if '_cls' not in values: - self._cls = self._class_name - # Set passed values after initialisation if self._dynamic: dynamic_data = {} diff --git a/mongoengine/base/metaclasses.py b/mongoengine/base/metaclasses.py index 481408bf..a3220c6c 100644 --- a/mongoengine/base/metaclasses.py +++ b/mongoengine/base/metaclasses.py @@ -18,14 +18,14 @@ class DocumentMetaclass(type): """Metaclass for all documents.""" # TODO lower complexity of this method - def __new__(cls, name, bases, attrs): - flattened_bases = cls._get_bases(bases) - super_new = super(DocumentMetaclass, cls).__new__ + def __new__(mcs, name, bases, attrs): + flattened_bases = mcs._get_bases(bases) + super_new = super(DocumentMetaclass, mcs).__new__ # If a base class just call super metaclass = attrs.get('my_metaclass') if metaclass and issubclass(metaclass, DocumentMetaclass): - return super_new(cls, name, bases, attrs) + return super_new(mcs, name, bases, attrs) attrs['_is_document'] = attrs.get('_is_document', False) attrs['_cached_reference_fields'] = [] @@ -138,7 +138,7 @@ class DocumentMetaclass(type): attrs['_types'] = attrs['_subclasses'] # TODO depreciate _types # Create the new_class - new_class = super_new(cls, name, bases, attrs) + new_class = super_new(mcs, name, bases, attrs) # Set _subclasses for base in document_bases: @@ -147,7 +147,7 @@ class DocumentMetaclass(type): base._types = base._subclasses # TODO depreciate _types (Document, EmbeddedDocument, DictField, - CachedReferenceField) = cls._import_classes() + CachedReferenceField) = mcs._import_classes() if issubclass(new_class, Document): new_class._collection = None @@ -219,29 +219,26 @@ class DocumentMetaclass(type): return new_class - def add_to_class(self, name, value): - setattr(self, name, value) - @classmethod - def _get_bases(cls, bases): + def _get_bases(mcs, bases): if isinstance(bases, BasesTuple): return bases seen = [] - bases = cls.__get_bases(bases) + bases = mcs.__get_bases(bases) unique_bases = (b for b in bases if not (b in seen or seen.append(b))) return BasesTuple(unique_bases) @classmethod - def __get_bases(cls, bases): + def __get_bases(mcs, bases): for base in bases: if base is object: continue yield base - for child_base in cls.__get_bases(base.__bases__): + for child_base in mcs.__get_bases(base.__bases__): yield child_base @classmethod - def _import_classes(cls): + def _import_classes(mcs): Document = _import_class('Document') EmbeddedDocument = _import_class('EmbeddedDocument') DictField = _import_class('DictField') @@ -254,9 +251,9 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): collection in the database. """ - def __new__(cls, name, bases, attrs): - flattened_bases = cls._get_bases(bases) - super_new = super(TopLevelDocumentMetaclass, cls).__new__ + def __new__(mcs, name, bases, attrs): + flattened_bases = mcs._get_bases(bases) + super_new = super(TopLevelDocumentMetaclass, mcs).__new__ # Set default _meta data if base class, otherwise get user defined meta if attrs.get('my_metaclass') == TopLevelDocumentMetaclass: @@ -319,7 +316,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): not parent_doc_cls._meta.get('abstract', False)): msg = 'Abstract document cannot have non-abstract base' raise ValueError(msg) - return super_new(cls, name, bases, attrs) + return super_new(mcs, name, bases, attrs) # Merge base class metas. # Uses a special MetaDict that handles various merging rules @@ -360,7 +357,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): attrs['_meta'] = meta # Call super and get the new class - new_class = super_new(cls, name, bases, attrs) + new_class = super_new(mcs, name, bases, attrs) meta = new_class._meta @@ -394,7 +391,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): '_auto_id_field', False) if not new_class._meta.get('id_field'): # After 0.10, find not existing names, instead of overwriting - id_name, id_db_name = cls.get_auto_id_names(new_class) + id_name, id_db_name = mcs.get_auto_id_names(new_class) new_class._auto_id_field = True new_class._meta['id_field'] = id_name new_class._fields[id_name] = ObjectIdField(db_field=id_db_name) @@ -419,7 +416,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass): return new_class @classmethod - def get_auto_id_names(cls, new_class): + def get_auto_id_names(mcs, new_class): id_name, id_db_name = ('id', '_id') if id_name not in new_class._fields and \ id_db_name not in (v.db_field for v in new_class._fields.values()): diff --git a/mongoengine/base/utils.py b/mongoengine/base/utils.py index 288c2f3e..8f27ee14 100644 --- a/mongoengine/base/utils.py +++ b/mongoengine/base/utils.py @@ -15,7 +15,7 @@ class LazyRegexCompiler(object): self._compiled_regex = re.compile(self._pattern, self._flags) return self._compiled_regex - def __get__(self, obj, objtype): + def __get__(self, instance, owner): return self.compiled_regex def __set__(self, instance, value): diff --git a/mongoengine/document.py b/mongoengine/document.py index cdeed4c6..0b07cfa2 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -170,8 +170,8 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): """ if self.pk is None: return super(BaseDocument, self).__hash__() - else: - return hash(self.pk) + + return hash(self.pk) @classmethod def _get_db(cls): diff --git a/mongoengine/errors.py b/mongoengine/errors.py index 131596d1..986ebf73 100644 --- a/mongoengine/errors.py +++ b/mongoengine/errors.py @@ -71,6 +71,7 @@ class ValidationError(AssertionError): _message = None def __init__(self, message='', **kwargs): + super(ValidationError, self).__init__(message) self.errors = kwargs.get('errors', {}) self.field_name = kwargs.get('field_name') self.message = message diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 85602215..e4e8ee2c 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -140,8 +140,7 @@ class URLField(StringField): r'(?:/?|[/?]\S+)$', re.IGNORECASE) _URL_SCHEMES = ['http', 'https', 'ftp', 'ftps'] - def __init__(self, verify_exists=False, url_regex=None, schemes=None, **kwargs): - self.verify_exists = verify_exists + def __init__(self, url_regex=None, schemes=None, **kwargs): self.url_regex = url_regex or self._URL_REGEX self.schemes = schemes or self._URL_SCHEMES super(URLField, self).__init__(**kwargs) @@ -274,14 +273,14 @@ class IntField(BaseField): def to_python(self, value): try: value = int(value) - except ValueError: + except (TypeError, ValueError): pass return value def validate(self, value): try: value = int(value) - except Exception: + except (TypeError, ValueError): self.error('%s could not be converted to int' % value) if self.min_value is not None and value < self.min_value: @@ -307,7 +306,7 @@ class LongField(BaseField): def to_python(self, value): try: value = long(value) - except ValueError: + except (TypeError, ValueError): pass return value @@ -317,7 +316,7 @@ class LongField(BaseField): def validate(self, value): try: value = long(value) - except Exception: + except (TypeError, ValueError): self.error('%s could not be converted to long' % value) if self.min_value is not None and value < self.min_value: @@ -416,7 +415,7 @@ class DecimalField(BaseField): # Convert to string for python 2.6 before casting to Decimal try: value = decimal.Decimal('%s' % value) - except decimal.InvalidOperation: + except (TypeError, ValueError, decimal.InvalidOperation): return value return value.quantize(decimal.Decimal('.%s' % ('0' * self.precision)), rounding=self.rounding) @@ -433,7 +432,7 @@ class DecimalField(BaseField): value = six.text_type(value) try: value = decimal.Decimal(value) - except Exception as exc: + except (TypeError, ValueError, decimal.InvalidOperation) as exc: self.error('Could not convert value to decimal: %s' % exc) if self.min_value is not None and value < self.min_value: @@ -852,8 +851,7 @@ class ListField(ComplexBaseField): def validate(self, value): """Make sure that a list of valid fields is being used.""" - if (not isinstance(value, (list, tuple, QuerySet)) or - isinstance(value, six.string_types)): + if not isinstance(value, (list, tuple, QuerySet)): self.error('Only lists and tuples may be used in a list field') super(ListField, self).validate(value) @@ -1953,8 +1951,7 @@ class SequenceField(BaseField): self.collection_name = collection_name or self.COLLECTION_NAME self.db_alias = db_alias or DEFAULT_CONNECTION_NAME self.sequence_name = sequence_name - self.value_decorator = (callable(value_decorator) and - value_decorator or self.VALUE_DECORATOR) + self.value_decorator = value_decorator if callable(value_decorator) else self.VALUE_DECORATOR super(SequenceField, self).__init__(*args, **kwargs) def generate(self): @@ -2063,7 +2060,7 @@ class UUIDField(BaseField): if not isinstance(value, six.string_types): value = six.text_type(value) return uuid.UUID(value) - except Exception: + except (ValueError, TypeError, AttributeError): return original_value return value @@ -2085,7 +2082,7 @@ class UUIDField(BaseField): value = str(value) try: uuid.UUID(value) - except Exception as exc: + except (ValueError, TypeError, AttributeError) as exc: self.error('Could not convert to UUID: %s' % exc) diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index 0be48654..d3a5f050 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -38,8 +38,6 @@ CASCADE = 2 DENY = 3 PULL = 4 -RE_TYPE = type(re.compile('')) - class BaseQuerySet(object): """A set of results returned from a query. Wraps a MongoDB cursor, @@ -356,7 +354,7 @@ class BaseQuerySet(object): try: inserted_result = insert_func(raw) - ids = return_one and [inserted_result.inserted_id] or inserted_result.inserted_ids + ids = [inserted_result.inserted_id] if return_one else inserted_result.inserted_ids except pymongo.errors.DuplicateKeyError as err: message = 'Could not save document (%s)' raise NotUniqueError(message % six.text_type(err)) @@ -377,14 +375,14 @@ class BaseQuerySet(object): if not load_bulk: signals.post_bulk_insert.send( self._document, documents=docs, loaded=False, **signal_kwargs) - return return_one and ids[0] or ids + return ids[0] if return_one else ids documents = self.in_bulk(ids) results = [] for obj_id in ids: results.append(documents.get(obj_id)) signals.post_bulk_insert.send( self._document, documents=results, loaded=True, **signal_kwargs) - return return_one and results[0] or results + return results[0] if return_one else results def count(self, with_limit_and_skip=False): """Count the selected elements in the query. @@ -974,11 +972,10 @@ class BaseQuerySet(object): # explicitly included, and then more complicated operators such as # $slice. def _sort_key(field_tuple): - key, value = field_tuple - if isinstance(value, (int)): + _, value = field_tuple + if isinstance(value, int): return value # 0 for exclusion, 1 for inclusion - else: - return 2 # so that complex values appear last + return 2 # so that complex values appear last fields = sorted(cleaned_fields, key=_sort_key) diff --git a/mongoengine/queryset/queryset.py b/mongoengine/queryset/queryset.py index f9fed7b7..c7c593b1 100644 --- a/mongoengine/queryset/queryset.py +++ b/mongoengine/queryset/queryset.py @@ -186,10 +186,3 @@ class QuerySetNoCache(BaseQuerySet): queryset = self.clone() queryset.rewind() return queryset - - -class QuerySetNoDeRef(QuerySet): - """Special no_dereference QuerySet""" - - def __dereference(items, max_depth=1, instance=None, name=None): - return items diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py index 2effa249..41f99bbc 100644 --- a/mongoengine/queryset/transform.py +++ b/mongoengine/queryset/transform.py @@ -429,7 +429,6 @@ def _infer_geometry(value): 'type and coordinates keys') elif isinstance(value, (list, set)): # TODO: shouldn't we test value[0][0][0][0] to see if it is MultiPolygon? - # TODO: should both TypeError and IndexError be alike interpreted? try: value[0][0][0] From 68c7764c63e3c7c5b854271cbdac29c748c55249 Mon Sep 17 00:00:00 2001 From: Luca Di Gaspero Date: Mon, 8 Oct 2018 17:06:08 +0200 Subject: [PATCH 50/85] Small fix to ensure to use the right document after pre_save_post_validation hook, which might modify the document itself (e.g., for adding a etag). --- mongoengine/document.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mongoengine/document.py b/mongoengine/document.py index cdeed4c6..220047d8 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -368,6 +368,8 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): signals.pre_save_post_validation.send(self.__class__, document=self, created=created, **signal_kwargs) + # it might be refreshed by the pre_save_post_validation hook, e.g., for etag generation + doc = self.to_mongo() if self._meta.get('auto_create_index', True): self.ensure_indexes() From 3c5c3b50261e92fb94e3573e8d301c843a49615b Mon Sep 17 00:00:00 2001 From: Arto Jantunen Date: Wed, 3 Oct 2018 09:56:59 +0300 Subject: [PATCH 51/85] Fix invalid isinstance check in ListField.validate Using QuerySet directly would fail if QuerySetNoCache was used. Sadly the test suite does not have a case where QuerySet would appear here, so adding a test for this special case is not trivial. --- mongoengine/fields.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index e4e8ee2c..04e89256 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -34,7 +34,8 @@ from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db from mongoengine.document import Document, EmbeddedDocument from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError from mongoengine.python_support import StringIO -from mongoengine.queryset import DO_NOTHING, QuerySet +from mongoengine.queryset import DO_NOTHING +from mongoengine.queryset.base import BaseQuerySet try: from PIL import Image, ImageOps @@ -851,7 +852,7 @@ class ListField(ComplexBaseField): def validate(self, value): """Make sure that a list of valid fields is being used.""" - if not isinstance(value, (list, tuple, QuerySet)): + if not isinstance(value, (list, tuple, BaseQuerySet)): self.error('Only lists and tuples may be used in a list field') super(ListField, self).validate(value) From 556f7e85fce9703bb3325af1534f861a01867f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Wed, 10 Oct 2018 23:13:34 +0200 Subject: [PATCH 52/85] Improve the error message that mentions that Document cant be subclassed --- mongoengine/base/metaclasses.py | 3 ++- tests/document/inheritance.py | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/mongoengine/base/metaclasses.py b/mongoengine/base/metaclasses.py index 481408bf..ae191706 100644 --- a/mongoengine/base/metaclasses.py +++ b/mongoengine/base/metaclasses.py @@ -121,7 +121,8 @@ class DocumentMetaclass(type): # inheritance of classes where inheritance is set to False allow_inheritance = base._meta.get('allow_inheritance') if not allow_inheritance and not base._meta.get('abstract'): - raise ValueError('Document %s may not be subclassed' % + raise ValueError('Document %s may not be subclassed. ' + 'To enable inheritance, use the "allow_inheritance" meta attribute.' % base.__name__) # Get superclasses from last base superclass diff --git a/tests/document/inheritance.py b/tests/document/inheritance.py index b2ab1b52..2789ad8e 100644 --- a/tests/document/inheritance.py +++ b/tests/document/inheritance.py @@ -258,9 +258,10 @@ class InheritanceTest(unittest.TestCase): name = StringField() # can't inherit because Animal didn't explicitly allow inheritance - with self.assertRaises(ValueError): + with self.assertRaises(ValueError) as cm: class Dog(Animal): pass + self.assertIn("Document Animal may not be subclassed", str(cm.exception)) # Check that _cls etc aren't present on simple documents dog = Animal(name='dog').save() @@ -277,9 +278,10 @@ class InheritanceTest(unittest.TestCase): name = StringField() meta = {'allow_inheritance': True} - with self.assertRaises(ValueError): + with self.assertRaises(ValueError) as cm: class Mammal(Animal): meta = {'allow_inheritance': False} + self.assertEqual(str(cm.exception), 'Only direct subclasses of Document may set "allow_inheritance" to False') def test_allow_inheritance_abstract_document(self): """Ensure that abstract documents can set inheritance rules and that @@ -292,7 +294,7 @@ class InheritanceTest(unittest.TestCase): class Animal(FinalDocument): name = StringField() - with self.assertRaises(ValueError): + with self.assertRaises(ValueError) as cm: class Mammal(Animal): pass From 59a06a242d5d7681bac6280291ba1c97f5f2b621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Mon, 15 Oct 2018 22:32:11 +0200 Subject: [PATCH 53/85] Fix a bug when using a ReferenceField(AbstractClass) #1920 --- mongoengine/fields.py | 9 ------- tests/document/inheritance.py | 48 +++++++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index 04e89256..284c614d 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -1176,15 +1176,6 @@ class ReferenceField(BaseField): self.error('You can only reference documents once they have been ' 'saved to the database') - if ( - self.document_type._meta.get('abstract') and - not isinstance(value, self.document_type) - ): - self.error( - '%s is not an instance of abstract reference type %s' % ( - value, self.document_type._class_name) - ) - def lookup_member(self, member_name): return self.document_type._fields.get(member_name) diff --git a/tests/document/inheritance.py b/tests/document/inheritance.py index b2ab1b52..735c68da 100644 --- a/tests/document/inheritance.py +++ b/tests/document/inheritance.py @@ -2,14 +2,11 @@ import unittest import warnings -from datetime import datetime - from tests.fixtures import Base -from mongoengine import Document, EmbeddedDocument, connect +from mongoengine import Document, EmbeddedDocument, connect, ReferenceField,\ + BooleanField, GenericReferenceField, IntField, StringField from mongoengine.connection import get_db -from mongoengine.fields import (BooleanField, GenericReferenceField, - IntField, StringField) __all__ = ('InheritanceTest', ) @@ -300,6 +297,41 @@ class InheritanceTest(unittest.TestCase): doc = Animal(name='dog') self.assertNotIn('_cls', doc.to_mongo()) + def test_using_abstract_class_in_reference_field(self): + # Ensures no regression of #1920 + class AbstractHuman(Document): + meta = {'abstract': True} + + class Dad(AbstractHuman): + name = StringField() + + class Home(Document): + dad = ReferenceField(AbstractHuman) # Referencing the abstract class + address = StringField() + + dad = Dad(name='5').save() + Home(dad=dad, address='street').save() + + home = Home.objects.first() + home.address = 'garbage' + home.save() # Was failing with ValidationError + + def test_abstract_class_referencing_self(self): + # Ensures no regression of #1920 + class Human(Document): + meta = {'abstract': True} + creator = ReferenceField('self', dbref=True) + + class User(Human): + name = StringField() + + user = User(name='John').save() + user2 = User(name='Foo', creator=user).save() + + user2 = User.objects.with_id(user2.id) + user2.name = 'Bar' + user2.save() # Was failing with ValidationError + def test_abstract_handle_ids_in_metaclass_properly(self): class City(Document): @@ -358,11 +390,11 @@ class InheritanceTest(unittest.TestCase): meta = {'abstract': True, 'allow_inheritance': False} - bkk = City(continent='asia') - self.assertEqual(None, bkk.pk) + city = City(continent='asia') + self.assertEqual(None, city.pk) # TODO: expected error? Shouldn't we create a new error type? with self.assertRaises(KeyError): - setattr(bkk, 'pk', 1) + setattr(city, 'pk', 1) def test_allow_inheritance_embedded_document(self): """Ensure embedded documents respect inheritance.""" From 0fa6610fdb3ceec259d28598903ce8363074c156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Fri, 5 Oct 2018 14:13:46 +0200 Subject: [PATCH 54/85] Fix complex combinations of DictField(ListField(ReferenceField)) (#1453) --- mongoengine/base/document.py | 4 +-- mongoengine/dereference.py | 50 +++++++++++++++++++++++------------- tests/fields/fields.py | 47 +++++++++++++++++++++++++++++++-- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index c82d670e..fd8fec80 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -3,9 +3,7 @@ import numbers from collections import Hashable from functools import partial -from bson import ObjectId, json_util -from bson.dbref import DBRef -from bson.son import SON +from bson import DBRef, ObjectId, SON, json_util import pymongo import six diff --git a/mongoengine/dereference.py b/mongoengine/dereference.py index 6c993223..dbcbc036 100644 --- a/mongoengine/dereference.py +++ b/mongoengine/dereference.py @@ -52,26 +52,40 @@ class DeReference(object): [i.__class__ == doc_type for i in items.values()]): return items elif not field.dbref: + # We must turn the ObjectIds into DBRefs + + # Recursively dig into the sub items of a list/dict + # to turn the ObjectIds into DBRefs + def _get_items_from_list(items): + new_items = [] + for v in items: + value = v + if isinstance(v, dict): + value = _get_items_from_dict(v) + elif isinstance(v, list): + value = _get_items_from_list(v) + elif not isinstance(v, (DBRef, Document)): + value = field.to_python(v) + new_items.append(value) + return new_items + + def _get_items_from_dict(items): + new_items = {} + for k, v in items.iteritems(): + value = v + if isinstance(v, list): + value = _get_items_from_list(v) + elif isinstance(v, dict): + value = _get_items_from_dict(v) + elif not isinstance(v, (DBRef, Document)): + value = field.to_python(v) + new_items[k] = value + return new_items + if not hasattr(items, 'items'): - - def _get_items(items): - new_items = [] - for v in items: - if isinstance(v, list): - new_items.append(_get_items(v)) - elif not isinstance(v, (DBRef, Document)): - new_items.append(field.to_python(v)) - else: - new_items.append(v) - return new_items - - items = _get_items(items) + items = _get_items_from_list(items) else: - items = { - k: (v if isinstance(v, (DBRef, Document)) - else field.to_python(v)) - for k, v in items.iteritems() - } + items = _get_items_from_dict(items) self.reference_map = self._find_references(items) self.object_map = self._fetch_objects(doc_type=doc_type) diff --git a/tests/fields/fields.py b/tests/fields/fields.py index b375822e..8abe32fc 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -1187,7 +1187,7 @@ class FieldTest(MongoDBTestCase): # aka 'del list[index]' # aka 'operator.delitem(list, index)' reset_post() - del post.info[2] # del from middle ('2') + del post.info[2] # del from middle ('2') self.assertEqual(post.info, ['0', '1', '3', '4', '5']) post.save() post.reload() @@ -1197,7 +1197,7 @@ class FieldTest(MongoDBTestCase): # aka 'del list[i:j]' # aka 'operator.delitem(list, slice(i,j))' reset_post() - del post.info[1:3] # removes '1', '2' + del post.info[1:3] # removes '1', '2' self.assertEqual(post.info, ['0', '3', '4', '5']) post.save() post.reload() @@ -1826,6 +1826,48 @@ class FieldTest(MongoDBTestCase): with self.assertRaises(ValueError): e.update(set__mapping={"somestrings": ["foo", "bar", ]}) + def test_dictfield_with_referencefield_complex_nesting_cases(self): + """Ensure complex nesting inside DictField handles dereferencing of ReferenceField(dbref=True | False)""" + # Relates to Issue #1453 + class Doc(Document): + s = StringField() + + class Simple(Document): + mapping0 = DictField(ReferenceField(Doc, dbref=True)) + mapping1 = DictField(ReferenceField(Doc, dbref=False)) + mapping2 = DictField(ListField(ReferenceField(Doc, dbref=True))) + mapping3 = DictField(ListField(ReferenceField(Doc, dbref=False))) + mapping4 = DictField(DictField(field=ReferenceField(Doc, dbref=True))) + mapping5 = DictField(DictField(field=ReferenceField(Doc, dbref=False))) + mapping6 = DictField(ListField(DictField(ReferenceField(Doc, dbref=True)))) + mapping7 = DictField(ListField(DictField(ReferenceField(Doc, dbref=False)))) + mapping8 = DictField(ListField(DictField(ListField(ReferenceField(Doc, dbref=True))))) + mapping9 = DictField(ListField(DictField(ListField(ReferenceField(Doc, dbref=False))))) + + Doc.drop_collection() + Simple.drop_collection() + + d = Doc(s='aa').save() + e = Simple() + e.mapping0['someint'] = e.mapping1['someint'] = d + e.mapping2['someint'] = e.mapping3['someint'] = [d] + e.mapping4['someint'] = e.mapping5['someint'] = {'d': d} + e.mapping6['someint'] = e.mapping7['someint'] = [{'d': d}] + e.mapping8['someint'] = e.mapping9['someint'] = [{'d': [d]}] + e.save() + + s = Simple.objects.first() + self.assertIsInstance(s.mapping0['someint'], Doc) + self.assertIsInstance(s.mapping1['someint'], Doc) + self.assertIsInstance(s.mapping2['someint'][0], Doc) + self.assertIsInstance(s.mapping3['someint'][0], Doc) + self.assertIsInstance(s.mapping4['someint']['d'], Doc) + self.assertIsInstance(s.mapping5['someint']['d'], Doc) + self.assertIsInstance(s.mapping6['someint'][0]['d'], Doc) + self.assertIsInstance(s.mapping7['someint'][0]['d'], Doc) + self.assertIsInstance(s.mapping8['someint'][0]['d'][0], Doc) + self.assertIsInstance(s.mapping9['someint'][0]['d'][0], Doc) + def test_mapfield(self): """Ensure that the MapField handles the declared type.""" class Simple(Document): @@ -4348,6 +4390,7 @@ class TestEmbeddedDocumentField(MongoDBTestCase): class MyFailingdoc2(Document): emb = EmbeddedDocumentField('MyDoc') + class CachedReferenceFieldTest(MongoDBTestCase): def test_cached_reference_field_get_and_save(self): From 4939a7dd7cee37d90b1d257129c9db7072cbc0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 16 Oct 2018 22:19:41 +0200 Subject: [PATCH 55/85] update input document ids during bulk insert #1919 --- docs/changelog.rst | 2 + mongoengine/queryset/base.py | 9 ++- tests/queryset/queryset.py | 142 +++++++++++++++++++++++------------ 3 files changed, 100 insertions(+), 53 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index edc0fb1a..1757be3e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,12 +5,14 @@ Changelog Development =========== - QuerySet limit function behaviour: Passing 0 as parameter will return all the documents in the cursor #1611 +- bulk insert updates the ids of the input documents #1919 - (Fill this out as you fix issues and develop your features). ======= Changes in 0.15.4 ================= - Added `DateField` #513 + Changes in 0.15.3 ================= - Subfield resolve error in generic_emdedded_document query #1651 #1652 diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index d3a5f050..9ea214cc 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -372,14 +372,17 @@ class BaseQuerySet(object): raise NotUniqueError(message % six.text_type(err)) raise OperationError(message % six.text_type(err)) + # Apply inserted_ids to documents + for doc, doc_id in zip(docs, ids): + doc.pk = doc_id + if not load_bulk: signals.post_bulk_insert.send( self._document, documents=docs, loaded=False, **signal_kwargs) return ids[0] if return_one else ids + documents = self.in_bulk(ids) - results = [] - for obj_id in ids: - results.append(documents.get(obj_id)) + results = [documents.get(obj_id) for obj_id in ids] signals.post_bulk_insert.send( self._document, documents=results, loaded=True, **signal_kwargs) return results[0] if return_one else results diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index c5004ed2..ad405bb1 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -704,38 +704,38 @@ class QuerySetTest(unittest.TestCase): self.assertRaises(ValidationError, Doc.objects().update, ed_f__str_f=1, upsert=True) def test_update_related_models(self): - class TestPerson(Document): - name = StringField() + class TestPerson(Document): + name = StringField() - class TestOrganization(Document): - name = StringField() - owner = ReferenceField(TestPerson) + class TestOrganization(Document): + name = StringField() + owner = ReferenceField(TestPerson) - TestPerson.drop_collection() - TestOrganization.drop_collection() + TestPerson.drop_collection() + TestOrganization.drop_collection() - p = TestPerson(name='p1') - p.save() - o = TestOrganization(name='o1') - o.save() + p = TestPerson(name='p1') + p.save() + o = TestOrganization(name='o1') + o.save() - o.owner = p - p.name = 'p2' + o.owner = p + p.name = 'p2' - self.assertEqual(o._get_changed_fields(), ['owner']) - self.assertEqual(p._get_changed_fields(), ['name']) + self.assertEqual(o._get_changed_fields(), ['owner']) + self.assertEqual(p._get_changed_fields(), ['name']) - o.save() + o.save() - self.assertEqual(o._get_changed_fields(), []) - self.assertEqual(p._get_changed_fields(), ['name']) # Fails; it's empty + self.assertEqual(o._get_changed_fields(), []) + self.assertEqual(p._get_changed_fields(), ['name']) # Fails; it's empty - # This will do NOTHING at all, even though we changed the name - p.save() + # This will do NOTHING at all, even though we changed the name + p.save() - p.reload() + p.reload() - self.assertEqual(p.name, 'p2') # Fails; it's still `p1` + self.assertEqual(p.name, 'p2') # Fails; it's still `p1` def test_upsert(self): self.Person.drop_collection() @@ -839,18 +839,18 @@ class QuerySetTest(unittest.TestCase): comment2 = Comment(name='testb') post1 = Post(comments=[comment1, comment2]) post2 = Post(comments=[comment2, comment2]) - blogs = [Blog(title="post %s" % i, posts=[post1, post2]) - for i in range(99)] # Check bulk insert using load_bulk=False + blogs = [Blog(title="%s" % i, posts=[post1, post2]) + for i in range(99)] with query_counter() as q: self.assertEqual(q, 0) Blog.objects.insert(blogs, load_bulk=False) if MONGO_VER == MONGODB_32: - self.assertEqual(q, 1) # 1 entry containing the list of inserts + self.assertEqual(q, 1) # 1 entry containing the list of inserts else: - self.assertEqual(q, len(blogs)) # 1 entry per doc inserted + self.assertEqual(q, len(blogs)) # 1 entry per doc inserted self.assertEqual(Blog.objects.count(), len(blogs)) @@ -858,14 +858,16 @@ class QuerySetTest(unittest.TestCase): Blog.ensure_indexes() # Check bulk insert using load_bulk=True + blogs = [Blog(title="%s" % i, posts=[post1, post2]) + for i in range(99)] with query_counter() as q: self.assertEqual(q, 0) Blog.objects.insert(blogs) if MONGO_VER == MONGODB_32: - self.assertEqual(q, 2) # 1 for insert 1 for fetch + self.assertEqual(q, 2) # 1 for insert 1 for fetch else: - self.assertEqual(q, len(blogs)+1) # + 1 to fetch all docs + self.assertEqual(q, len(blogs)+1) # + 1 to fetch all docs Blog.drop_collection() @@ -886,26 +888,10 @@ class QuerySetTest(unittest.TestCase): blog = Blog.objects.first() Blog.objects.insert(blog) - # test inserting a query set - with self.assertRaises(OperationError): - blogs = Blog.objects - Blog.objects.insert(blogs) - - # insert a new doc + # insert 1 new doc new_post = Blog(title="code123", id=ObjectId()) Blog.objects.insert(new_post) - class Author(Document): - pass - - # try inserting a different document class - with self.assertRaises(OperationError): - Blog.objects.insert(Author()) - - # try inserting a non-document - with self.assertRaises(OperationError): - Blog.objects.insert("HELLO WORLD") - Blog.drop_collection() blog1 = Blog(title="code", posts=[post1, post2]) @@ -916,20 +902,76 @@ class QuerySetTest(unittest.TestCase): Blog.drop_collection() blog1 = Blog(title="code", posts=[post1, post2]) obj_id = Blog.objects.insert(blog1, load_bulk=False) - self.assertEqual(obj_id.__class__.__name__, 'ObjectId') + self.assertIsInstance(obj_id, ObjectId) Blog.drop_collection() post3 = Post(comments=[comment1, comment1]) blog1 = Blog(title="foo", posts=[post1, post2]) blog2 = Blog(title="bar", posts=[post2, post3]) - blog3 = Blog(title="baz", posts=[post1, post2]) Blog.objects.insert([blog1, blog2]) with self.assertRaises(NotUniqueError): - Blog.objects.insert([blog2, blog3]) + Blog.objects.insert(Blog(title=blog2.title)) self.assertEqual(Blog.objects.count(), 2) + def test_bulk_insert_different_class_fails(self): + class Blog(Document): + pass + + class Author(Document): + pass + + # try inserting a different document class + with self.assertRaises(OperationError): + Blog.objects.insert(Author()) + + def test_bulk_insert_with_wrong_type(self): + class Blog(Document): + name = StringField() + + Blog.drop_collection() + Blog(name='test').save() + + with self.assertRaises(OperationError): + Blog.objects.insert("HELLO WORLD") + + with self.assertRaises(OperationError): + Blog.objects.insert({'name': 'garbage'}) + + # test inserting a query set + with self.assertRaises(OperationError) as cm: + blogs_qs = Blog.objects + Blog.objects.insert(blogs_qs) + self.assertEqual(cm.exception.message, 'Some documents have ObjectIds use doc.update() instead') + + def test_bulk_insert_update_input_document_ids(self): + class Comment(Document): + idx = IntField() + + Comment.drop_collection() + + # Test with bulk + comments = [Comment(idx=idx) for idx in range(20)] + for com in comments: + self.assertIsNone(com.id) + + returned_comments = Comment.objects.insert(comments, load_bulk=True) + + for com in comments: + self.assertIsInstance(com.id, ObjectId) + + input_mapping = {com.id: com.idx for com in comments} + saved_mapping = {com.id: com.idx for com in returned_comments} + self.assertEqual(input_mapping, saved_mapping) + + Comment.drop_collection() + + # Test with just one + comment = Comment(idx=0) + inserted_comment_id = Comment.objects.insert(comment, load_bulk=False) + self.assertEqual(comment.id, inserted_comment_id) + def test_get_changed_fields_query_count(self): """Make sure we don't perform unnecessary db operations when none of document's fields were updated. @@ -2007,7 +2049,7 @@ class QuerySetTest(unittest.TestCase): post.reload() self.assertEqual(post.tags, ['mongodb', 'python', 'java']) - #test push with singular value + # test push with singular value BlogPost.objects.filter(id=post.id).update(push__tags__0='scala') post.reload() self.assertEqual(post.tags, ['scala', 'mongodb', 'python', 'java']) @@ -2286,7 +2328,7 @@ class QuerySetTest(unittest.TestCase): class User(Document): username = StringField() - bar = GenericEmbeddedDocumentField(choices=[Bar,]) + bar = GenericEmbeddedDocumentField(choices=[Bar, ]) User.drop_collection() From a1a93a4bdda7b5ab59072b2484a39f9a00fa4d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 16 Oct 2018 22:35:58 +0200 Subject: [PATCH 56/85] minor additions --- docs/changelog.rst | 1 - tests/queryset/queryset.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 1757be3e..69581a4b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,7 +12,6 @@ Changes in 0.15.4 ================= - Added `DateField` #513 - Changes in 0.15.3 ================= - Subfield resolve error in generic_emdedded_document query #1651 #1652 diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index ad405bb1..0eed3e47 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -2328,7 +2328,7 @@ class QuerySetTest(unittest.TestCase): class User(Document): username = StringField() - bar = GenericEmbeddedDocumentField(choices=[Bar, ]) + bar = GenericEmbeddedDocumentField(choices=[Bar]) User.drop_collection() From 26b70e9ed3a169fab4127404f93597631bdc5aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 16 Oct 2018 22:55:55 +0200 Subject: [PATCH 57/85] fix test --- tests/queryset/queryset.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 0eed3e47..f4bc1dcd 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -884,9 +884,16 @@ class QuerySetTest(unittest.TestCase): self.assertEqual(Blog.objects.count(), 2) # test inserting an existing document (shouldn't be allowed) - with self.assertRaises(OperationError): + with self.assertRaises(OperationError) as cm: blog = Blog.objects.first() Blog.objects.insert(blog) + self.assertEqual(str(cm.exception), 'Some documents have ObjectIds use doc.update() instead') + + # test inserting a query set + with self.assertRaises(OperationError) as cm: + blogs_qs = Blog.objects + Blog.objects.insert(blogs_qs) + self.assertEqual(str(cm.exception), 'Some documents have ObjectIds use doc.update() instead') # insert 1 new doc new_post = Blog(title="code123", id=ObjectId()) @@ -939,12 +946,6 @@ class QuerySetTest(unittest.TestCase): with self.assertRaises(OperationError): Blog.objects.insert({'name': 'garbage'}) - # test inserting a query set - with self.assertRaises(OperationError) as cm: - blogs_qs = Blog.objects - Blog.objects.insert(blogs_qs) - self.assertEqual(cm.exception.message, 'Some documents have ObjectIds use doc.update() instead') - def test_bulk_insert_update_input_document_ids(self): class Comment(Document): idx = IntField() From 598d6bf4c5e75ad78a8562efda1aa7e4f7c1b3ba Mon Sep 17 00:00:00 2001 From: __ROLLER__ Date: Sun, 21 Oct 2018 12:47:23 -0600 Subject: [PATCH 58/85] Update README.rst Added information on using pipenv to the Installation section --- README.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 17cb3e33..4e186a85 100644 --- a/README.rst +++ b/README.rst @@ -36,9 +36,11 @@ Installation We recommend the use of `virtualenv `_ and of `pip `_. You can then use ``pip install -U mongoengine``. You may also have `setuptools `_ -and thus you can use ``easy_install -U mongoengine``. Otherwise, you can download the -source from `GitHub `_ and run ``python -setup.py install``. +and thus you can use ``easy_install -U mongoengine``. Another option is +`pipenv `_. You can then use ``pipenv install mongoengine`` +to both create the virtual environment and install the package. Otherwise, you can +download the source from `GitHub `_ and +run ``python setup.py install``. Dependencies ============ From 83fe7f7eefba10e2ad71fa68b25235d226860811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Mon, 22 Oct 2018 21:54:46 +0200 Subject: [PATCH 59/85] Document the index option and the fact that additional options gets forwarded to pymongo's create_index method --- docs/guide/defining-documents.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index bf74ad8c..be8d9a15 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -492,7 +492,9 @@ the field name with a **#**:: ] } -If a dictionary is passed then the following options are available: +If a dictionary is passed then additional options become available. Valid options include, +but are not limited to: + :attr:`fields` (Default: None) The fields to index. Specified in the same format as described above. @@ -516,8 +518,12 @@ If a dictionary is passed then the following options are available: :attr:`name` (Optional) Allows you to specify a name for the index +:attr:`collation` (Optional) + Allows to create case insensitive indexes (MongoDB v3.4+ only) + .. note:: + Additional options are forwarded as **kwargs to pymongo's create_index method. Inheritance adds extra fields indices see: :ref:`document-inheritance`. Global index default options @@ -736,7 +742,7 @@ document.:: .. note:: From 0.8 onwards :attr:`allow_inheritance` defaults to False, meaning you must set it to True to use inheritance. - + Setting :attr:`allow_inheritance` to True should also be used in :class:`~mongoengine.EmbeddedDocument` class in case you need to subclass it From f23b0faf4189a820827c4cc9efd75503b49a276a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 23 Oct 2018 22:10:34 +0200 Subject: [PATCH 60/85] fix some asserts in no_dereference doc --- docs/guide/querying.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/querying.rst b/docs/guide/querying.rst index f1594dd2..08987835 100644 --- a/docs/guide/querying.rst +++ b/docs/guide/querying.rst @@ -456,14 +456,14 @@ data. To turn off dereferencing of the results of a query use :func:`~mongoengine.queryset.QuerySet.no_dereference` on the queryset like so:: post = Post.objects.no_dereference().first() - assert(isinstance(post.author, ObjectId)) + assert(isinstance(post.author, DBRef)) You can also turn off all dereferencing for a fixed period by using the :class:`~mongoengine.context_managers.no_dereference` context manager:: with no_dereference(Post) as Post: post = Post.objects.first() - assert(isinstance(post.author, ObjectId)) + assert(isinstance(post.author, DBRef)) # Outside the context manager dereferencing occurs. assert(isinstance(post.author, User)) From c685ace32795fad8a320707e178581a90f642887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 23 Oct 2018 23:55:55 +0200 Subject: [PATCH 61/85] minor improvement to index doc --- docs/guide/defining-documents.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index be8d9a15..14f2ff1b 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -544,7 +544,7 @@ There are a few top level defaults for all indexes that can be set:: :attr:`index_opts` (Optional) - Set any default index options - see the `full options list `_ + Set any default index options - see the `full options list `_ :attr:`index_background` (Optional) Set the default value for if an index should be indexed in the background @@ -560,8 +560,7 @@ There are a few top level defaults for all indexes that can be set:: :attr:`index_drop_dups` (Optional) Set the default value for if an index should drop duplicates - -.. note:: Since MongoDB 3.0 drop_dups is not supported anymore. Raises a Warning + Since MongoDB 3.0 drop_dups is not supported anymore. Raises a Warning and has no effect From f6cd349a1698efe85f9501e369d26d8f6ec0b7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 30 Oct 2018 22:52:50 +0100 Subject: [PATCH 62/85] Fix bug when using GenericReferenceField, modifications to the referenced document are tracked in the parent #1934 --- docs/changelog.rst | 2 ++ mongoengine/base/document.py | 3 ++- tests/fields/fields.py | 32 +++++++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 69581a4b..255d3743 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,8 @@ Development =========== - QuerySet limit function behaviour: Passing 0 as parameter will return all the documents in the cursor #1611 - bulk insert updates the ids of the input documents #1919 +- Fix an harmless bug related to GenericReferenceField where modifications in the generic-referenced document + were tracked in the parent (#1934) - (Fill this out as you fix issues and develop your features). ======= Changes in 0.15.4 diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 66217921..aad027e5 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -528,6 +528,7 @@ class BaseDocument(object): EmbeddedDocument = _import_class('EmbeddedDocument') DynamicEmbeddedDocument = _import_class('DynamicEmbeddedDocument') ReferenceField = _import_class('ReferenceField') + GenericReferenceField = _import_class('GenericReferenceField') SortedListField = _import_class('SortedListField') changed_fields = [] @@ -560,7 +561,7 @@ class BaseDocument(object): elif (isinstance(data, (list, tuple, dict)) and db_field_name not in changed_fields): if (hasattr(field, 'field') and - isinstance(field.field, ReferenceField)): + isinstance(field.field, (ReferenceField, GenericReferenceField))): continue elif isinstance(field, SortedListField) and field._ordering: # if ordering is affected whole list is changed diff --git a/tests/fields/fields.py b/tests/fields/fields.py index b375822e..084be71f 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -1187,7 +1187,7 @@ class FieldTest(MongoDBTestCase): # aka 'del list[index]' # aka 'operator.delitem(list, index)' reset_post() - del post.info[2] # del from middle ('2') + del post.info[2] # del from middle ('2') self.assertEqual(post.info, ['0', '1', '3', '4', '5']) post.save() post.reload() @@ -1197,7 +1197,7 @@ class FieldTest(MongoDBTestCase): # aka 'del list[i:j]' # aka 'operator.delitem(list, slice(i,j))' reset_post() - del post.info[1:3] # removes '1', '2' + del post.info[1:3] # removes '1', '2' self.assertEqual(post.info, ['0', '3', '4', '5']) post.save() post.reload() @@ -2816,7 +2816,32 @@ class FieldTest(MongoDBTestCase): doc = Doc.objects.get(ref=DBRef('doc', doc1.pk)) self.assertEqual(doc, doc2) - def test_generic_reference_filter_by_objectid(self): + def test_generic_reference_is_not_tracked_in_parent_doc(self): + """Ensure that modifications of related documents (through generic reference) don't influence + the owner changed fields (#1934) + """ + class Doc1(Document): + name = StringField() + + class Doc2(Document): + ref = GenericReferenceField() + refs = ListField(GenericReferenceField()) + + Doc1.drop_collection() + Doc2.drop_collection() + + doc1 = Doc1(name='garbage1').save() + doc11 = Doc1(name='garbage11').save() + doc2 = Doc2(ref=doc1, refs=[doc11]).save() + + doc2.ref.name = 'garbage2' + self.assertEqual(doc2._get_changed_fields(), []) + + doc2.refs[0].name = 'garbage3' + self.assertEqual(doc2._get_changed_fields(), []) + self.assertEqual(doc2._delta(), ({}, {})) + + def test_generic_reference_field(self): """Ensure we can search for a specific generic reference by providing its DBRef. """ @@ -4348,6 +4373,7 @@ class TestEmbeddedDocumentField(MongoDBTestCase): class MyFailingdoc2(Document): emb = EmbeddedDocumentField('MyDoc') + class CachedReferenceFieldTest(MongoDBTestCase): def test_cached_reference_field_get_and_save(self): From 983474b2bdc33cc5550d8c9e2c45b166c64bd029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 30 Oct 2018 23:40:46 +0100 Subject: [PATCH 63/85] Ignore 2 more flake8 warnings (introduced in latest flake8 3.6.0 release) --- mongoengine/queryset/base.py | 4 ++-- setup.cfg | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index 9ea214cc..6c36d984 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -1872,8 +1872,8 @@ class BaseQuerySet(object): # Substitute the correct name for the field into the javascript return '.'.join([f.db_field for f in fields]) - code = re.sub(u'\[\s*~([A-z_][A-z_0-9.]+?)\s*\]', field_sub, code) - code = re.sub(u'\{\{\s*~([A-z_][A-z_0-9.]+?)\s*\}\}', field_path_sub, + code = re.sub(r'\[\s*~([A-z_][A-z_0-9.]+?)\s*\]', field_sub, code) + code = re.sub(r'\{\{\s*~([A-z_][A-z_0-9.]+?)\s*\}\}', field_path_sub, code) return code diff --git a/setup.cfg b/setup.cfg index fd6192b8..84086601 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ detailed-errors=1 cover-package=mongoengine [flake8] -ignore=E501,F401,F403,F405,I201,I202 +ignore=E501,F401,F403,F405,I201,I202,W504, W605 exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests max-complexity=47 application-import-names=mongoengine,tests From 8e18484898d15ddefd9f74c9ff5aaef2e80e0b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Thu, 1 Nov 2018 22:28:12 +0100 Subject: [PATCH 64/85] Add a comment on a suspicious line (#1322) --- mongoengine/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index d035ff01..dca4e84b 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -991,7 +991,7 @@ class DictField(ComplexBaseField): if op in match_operators and isinstance(value, six.string_types): return StringField().prepare_query_value(op, value) - if hasattr(self.field, 'field'): + if hasattr(self.field, 'field'): # Used for instance when using DictField(ListField(IntField())) if op in ('set', 'unset') and isinstance(value, dict): return { k: self.field.prepare_query_value(op, v) From 424b3ca30806772021e888fe7214d9eb67c095bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sat, 27 Oct 2018 23:01:14 +0200 Subject: [PATCH 65/85] update changelog with changes since 0.15.3 --- docs/changelog.rst | 40 ++++++++++++++++++++++++++++++++------ mongoengine/dereference.py | 2 +- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 255d3743..222c6ea8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,15 +4,43 @@ Changelog Development =========== -- QuerySet limit function behaviour: Passing 0 as parameter will return all the documents in the cursor #1611 -- bulk insert updates the ids of the input documents #1919 -- Fix an harmless bug related to GenericReferenceField where modifications in the generic-referenced document - were tracked in the parent (#1934) - (Fill this out as you fix issues and develop your features). ======= -Changes in 0.15.4 +Changes in 0.16.0 ================= -- Added `DateField` #513 +- Various improvements to the doc +- Improvement to code quality +- POTENTIAL BREAKING CHANGES: + - EmbeddedDocumentField will no longer accept references to Document classes in its constructor #1661 + - Get rid of the `basecls` parameter from the DictField constructor (dead code) #1876 + - default value of ComplexDateTime is now None (and no longer the current datetime) #1368 +- Fix unhashable TypeError when referencing a Document with a compound key in an EmbeddedDocument #1685 +- Fix bug where an EmbeddedDocument with the same id as its parent would not be tracked for changes #1768 +- Fix the fact that bulk `insert()` was not setting primary keys of inserted documents instances #1919 +- Fix bug when referencing the abstract class in a ReferenceField #1920 +- Allow modification to the document made in pre_save_post_validation to be taken into account #1202 +- Replaced MongoDB 2.4 tests in CI by MongoDB 3.2 #1903 +- Fix side effects of using queryset.`no_dereference` on other documents #1677 +- Fix TypeError when using lazy django translation objects as translated choices #1879 +- Improve 2-3 codebase compatibility #1889 +- Fix the support for changing the default value of ComplexDateTime #1368 +- Improves error message in case an EmbeddedDocumentListField receives an EmbeddedDocument instance + instead of a list #1877 +- Fix the Decimal operator inc/dec #1517 #1320 +- Ignore killcursors queries in `query_counter` context manager #1869 +- Fix the fact that `query_counter` was modifying the initial profiling_level in case it was != 0 #1870 +- Repaired the `no_sub_classes` context manager + fix the fact that it was swallowing exceptions #1865 +- Fix index creation error that was swallowed by hasattr under python2 #1688 +- QuerySet limit function behaviour: Passing 0 as parameter will return all the documents in the cursor #1611 +- bulk insert updates the ids of the input documents instances #1919 +- Fix an harmless bug related to GenericReferenceField where modifications in the generic-referenced document + were tracked in the parent #1934 +- Improve validator of BinaryField #273 +- Implemented lazy regex compiling in Field classes to improve 'import mongoengine' performance #1806 +- Updated GridFSProxy.__str__ so that it would always print both the filename and grid_id #710 +- Add __repr__ to Q and QCombination #1843 +- fix bug in BaseList.__iter__ operator (was occuring when modifying a BaseList while iterating over it) #1676 +- Added field `DateField`#513 Changes in 0.15.3 ================= diff --git a/mongoengine/dereference.py b/mongoengine/dereference.py index 6c993223..44266613 100644 --- a/mongoengine/dereference.py +++ b/mongoengine/dereference.py @@ -134,7 +134,7 @@ class DeReference(object): object_map = {} for collection, dbrefs in self.reference_map.iteritems(): - # we use getattr instead of hasattr because as hasattr swallows any exception under python2 + # we use getattr instead of hasattr because hasattr swallows any exception under python2 # so it could hide nasty things without raising exceptions (cfr bug #1688)) ref_document_cls_exists = (getattr(collection, 'objects', None) is not None) From 17fa9a3b7707fa5071cc0a13c21e0cdc60d4c663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sat, 3 Nov 2018 21:48:12 +0100 Subject: [PATCH 66/85] bump version to 0.16.0 --- mongoengine/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongoengine/__init__.py b/mongoengine/__init__.py index e6dc6b9d..6e15d8b9 100644 --- a/mongoengine/__init__.py +++ b/mongoengine/__init__.py @@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) + list(signals.__all__) + list(errors.__all__)) -VERSION = (0, 15, 3) +VERSION = (0, 16, 0) def get_version(): From 23324f0f8767b9fa268f7cf37f5f62d605074f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sun, 4 Nov 2018 22:13:54 +0100 Subject: [PATCH 67/85] minor fix in ImageField docstring --- mongoengine/fields.py | 9 +++------ tests/fields/fields.py | 3 ++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/mongoengine/fields.py b/mongoengine/fields.py index dca4e84b..e9a5e4b2 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -1867,12 +1867,9 @@ class ImageField(FileField): """ A Image File storage field. - @size (width, height, force): - max size to store images, if larger will be automatically resized - ex: size=(800, 600, True) - - @thumbnail (width, height, force): - size to generate a thumbnail + :param size: max size to store images, provided as (width, height, force) + if larger, it will be automatically resized (ex: size=(800, 600, True)) + :param thumbnail_size: size to generate a thumbnail, provided as (width, height, force) .. versionadded:: 0.6 """ diff --git a/tests/fields/fields.py b/tests/fields/fields.py index 7500bbad..ccf67031 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -618,6 +618,8 @@ class FieldTest(MongoDBTestCase): self.assertRaises(ValidationError, person.validate) person.admin = 'Yes' self.assertRaises(ValidationError, person.validate) + person.admin = 'False' + self.assertRaises(ValidationError, person.validate) def test_uuid_field_string(self): """Test UUID fields storing as String @@ -2905,7 +2907,6 @@ class FieldTest(MongoDBTestCase): for invalid_data in (2, u'Im_a_unicode', ['some_str']): self.assertRaises(ValidationError, Attachment(blob=invalid_data).validate) - def test_binary_field_primary(self): class Attachment(Document): id = BinaryField(primary_key=True) From 34e3e45843ef0475824b79b3405285a2d75d7648 Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Thu, 8 Nov 2018 23:47:12 +0800 Subject: [PATCH 68/85] Use insert_one instead of deprecated one #1899 --- mongoengine/document.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/mongoengine/document.py b/mongoengine/document.py index fc379b61..37d60628 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -12,7 +12,11 @@ from mongoengine.base import (BaseDict, BaseDocument, BaseList, TopLevelDocumentMetaclass, get_document) from mongoengine.common import _import_class from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db -from mongoengine.context_managers import switch_collection, switch_db +from mongoengine.context_managers import ( + set_write_concern, + switch_collection, + switch_db +) from mongoengine.errors import (InvalidDocumentError, InvalidQueryError, SaveConditionError) from mongoengine.python_support import IS_PYMONGO_3 @@ -429,11 +433,11 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): Helper method, should only be used inside save(). """ collection = self._get_collection() + with set_write_concern(collection, write_concern) as wc_collection: + if force_insert: + return wc_collection.insert_one(doc).inserted_id - if force_insert: - return collection.insert(doc, **write_concern) - - object_id = collection.save(doc, **write_concern) + object_id = wc_collection.insert_one(doc).inserted_id # In PyMongo 3.0, the save() call calls internally the _update() call # but they forget to return the _id value passed back, therefore getting it back here From 47577f2f47cfc6974baa795ff217f29351f919e8 Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Fri, 9 Nov 2018 01:44:30 +0800 Subject: [PATCH 69/85] Update existing document #1899 --- mongoengine/document.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mongoengine/document.py b/mongoengine/document.py index 37d60628..4abecaa9 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -436,6 +436,13 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): with set_write_concern(collection, write_concern) as wc_collection: if force_insert: return wc_collection.insert_one(doc).inserted_id + # insert_one will provoke UniqueError alongside save does not + # therefore, it need to catch and call replace_one. + if '_id' in doc: + raw_object = wc_collection.find_one_and_replace( + {'_id': doc['_id']}, doc) + if raw_object: + return doc['_id'] object_id = wc_collection.insert_one(doc).inserted_id From 2a8579a6a50b88bbd5507746a93376f7c032b3fe Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Fri, 9 Nov 2018 09:12:29 +0800 Subject: [PATCH 70/85] Update changelog #1899 --- docs/changelog.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 222c6ea8..47af2b1b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,7 +5,9 @@ Changelog Development =========== - (Fill this out as you fix issues and develop your features). -======= +- Remove deprecated `save()` method and used `insert_one()` #1899 + +================= Changes in 0.16.0 ================= - Various improvements to the doc From 611094e92e7994c03ba54e68ded8a9a2d7db9175 Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Fri, 9 Nov 2018 09:20:55 +0800 Subject: [PATCH 71/85] Refactor write_concern #1945 --- mongoengine/document.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mongoengine/document.py b/mongoengine/document.py index fc379b61..f2e3664c 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -297,7 +297,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): return True def save(self, force_insert=False, validate=True, clean=True, - write_concern=None, cascade=None, cascade_kwargs=None, + write_concern={'w': 1}, cascade=None, cascade_kwargs=None, _refs=None, save_condition=None, signal_kwargs=None, **kwargs): """Save the :class:`~mongoengine.Document` to the database. If the document already exists, it will be updated, otherwise it will be @@ -359,9 +359,6 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): if validate: self.validate(clean=clean) - if write_concern is None: - write_concern = {'w': 1} - doc = self.to_mongo() created = ('_id' not in doc or self._created or force_insert) From b47c5b5bfcad03d2b64ef8161f60dc288e28fd72 Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Mon, 12 Nov 2018 09:53:39 +0800 Subject: [PATCH 72/85] Adhere imports into existing one #1899 --- mongoengine/document.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/mongoengine/document.py b/mongoengine/document.py index 4abecaa9..0945a8ed 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -12,11 +12,9 @@ from mongoengine.base import (BaseDict, BaseDocument, BaseList, TopLevelDocumentMetaclass, get_document) from mongoengine.common import _import_class from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db -from mongoengine.context_managers import ( - set_write_concern, - switch_collection, - switch_db -) +from mongoengine.context_managers import (set_write_concern, + switch_collection, + switch_db) from mongoengine.errors import (InvalidDocumentError, InvalidQueryError, SaveConditionError) from mongoengine.python_support import IS_PYMONGO_3 From e31558318e48a5734818360e9632239efdb1a762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Mon, 12 Nov 2018 23:12:35 +0100 Subject: [PATCH 73/85] BugFix - _cls not set in constructor #1950 --- docs/changelog.rst | 1 + mongoengine/base/document.py | 3 +++ tests/document/inheritance.py | 29 +++++++++++++++++++++++++---- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 47af2b1b..1e223aa1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,7 @@ Changelog Development =========== - (Fill this out as you fix issues and develop your features). +- Fix `_cls` that is not set properly in Document constructor (regression) #1950 - Remove deprecated `save()` method and used `insert_one()` #1899 ================= diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 348ab24b..11b3a49a 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -91,6 +91,9 @@ class BaseDocument(object): value = getattr(self, key, None) setattr(self, key, value) + if '_cls' not in values: + self._cls = self._class_name + # Set passed values after initialisation if self._dynamic: dynamic_data = {} diff --git a/tests/document/inheritance.py b/tests/document/inheritance.py index b4ba6058..32e3ed29 100644 --- a/tests/document/inheritance.py +++ b/tests/document/inheritance.py @@ -2,11 +2,11 @@ import unittest import warnings -from tests.fixtures import Base - -from mongoengine import Document, EmbeddedDocument, connect, ReferenceField,\ - BooleanField, GenericReferenceField, IntField, StringField +from mongoengine import (BooleanField, Document, EmbeddedDocument, + EmbeddedDocumentField, GenericReferenceField, + IntField, ReferenceField, StringField, connect) from mongoengine.connection import get_db +from tests.fixtures import Base __all__ = ('InheritanceTest', ) @@ -23,6 +23,27 @@ class InheritanceTest(unittest.TestCase): continue self.db.drop_collection(collection) + def test_constructor_cls(self): + # Ensures _cls is properly set during construction + # and when object gets reloaded (prevent regression of #1950) + class EmbedData(EmbeddedDocument): + data = StringField() + meta = {'allow_inheritance': True} + + class DataDoc(Document): + name = StringField() + embed = EmbeddedDocumentField(EmbedData) + meta = {'allow_inheritance': True} + + test_doc = DataDoc(name='test', embed=EmbedData(data='data')) + assert test_doc._cls == 'DataDoc' + assert test_doc.embed._cls == 'EmbedData' + test_doc.save() + saved_doc = DataDoc.objects.with_id(test_doc.id) + assert test_doc._cls == saved_doc._cls + assert test_doc.embed._cls == saved_doc.embed._cls + test_doc.delete() + def test_superclasses(self): """Ensure that the correct list of superclasses is assembled. """ From c306d42d08cce474fa4f46070404c221255150e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Sun, 11 Nov 2018 22:42:15 +0100 Subject: [PATCH 74/85] Fix bug #1733 of _delta method (Issue with DynamicDocument and default value) + add test case --- docs/changelog.rst | 1 + mongoengine/base/document.py | 34 +++++++++++++++------------------- tests/fields/fields.py | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 19 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 47af2b1b..7fdf5e9c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,7 @@ Changelog Development =========== - (Fill this out as you fix issues and develop your features). +- Fix bug in _delta method - Update of a ListField depends on an unrelated dynamic field update #1733 - Remove deprecated `save()` method and used `insert_one()` #1899 ================= diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 348ab24b..c2f83932 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -579,7 +579,6 @@ class BaseDocument(object): set_fields = self._get_changed_fields() unset_data = {} - parts = [] if hasattr(self, '_changed_fields'): set_data = {} # Fetch each set item from its path @@ -589,15 +588,13 @@ class BaseDocument(object): new_path = [] for p in parts: if isinstance(d, (ObjectId, DBRef)): + # Don't dig in the references break - elif isinstance(d, list) and p.lstrip('-').isdigit(): - if p[0] == '-': - p = str(len(d) + int(p)) - try: - d = d[int(p)] - except IndexError: - d = None + elif isinstance(d, list) and p.isdigit(): + # An item of a list (identified by its index) is updated + d = d[int(p)] elif hasattr(d, 'get'): + # dict-like (dict, embedded document) d = d.get(p) new_path.append(p) path = '.'.join(new_path) @@ -609,26 +606,26 @@ class BaseDocument(object): # Determine if any changed items were actually unset. for path, value in set_data.items(): - if value or isinstance(value, (numbers.Number, bool)): + if value or isinstance(value, (numbers.Number, bool)): # Account for 0 and True that are truthy continue - # If we've set a value that ain't the default value don't unset it. - default = None + parts = path.split('.') + if (self._dynamic and len(parts) and parts[0] in self._dynamic_fields): del set_data[path] unset_data[path] = 1 continue - elif path in self._fields: + + # If we've set a value that ain't the default value don't unset it. + default = None + if path in self._fields: default = self._fields[path].default else: # Perform a full lookup for lists / embedded lookups d = self - parts = path.split('.') db_field_name = parts.pop() for p in parts: - if isinstance(d, list) and p.lstrip('-').isdigit(): - if p[0] == '-': - p = str(len(d) + int(p)) + if isinstance(d, list) and p.isdigit(): d = d[int(p)] elif (hasattr(d, '__getattribute__') and not isinstance(d, dict)): @@ -646,10 +643,9 @@ class BaseDocument(object): default = None if default is not None: - if callable(default): - default = default() + default = default() if callable(default) else default - if default != value: + if value != default: continue del set_data[path] diff --git a/tests/fields/fields.py b/tests/fields/fields.py index ccf67031..a1b586ce 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -186,6 +186,31 @@ class FieldTest(MongoDBTestCase): data_to_be_saved = sorted(person.to_mongo().keys()) self.assertEqual(data_to_be_saved, ['age', 'created', 'userid']) + def test_default_value_is_not_used_when_changing_value_to_empty_list_for_strict_doc(self): + """List field with default can be set to the empty list (strict)""" + # Issue #1733 + class Doc(Document): + x = ListField(IntField(), default=lambda: [42]) + + doc = Doc(x=[1]).save() + doc.x = [] + doc.save() + reloaded = Doc.objects.get(id=doc.id) + self.assertEqual(reloaded.x, []) + + def test_default_value_is_not_used_when_changing_value_to_empty_list_for_dyn_doc(self): + """List field with default can be set to the empty list (dynamic)""" + # Issue #1733 + class Doc(DynamicDocument): + x = ListField(IntField(), default=lambda: [42]) + + doc = Doc(x=[1]).save() + doc.x = [] + doc.y = 2 # Was triggering the bug + doc.save() + reloaded = Doc.objects.get(id=doc.id) + self.assertEqual(reloaded.x, []) + def test_default_values_when_deleting_value(self): """Ensure that default field values are used after non-default values are explicitly deleted. From 8807c0dbef50abafbcbdfa0cc27b5864369b89a3 Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Wed, 14 Nov 2018 17:10:17 +0800 Subject: [PATCH 75/85] bump version 0.16.1 --- docs/changelog.rst | 4 ++++ mongoengine/__init__.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 33578f01..20252039 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,10 @@ Changelog Development =========== - (Fill this out as you fix issues and develop your features). + +================= +Changes in 0.16.1 +================= - Fix `_cls` that is not set properly in Document constructor (regression) #1950 - Fix bug in _delta method - Update of a ListField depends on an unrelated dynamic field update #1733 - Remove deprecated `save()` method and used `insert_one()` #1899 diff --git a/mongoengine/__init__.py b/mongoengine/__init__.py index 6e15d8b9..2a7019a3 100644 --- a/mongoengine/__init__.py +++ b/mongoengine/__init__.py @@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) + list(signals.__all__) + list(errors.__all__)) -VERSION = (0, 16, 0) +VERSION = (0, 16, 1) def get_version(): From fcbabbe35721669d3d954565a79b1787bffa31bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Tue, 20 Nov 2018 23:56:39 +0100 Subject: [PATCH 76/85] Fix but with save(write_concern=None) - introduced in 0.16.1 --- docs/changelog.rst | 5 +++++ mongoengine/document.py | 5 ++++- tests/queryset/queryset.py | 8 ++++++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 20252039..d9742d6f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,11 @@ Development =========== - (Fill this out as you fix issues and develop your features). +================= +Changes in 0.16.2 +================= +- Fix .save() that fails when called with write_concern=None (regression of 0.16.1) #1958 + ================= Changes in 0.16.1 ================= diff --git a/mongoengine/document.py b/mongoengine/document.py index 53814961..0945a8ed 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -299,7 +299,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): return True def save(self, force_insert=False, validate=True, clean=True, - write_concern={'w': 1}, cascade=None, cascade_kwargs=None, + write_concern=None, cascade=None, cascade_kwargs=None, _refs=None, save_condition=None, signal_kwargs=None, **kwargs): """Save the :class:`~mongoengine.Document` to the database. If the document already exists, it will be updated, otherwise it will be @@ -361,6 +361,9 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)): if validate: self.validate(clean=clean) + if write_concern is None: + write_concern = {'w': 1} + doc = self.to_mongo() created = ('_id' not in doc or self._created or force_insert) diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index f4bc1dcd..9e251af7 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -400,13 +400,17 @@ class QuerySetTest(unittest.TestCase): self.Person.drop_collection() write_concern = {"fsync": True} - author = self.Person.objects.create(name='Test User') author.save(write_concern=write_concern) + # Ensure no regression of #1958 + author = self.Person(name='Test User2') + author.save(write_concern=None) # will default to {w: 1} + result = self.Person.objects.update( set__name='Ross', write_concern={"w": 1}) - self.assertEqual(result, 1) + + self.assertEqual(result, 2) result = self.Person.objects.update( set__name='Ross', write_concern={"w": 0}) self.assertEqual(result, None) From 1894003f8a704f516a1f246fd463f78a1bcc0bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastien=20G=C3=A9rard?= Date: Wed, 21 Nov 2018 12:37:42 +0100 Subject: [PATCH 77/85] bump version to 0.16.2 --- mongoengine/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongoengine/__init__.py b/mongoengine/__init__.py index 2a7019a3..4b4dddc9 100644 --- a/mongoengine/__init__.py +++ b/mongoengine/__init__.py @@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) + list(signals.__all__) + list(errors.__all__)) -VERSION = (0, 16, 1) +VERSION = (0, 16, 2) def get_version(): From 352b23331b8486761415a117f9c017efb90569c6 Mon Sep 17 00:00:00 2001 From: Trevor Hall Date: Wed, 5 Dec 2018 20:18:48 -0600 Subject: [PATCH 78/85] Fix bug #1965 of $position and $push operators do not work with list in an EmbeddedDocument. Set key value to joined parts excluding the index at the end. Added test case --- AUTHORS | 1 + mongoengine/queryset/transform.py | 2 +- tests/document/instance.py | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index b38825dc..5998520a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -247,3 +247,4 @@ that much better: * Erdenezul Batmunkh (https://github.com/erdenezul) * Andy Yankovsky (https://github.com/werat) * Bastien Gérard (https://github.com/bagerard) + * Trevor Hall (https://github.com/tjhall13) diff --git a/mongoengine/queryset/transform.py b/mongoengine/queryset/transform.py index 41f99bbc..2d22c350 100644 --- a/mongoengine/queryset/transform.py +++ b/mongoengine/queryset/transform.py @@ -345,7 +345,7 @@ def update(_doc_cls=None, **update): value = {key: {'$each': value}} elif op in ('push', 'pushAll'): if parts[-1].isdigit(): - key = parts[0] + key = '.'.join(parts[0:-1]) position = int(parts[-1]) # $position expects an iterable. If pushing a single value, # wrap it in a list. diff --git a/tests/document/instance.py b/tests/document/instance.py index ad22c086..5319ace4 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -842,10 +842,16 @@ class InstanceTest(MongoDBTestCase): @requires_mongodb_gte_26 def test_modify_with_positional_push(self): + class Content(EmbeddedDocument): + keywords = ListField(StringField()) + class BlogPost(Document): tags = ListField(StringField()) + content = EmbeddedDocumentField(Content) + + post = BlogPost.objects.create( + tags=['python'], content=Content(keywords=['ipsum'])) - post = BlogPost.objects.create(tags=['python']) self.assertEqual(post.tags, ['python']) post.modify(push__tags__0=['code', 'mongo']) self.assertEqual(post.tags, ['code', 'mongo', 'python']) @@ -856,6 +862,16 @@ class InstanceTest(MongoDBTestCase): ['code', 'mongo', 'python'] ) + self.assertEqual(post.content.keywords, ['ipsum']) + post.modify(push__content__keywords__0=['lorem']) + self.assertEqual(post.content.keywords, ['lorem', 'ipsum']) + + # Assert same order of the list items is maintained in the db + self.assertEqual( + BlogPost._get_collection().find_one({'_id': post.pk})['content']['keywords'], + ['lorem', 'ipsum'] + ) + def test_save(self): """Ensure that a document may be saved in the database.""" From d3b4af116eb9bdc0f1ec280b79a9be0a9920b22a Mon Sep 17 00:00:00 2001 From: Trevor Hall Date: Wed, 5 Dec 2018 23:35:36 -0600 Subject: [PATCH 79/85] Add fix to changelog.rst #1965 --- docs/changelog.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index d9742d6f..30b88165 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,11 @@ Development =========== - (Fill this out as you fix issues and develop your features). +================= +Changes in 0.16.3 +================= +- Fix $push with $position operator not working with lists in embedded document #1965 + ================= Changes in 0.16.2 ================= From a03fe234d0cd5b1f4054536b074b14e0a8e7488e Mon Sep 17 00:00:00 2001 From: Erdenezul Batmunkh Date: Thu, 6 Dec 2018 16:22:00 +0800 Subject: [PATCH 80/85] bump version 0.16.3 --- mongoengine/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mongoengine/__init__.py b/mongoengine/__init__.py index 4b4dddc9..2b78d4e6 100644 --- a/mongoengine/__init__.py +++ b/mongoengine/__init__.py @@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) + list(signals.__all__) + list(errors.__all__)) -VERSION = (0, 16, 2) +VERSION = (0, 16, 3) def get_version(): From 48a691e722460a3bfb130a08a3c863c9fd7170ef Mon Sep 17 00:00:00 2001 From: amir-almusawi Date: Fri, 7 Dec 2018 22:08:05 -0600 Subject: [PATCH 81/85] fixed small typographical error --- docs/guide/defining-documents.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/defining-documents.rst b/docs/guide/defining-documents.rst index 14f2ff1b..4449b00b 100644 --- a/docs/guide/defining-documents.rst +++ b/docs/guide/defining-documents.rst @@ -155,7 +155,7 @@ arguments can be set on all fields: An iterable (e.g. list, tuple or set) of choices to which the value of this field should be limited. - Can be either be a nested tuples of value (stored in mongo) and a + Can either be nested tuples of value (stored in mongo) and a human readable key :: SIZE = (('S', 'Small'), From 72ebaa52e9330a063d7d45a72ce65710498ef99f Mon Sep 17 00:00:00 2001 From: Tom Floyer Date: Sat, 8 Dec 2018 22:10:10 +0300 Subject: [PATCH 82/85] Ensure that QuerySet.only() works correctly after QuerySet.count() This test checks if .only() method of QuerySet instance works after using .count() method. --- tests/queryset/queryset.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/queryset/queryset.py b/tests/queryset/queryset.py index 9e251af7..b44c36df 100644 --- a/tests/queryset/queryset.py +++ b/tests/queryset/queryset.py @@ -4720,6 +4720,28 @@ class QuerySetTest(unittest.TestCase): 'password_salt').only('email').to_json() self.assertEqual('[{"email": "ross@example.com"}]', serialized_user) + def test_only_after_count(self): + """Test that only() works after count()""" + + class User(Document): + name = StringField() + age = IntField() + address = StringField() + User.drop_collection() + User(name="User", age=50, + address="Moscow, Russia").save() + + user_queryset = User.objects(age=50) + + result = user_queryset.only("name", "age").as_pymongo().first() + self.assertEqual(result, {"name": "User", "age": 50}) + + result = user_queryset.count() + self.assertEqual(result, 1) + + result = user_queryset.only("name", "age").as_pymongo().first() + self.assertEqual(result, {"name": "User", "age": 50}) + def test_no_dereference(self): class Organization(Document): From 3194a37fcb9654458373ffe0a738038003f7e521 Mon Sep 17 00:00:00 2001 From: Tom Floyer Date: Sat, 8 Dec 2018 22:14:43 +0300 Subject: [PATCH 83/85] Reset cursor object after .count() This change fixes incorrect result of .only() method of QuerySet instance after using .count(). --- mongoengine/queryset/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index 6c36d984..862a1826 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -396,7 +396,9 @@ class BaseQuerySet(object): """ if self._limit == 0 and with_limit_and_skip is False or self._none: return 0 - return self._cursor.count(with_limit_and_skip=with_limit_and_skip) + count = self._cursor.count(with_limit_and_skip=with_limit_and_skip) + self._cursor_obj = None + return count def delete(self, write_concern=None, _from_doc_delete=False, cascade_refs=None): From c88ea40b576157d6e886b1d26ef99ea09502f0c3 Mon Sep 17 00:00:00 2001 From: Tom Floyer Date: Sat, 8 Dec 2018 22:32:46 +0300 Subject: [PATCH 84/85] Updated AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 5998520a..880dfad1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -248,3 +248,4 @@ that much better: * Andy Yankovsky (https://github.com/werat) * Bastien Gérard (https://github.com/bagerard) * Trevor Hall (https://github.com/tjhall13) + * Gleb Voropaev (https://github.com/buggyspace) \ No newline at end of file From 1319e422ea236d914b72c4a90835e6f4e18d80d8 Mon Sep 17 00:00:00 2001 From: Tom Floyer Date: Sat, 8 Dec 2018 22:33:17 +0300 Subject: [PATCH 85/85] Updated docs/changelog.rst --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 30b88165..def5cc34 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,7 @@ Changelog Development =========== - (Fill this out as you fix issues and develop your features). +- Fix .only() working improperly after using .count() of the same instance of QuerySet ================= Changes in 0.16.3