Repository: MongoEngine/mongoengine

Issue:
Suggestion: Better exception messages in fields validation
Motivation: TypeError: data must be an instance of str

We receive an exception like this but we did not know which field cause this error. This is hard to debug. During the fields validation, the main class can catch conversion erros to raise another exception with a more expressive message.



Discussion:
What version of MongoEngine are you using?  Validation errors should be explicit (https://github.com/MongoEngine/mongoengine/blob/master/tests/test_document.py#L3372) and have the field information and even a way to convert document errors to a full dict.  

Can you provide a test case to highlight what where its failing?

mongoengine==0.7.9
pymongo==2.4.2

```
class X(Document):
    a = BinaryField()

x = X()
x.a = unicode('unicode string')
x.save()
```

This raises an TypeError: "data must be an instance of str"

It would be greate if it this raise an error: "AssertionError("Field X.a must be an instance of str")

Something like that:

```
    try:
        value = field.to_mongo(value)
    except Exception as e:
        six.reraise(ValidationError('Invalid field value for %s: %s. %s' % (field_name, value, e)))
```


Resolving patch:
diff --git a/mongoengine/fields.py b/mongoengine/fields.py
--- a/mongoengine/fields.py
+++ b/mongoengine/fields.py
@@ -1460,10 +1460,10 @@ def to_mongo(self, value):
         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')


Test patch:
diff --git a/tests/fields/fields.py b/tests/fields/fields.py
--- a/tests/fields/fields.py
+++ b/tests/fields/fields.py
@@ -2967,37 +2967,34 @@ class Attachment(Document):
         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):