From cd2d9517a034d0534a87520cf84daacad5b07fd3 Mon Sep 17 00:00:00 2001 From: Axel Haustant Date: Mon, 13 Apr 2015 17:49:08 +0200 Subject: [PATCH 1/4] Added 'strict' meta parameter --- mongoengine/base/document.py | 2 +- tests/document/instance.py | 114 ++++++++++++++++++++++++++++++++++- tests/fields/fields.py | 2 +- 3 files changed, 115 insertions(+), 3 deletions(-) diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 33a6d826..0ca3fcde 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -72,7 +72,7 @@ class BaseDocument(object): # Check if there are undefined fields supplied, if so raise an # Exception. - if not self._dynamic: + if not self._dynamic and self._meta.get('strict', True): for var in values.keys(): if var not in self._fields.keys() + ['id', 'pk', '_cls', '_text_score']: msg = ( diff --git a/tests/document/instance.py b/tests/document/instance.py index 10e38d49..6a34b749 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -16,7 +16,8 @@ from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest, from mongoengine import * from mongoengine.errors import (NotRegistered, InvalidDocumentError, - InvalidQueryError, NotUniqueError) + InvalidQueryError, NotUniqueError, + FieldDoesNotExist) from mongoengine.queryset import NULLIFY, Q from mongoengine.connection import get_db from mongoengine.base import get_document @@ -2467,6 +2468,117 @@ class InstanceTest(unittest.TestCase): group = Group.objects.first() self.assertEqual("hello - default", group.name) + def test_load_undefined_fields(self): + class User(Document): + name = StringField() + + User.drop_collection() + + User._get_collection().save({ + 'name': 'John', + 'foo': 'Bar', + 'data': [1, 2, 3] + }) + + with self.assertRaises(FieldDoesNotExist): + User.objects.first() + + def test_load_undefined_fields_with_strict_false(self): + class User(Document): + name = StringField() + + meta = {'strict': False} + + User.drop_collection() + + User._get_collection().save({ + 'name': 'John', + 'foo': 'Bar', + 'data': [1, 2, 3] + }) + + user = User.objects.first() + self.assertEqual(user.name, 'John') + self.assertFalse(hasattr(user, 'foo')) + self.assertEqual(user._data['foo'], 'Bar') + self.assertFalse(hasattr(user, 'data')) + self.assertEqual(user._data['data'], [1, 2, 3]) + + def test_load_undefined_fields_on_embedded_document(self): + class Thing(EmbeddedDocument): + name = StringField() + + class User(Document): + name = StringField() + thing = EmbeddedDocumentField(Thing) + + User.drop_collection() + + User._get_collection().save({ + 'name': 'John', + 'thing': { + 'name': 'My thing', + 'foo': 'Bar', + 'data': [1, 2, 3] + } + }) + + with self.assertRaises(FieldDoesNotExist): + User.objects.first() + + def test_load_undefined_fields_on_embedded_document_with_strict_false_on_doc(self): + class Thing(EmbeddedDocument): + name = StringField() + + class User(Document): + name = StringField() + thing = EmbeddedDocumentField(Thing) + + meta = {'strict': False} + + User.drop_collection() + + User._get_collection().save({ + 'name': 'John', + 'thing': { + 'name': 'My thing', + 'foo': 'Bar', + 'data': [1, 2, 3] + } + }) + + with self.assertRaises(FieldDoesNotExist): + User.objects.first() + + def test_load_undefined_fields_on_embedded_document_with_strict_false(self): + class Thing(EmbeddedDocument): + name = StringField() + + meta = {'strict': False} + + class User(Document): + name = StringField() + thing = EmbeddedDocumentField(Thing) + + User.drop_collection() + + User._get_collection().save({ + 'name': 'John', + 'thing': { + 'name': 'My thing', + 'foo': 'Bar', + 'data': [1, 2, 3] + } + }) + + user = User.objects.first() + self.assertEqual(user.name, 'John') + self.assertEqual(user.thing.name, 'My thing') + self.assertFalse(hasattr(user.thing, 'foo')) + self.assertEqual(user.thing._data['foo'], 'Bar') + self.assertFalse(hasattr(user.thing, 'data')) + self.assertEqual(user.thing._data['data'], [1, 2, 3]) + def test_spaces_in_keys(self): class Embedded(DynamicEmbeddedDocument): diff --git a/tests/fields/fields.py b/tests/fields/fields.py index 5e7f34b5..887291db 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -3160,7 +3160,7 @@ class FieldTest(unittest.TestCase): def test_undefined_field_exception(self): """Tests if a `FieldDoesNotExist` exception is raised when trying to - set a value to a field that's not defined. + instanciate a document with a field that's not defined. """ class Doc(Document): From 2bfb195ad671aee104111f3fe2a324530df6b1d3 Mon Sep 17 00:00:00 2001 From: Axel Haustant Date: Mon, 13 Apr 2015 18:07:48 +0200 Subject: [PATCH 2/4] Document FieldDoesNotExist and meta.strict --- docs/apireference.rst | 3 +++ mongoengine/document.py | 5 +++++ mongoengine/errors.py | 9 ++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/apireference.rst b/docs/apireference.rst index 7ba93408..625d4a8b 100644 --- a/docs/apireference.rst +++ b/docs/apireference.rst @@ -34,6 +34,9 @@ Documents .. autoclass:: mongoengine.ValidationError :members: +.. autoclass:: mongoengine.FieldDoesNotExist + + Context Managers ================ diff --git a/mongoengine/document.py b/mongoengine/document.py index eea5dabc..f8275021 100644 --- a/mongoengine/document.py +++ b/mongoengine/document.py @@ -135,6 +135,11 @@ class Document(BaseDocument): doesn't contain a list) if allow_inheritance is True. This can be disabled by either setting cls to False on the specific index or by setting index_cls to False on the meta dictionary for the document. + + By default, any extra attribute existing in stored data but not declared + in your model will raise a :class:`~mongoengine.FieldDoesNotExist` error. + This can be disabled by setting :attr:`strict` to ``False`` + in the :attr:`meta` dictionnary. """ # The __metaclass__ attribute is removed by 2to3 when running with Python3 diff --git a/mongoengine/errors.py b/mongoengine/errors.py index 6cde7771..a411ac47 100644 --- a/mongoengine/errors.py +++ b/mongoengine/errors.py @@ -42,7 +42,14 @@ class NotUniqueError(OperationError): class FieldDoesNotExist(Exception): - pass + """Raised when trying to set a field + not declared in a :class:`~mongoengine.Document` + or an :class:`~mongoengine.EmbeddedDocument`. + + To avoid this behavior on data loading, + you should the :attr:`strict` to ``False`` + in the :attr:`meta` dictionnary. + """ class ValidationError(AssertionError): From ddbcc8e84ba45539a01ab622161e28350268e327 Mon Sep 17 00:00:00 2001 From: Axel Haustant Date: Mon, 13 Apr 2015 18:48:42 +0200 Subject: [PATCH 3/4] Ensure meta.strict does not bypass constructor check --- mongoengine/base/document.py | 6 +++--- tests/fields/fields.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 0ca3fcde..d31d75ba 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -70,9 +70,9 @@ class BaseDocument(object): signals.pre_init.send(self.__class__, document=self, values=values) - # Check if there are undefined fields supplied, if so raise an - # Exception. - if not self._dynamic and self._meta.get('strict', True): + # Check if there are undefined fields supplied to the constructor, + # if so raise an Exception. + if not self._dynamic and (self._meta.get('strict', True) or _created): for var in values.keys(): if var not in self._fields.keys() + ['id', 'pk', '_cls', '_text_score']: msg = ( diff --git a/tests/fields/fields.py b/tests/fields/fields.py index 887291db..f0a73951 100644 --- a/tests/fields/fields.py +++ b/tests/fields/fields.py @@ -3171,6 +3171,21 @@ class FieldTest(unittest.TestCase): self.assertRaises(FieldDoesNotExist, test) + def test_undefined_field_exception_with_strict(self): + """Tests if a `FieldDoesNotExist` exception is raised when trying to + instanciate a document with a field that's not defined, + even when strict is set to False. + """ + + class Doc(Document): + foo = StringField(db_field='f') + meta = {'strict': False} + + def test(): + Doc(bar='test') + + self.assertRaises(FieldDoesNotExist, test) + class EmbeddedDocumentListFieldTestCase(unittest.TestCase): From 52c7b68cc31a5de0736d291b51906a0d639c0efc Mon Sep 17 00:00:00 2001 From: Axel Haustant Date: Mon, 13 Apr 2015 22:28:58 +0200 Subject: [PATCH 4/4] Restore Py26 compatibility on assertRaises --- tests/document/instance.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/document/instance.py b/tests/document/instance.py index 6a34b749..2cfdef65 100644 --- a/tests/document/instance.py +++ b/tests/document/instance.py @@ -2480,8 +2480,7 @@ class InstanceTest(unittest.TestCase): 'data': [1, 2, 3] }) - with self.assertRaises(FieldDoesNotExist): - User.objects.first() + self.assertRaises(FieldDoesNotExist, User.objects.first) def test_load_undefined_fields_with_strict_false(self): class User(Document): @@ -2523,8 +2522,7 @@ class InstanceTest(unittest.TestCase): } }) - with self.assertRaises(FieldDoesNotExist): - User.objects.first() + self.assertRaises(FieldDoesNotExist, User.objects.first) def test_load_undefined_fields_on_embedded_document_with_strict_false_on_doc(self): class Thing(EmbeddedDocument): @@ -2547,8 +2545,7 @@ class InstanceTest(unittest.TestCase): } }) - with self.assertRaises(FieldDoesNotExist): - User.objects.first() + self.assertRaises(FieldDoesNotExist, User.objects.first) def test_load_undefined_fields_on_embedded_document_with_strict_false(self): class Thing(EmbeddedDocument):