Repository: wtforms/wtforms
Pull request #298 (2016-09-17 15:27:43)

Adds a validator check when creating a field
When doing `*Field(*args, validators=[DummyValidator])`, there is no check
on the callable received (`DummyValidator`). For builtin validators, if
this is not an instance, you get an incomprehensible error from
`_run_validation_chain`. This commit adds a check at field's
initialization time to ensure either a callable instance object or a
function has been given as a validator by raising a meaningful error
message.



Patch:
diff --git a/wtforms/fields/core.py b/wtforms/fields/core.py
index 6ea64e1..733f1c6 100644
--- a/wtforms/fields/core.py
+++ b/wtforms/fields/core.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
 import datetime
 import decimal
 import itertools
+import inspect
 
 from copy import copy
 
@@ -104,6 +105,8 @@ class Field(object):
         self.name = _prefix + _name
         self.short_name = _name
         self.type = type(self).__name__
+
+        self.checkValidators(validators)
         self.validators = validators or list(self.validators)
 
         self.id = id or self.name
@@ -154,6 +157,14 @@ class Field(object):
         """
         return self.meta.render_field(self, kwargs)
 
+    @classmethod
+    def checkValidators(cls, validators):
+        if validators is not None:
+            for validator in list(validators):
+                if inspect.isclass(validator) or not hasattr(validator, '__call__'):
+                    raise TypeError("All validators must be callable instances"
+                                    "(functions/methods or instances with a __call__ method)")
+
     def gettext(self, string):
         """
         Get a translation for the given message.
@@ -190,6 +201,9 @@ class Field(object):
         self.errors = list(self.process_errors)
         stop_validation = False
 
+        # Check the type of extra_validators
+        self.checkValidators(extra_validators)
+
         # Call pre_validate
         try:
             self.pre_validate(form)
@@ -340,6 +354,9 @@ class UnboundField(object):
         self.args = args
         self.kwargs = kwargs
         self.creation_counter = UnboundField.creation_counter
+        validators = kwargs.get('validators')
+        if validators:
+            self.field_class.checkValidators(validators)
 
     def bind(self, form, name, prefix='', translations=None, **kwargs):
         kw = dict(


Test patch:
diff --git a/tests/fields.py b/tests/fields.py
index b54833a..8670957 100644
--- a/tests/fields.py
+++ b/tests/fields.py
@@ -12,7 +12,7 @@ from wtforms.fields import Label, Field, SelectFieldBase, html5
 from wtforms.form import Form
 from wtforms.compat import text_type
 from wtforms.utils import unset_value
-from tests.common import DummyPostData
+from tests.common import DummyPostData, assert_raises_text
 from wtforms.widgets import TextInput
 
 PYTHON_VERSION = sys.version_info
@@ -204,6 +204,11 @@ class FieldTest(TestCase):
         self.assertEqual(f2.items.choices, [('b', 'b')])
         self.assertTrue(f1.items.choices is not f2.items.choices)
 
+    def test_checkValidators(self):
+        with assert_raises_text(TypeError, "All validators must be callable instances"
+                                "\(functions/methods or instances with a __call__ method\)"):
+            Field(validators=[validators.DataRequired])
+
 
 class PrePostTestField(StringField):
     def pre_validate(self, form):