[Twisted-web] Validator -> InputProcessor Patch

Justin Johnson justinjohnson at fastmail.fm
Thu Apr 29 08:53:36 MDT 2004


Attached is a patch for performing the steps outlined at
http://divmod.org/users/wiki.twistd/nevow/moin.cgi/DesignDiscussion in
the Proposal section.

Please send feedback.
Thanks.
-Justin
-------------- next part --------------
Index: nevow/test/test_formless.py
===================================================================
--- nevow/test/test_formless.py	(revision 231)
+++ nevow/test/test_formless.py	(working copy)
@@ -12,102 +12,113 @@
 class Typed(TestCase):
     def testString(self):
         s = formless.String()
-        self.assertEquals(s.coerce(""), None)
+        self.assertEquals(s.coerce(""), "")
         self.assertEquals(s.coerce("Fooo"), "Fooo")
         self.assertEquals(s.coerce("This is a string"), "This is a string")
 
-        s = formless.String(allowNone=False)
-        self.assertRaises(formless.InputError, s.coerce, "")
+        # Requiredness should be tested in freeform, since that
+	# happens in *InputProcessor.process now.
+        #s = formless.String(required=True)
+        #self.assertRaises(formless.InputError, s.coerce, "")
         
-        s = formless.String(allowNone=True)
+        s = formless.String()
         self.assertEquals(s.coerce("Bar"), "Bar")
-        self.assertEquals(s.coerce(""), None)
+        self.assertEquals(s.coerce(""), "")
     
         s = formless.String()
         self.assertEquals(s.coerce(' abc '), ' abc ')
         
-        s = formless.String(strip=True, allowNone=False)
+        s = formless.String(strip=True, required=True)
         self.assertEquals(s.coerce(' abc '), 'abc')
         self.assertEquals(s.coerce('\t abc \t  \n '), 'abc')
-        self.assertRaises(formless.InputError, s.coerce, ' ')
+	# This test should be moved to freeform, and should use
+	# TypedInputProcessor.process instead of String.coerce.
+        #self.assertRaises(formless.InputError, s.coerce, ' ')
         
-        s = formless.String(allowNone=True, strip=True)
+        s = formless.String(strip=True)
         self.assertEquals(s.coerce(' abc '), 'abc')
-        self.assertEquals(s.coerce(' '), None)
+        self.assertEquals(s.coerce(' '), '')
         
     def testText(self):
         s = formless.Text()
-        self.assertEquals(s.coerce(""), None)
+        self.assertEquals(s.coerce(""), "")
         self.assertEquals(s.coerce("Fooo"), "Fooo")
         self.assertEquals(s.coerce("This is a string"), "This is a string")
 
-        s = formless.Text(allowNone=False)
-        self.assertRaises(formless.InputError, s.coerce, "")
+        # Move to freeform and call TypedInputProcessor.process
+        # instead of Text.coerce.
+        #s = formless.Text(required=True)
+        #self.assertRaises(formless.InputError, s.coerce, "")
         
-        s = formless.Text(allowNone=True)
+        s = formless.Text()
         self.assertEquals(s.coerce("Bar"), "Bar")
-        self.assertEquals(s.coerce(""), None)
+        self.assertEquals(s.coerce(""), "")
         
         s = formless.Text()
         self.assertEquals(s.coerce(' abc '), ' abc ')
         
-        s = formless.Text(strip=True, allowNone=False)
+        s = formless.Text(strip=True, required=True)
         self.assertEquals(s.coerce(' abc '), 'abc')
-        self.assertRaises(formless.InputError, s.coerce, ' ')
+        # Move to freeform and call TypedInputProcessor.process
+        # instead of Text.coerce.
+        #self.assertRaises(formless.InputError, s.coerce, ' ')
         
-        s = formless.Text(allowNone=True, strip=True)
+        s = formless.Text(strip=True)
         self.assertEquals(s.coerce(' abc '), 'abc')
-        self.assertEquals(s.coerce(' '), None)
+        self.assertEquals(s.coerce(' '), "")
         
     def testPassword(self):
         s = formless.Password()
         self.assertEquals(s.coerce("Fooo"), "Fooo")
         self.assertEquals(s.coerce("This is a string"), "This is a string")
 
-        s = formless.Password(allowNone=False)
-        self.assertRaises(formless.InputError, s.coerce, "")
+        # Move to freeform and call TypedInputProcessor.process
+        # instead of Text.coerce.
+        #s = formless.Password(required=True)
+        #self.assertRaises(formless.InputError, s.coerce, "")
         
-        s = formless.Password(allowNone=True)
+        s = formless.Password()
         self.assertEquals(s.coerce("Bar"), "Bar")
-        self.assertEquals(s.coerce(""), None)
+        self.assertEquals(s.coerce(""), "")
     
         s = formless.Password()
         self.assertEquals(s.coerce(' abc '), ' abc ')
         
-        s = formless.Password(strip=True, allowNone=False)
+        s = formless.Password(strip=True, required=True)
         self.assertEquals(s.coerce(' abc '), 'abc')
-        self.assertRaises(formless.InputError, s.coerce, ' ')
+        # Move to freeform and call TypedInputProcessor.process
+        # instead of Text.coerce.
+        #self.assertRaises(formless.InputError, s.coerce, ' ')
         
-        s = formless.Password(allowNone=True, strip=True)
+        s = formless.Password(strip=True)
         self.assertEquals(s.coerce(' abc '), 'abc')
-        self.assertEquals(s.coerce(' '), None)
+        self.assertEquals(s.coerce(' '), '')
         
     def testInteger(self):
-        i = formless.Integer(allowNone=False)
+        # Note: these tests don't check requiredness
+        i = formless.Integer(required=True)
         self.assertEquals(i.coerce("0"), 0)
         self.assertEquals(i.coerce("3409823098"), 3409823098)
         self.assertRaises(formless.InputError, i.coerce, "")
         self.assertRaises(formless.InputError, i.coerce, "a string")
         self.assertRaises(formless.InputError, i.coerce, "1.5")
         
-        i = formless.Integer(allowNone=True)
+        i = formless.Integer()
         self.assertEquals(i.coerce("1234567"), 1234567)
-        self.assertEquals(i.coerce(""), None)
         
     def testReal(self):
-        i = formless.Real(allowNone=False)
+        i = formless.Real(required=True)
         self.assertApproximates(i.coerce("0.0"), 0.0, 1e-10)
         self.assertApproximates(i.coerce("34098.23098"), 34098.23098, 1e-10)
         self.assertRaises(formless.InputError, i.coerce, "")
         self.assertRaises(formless.InputError, i.coerce, "a string")
         self.assertRaises(formless.InputError, i.coerce, "1.5j")
 
-        i = formless.Real(allowNone=True)
+        i = formless.Real()
         self.assertApproximates(i.coerce("1234.567"), 1234.567, 1e-10)
-        self.assertEquals(i.coerce(""), None)
 
     def testBoolean(self):
-        b = formless.Boolean(allowNone=False)
+        b = formless.Boolean(required=True)
         self.assertRaises(formless.InputError, b.coerce, "zoom")
         self.assertRaises(formless.InputError, b.coerce, True)
         self.assertRaises(formless.InputError, b.coerce, 54)
@@ -115,14 +126,14 @@
         self.assertEquals(b.coerce("True"), True)
         self.assertEquals(b.coerce("False"), False)
 
-        b = formless.Boolean(allowNone=True)
+        b = formless.Boolean()
         self.assertRaises(formless.InputError, b.coerce, "zoom")
-        self.assertEquals(b.coerce(""), None)
+        self.assertRaises(formless.InputError, b.coerce, "")
         self.assertEquals(b.coerce("True"), True)
         self.assertEquals(b.coerce("False"), False)
         
     def testFixedDigitInteger(self):
-        d = formless.FixedDigitInteger(3, allowNone=False)
+        d = formless.FixedDigitInteger(3, required=True)
         self.assertEquals(d.coerce("123"), 123)
         self.assertEquals(d.coerce("567"), 567)
         self.assertRaises(formless.InputError, d.coerce, "12")
@@ -132,10 +143,10 @@
         self.assertRaises(formless.InputError, d.coerce, "   ")
         self.assertRaises(formless.InputError, d.coerce, "")
 
-        d = formless.FixedDigitInteger(3, allowNone=True)
+        d = formless.FixedDigitInteger(3)
         self.assertEquals(d.coerce("123"), 123)
         self.assertRaises(formless.InputError, d.coerce, "foo")
-        self.assertEquals(d.coerce(""), None)
+        self.assertRaises(formless.InputError, d.coerce, "")
         
 
     def testDirectory(self):
@@ -143,15 +154,15 @@
         os.mkdir(p1)
         p2 = self.mktemp()
         
-        d = formless.Directory(allowNone=False)
+        d = formless.Directory(required=True)
         self.assertEquals(d.coerce(p1), p1)
         self.assertRaises(formless.InputError, d.coerce, p2)
         self.assertRaises(formless.InputError, d.coerce, "")
         
-        d = formless.Directory(allowNone=True)
+        d = formless.Directory()
         self.assertEquals(d.coerce(p1), p1)
         self.assertRaises(formless.InputError, d.coerce, p2)
-        self.assertEquals(d.coerce(""), None)
+        self.assertRaises(formless.InputError, d.coerce, "")
         
     def testTypedInterfaceProperties(self):
         class Test(formless.TypedInterface):
Index: nevow/formless.py
===================================================================
--- nevow/formless.py	(revision 231)
+++ nevow/formless.py	(working copy)
@@ -107,7 +107,7 @@
 class ITyped(Interface):
     """Typeds correspond roughly to <input> tags in HTML, or
     with a complex type, more than one <input> tag whose input
-    is validated and coerced as a unit.
+    is processed and coerced as a unit.
     """
     def coerce(self, val):
         """OPTIONAL.
@@ -174,12 +174,14 @@
 
     complexType = False
 
-    def __init__(self, label='', description='', default='', allowNone=True, **attributes):
+    def __init__(self, label='', description='', default='', required=False,
+                 requiredFailMessage='Please enter a value', **attributes):
         self.id = nextId()
         self.label = label
         self.description = description
         self.default = default
-        self.allowNone=allowNone
+        self.required=required
+        self.requiredFailMessage=requiredFailMessage
         self.attributes = attributes
 
     def getAttribute(self, name, default=None):
@@ -189,32 +191,18 @@
         raise NotImplementedError, "Implement in subclass"
 
 
-class AllowNoneMixin:
-    """Mixin for typed types that helps with checking for allowNone."""
-
-    allowNoneFailMessage = 'Please enter a value.'
-
-    def coerce(self, val):
-        if val == '':
-            if self.allowNone:
-                return None
-            raise InputError(self.allowNoneFailMessage)
-        return val
-
-    
-
 #######################################
 ## External API; your code will create instances of these objects
 #######################################
 
-class String(Typed, AllowNoneMixin):
+class String(Typed):
     """A string that is expected to be reasonably short and contain no
     newlines or tabs.
 
     strip: remove leading and trailing whitespace.
     """
 
-    allowNoneFailMessage = 'Please enter a string.'
+    requiredFailMessage = 'Please enter a string.'
 
     def __init__(self, *args, **kwargs):
         if 'strip' in kwargs:
@@ -227,7 +215,7 @@
     def coerce(self, val):
         if self.strip:
             val = val.strip()
-        return AllowNoneMixin.coerce(self, val)
+        return val
 
 
 class Text(String):
@@ -237,24 +225,22 @@
 
 
 class Password(String):
-    allowNoneFailMessage = 'Please enter a password.'
+    requiredFailMessage = 'Please enter a password.'
 
 
-class FileUpload(Typed, AllowNoneMixin):
+class FileUpload(Typed):
 
-    allowNoneFailMessage = 'Please enter a file name.'
+    requiredFailMessage = 'Please enter a file name.'
 
     def coerce(self, val):
-        AllowNoneMixin.coerce(self, val.filename)
-        return val
+        return val.filename
 
 
-class Integer(Typed, AllowNoneMixin):
+class Integer(Typed):
 
-    allowNoneFailMessage = 'Please enter an integer.'
+    requiredFailMessage = 'Please enter an integer.'
 
     def coerce(self, val):
-        val = AllowNoneMixin.coerce(self, val)
         if val is None:
             return None
         try:
@@ -269,12 +255,11 @@
             raise InputError("%r is not an integer." % val)
 
 
-class Real(Typed, AllowNoneMixin):
+class Real(Typed):
 
-    allowNoneFailMessage = 'Please enter a real number.'
+    requiredFailMessage = 'Please enter a real number.'
 
     def coerce(self, val):
-        val = AllowNoneMixin.coerce(self, val)
         if val is None:
             return None
         try:
@@ -285,9 +270,7 @@
 
 class Boolean(Typed):
     def coerce(self, val):
-        if self.allowNone and val=='':
-            return None
-        elif val == 'False':
+        if val == 'False':
             return False
         elif val == 'True':
             return True
@@ -299,24 +282,21 @@
     def __init__(self, digits = 1, *args, **kw):
         Integer.__init__(self, *args, **kw)
         self.digits = digits
-        self.allowNoneFailMessage = \
+        self.requiredFailMessage = \
             'Please enter a %d digit integer.' % self.digits
 
     def coerce(self, val):
-        if self.allowNone and val=='':
-            return None
         v = Integer.coerce(self, val)
         if len(str(v)) != self.digits:
             raise InputError("Number must be %s digits." % self.digits)
         return v
 
 
-class Directory(Typed, AllowNoneMixin):
+class Directory(Typed):
     
-    allowNoneFailMessage = 'Please enter a directory name.'
+    requiredFailMessage = 'Please enter a directory name.'
 
     def coerce(self, val):
-        val = AllowNoneMixin.coerce(self, val)
         if val is None:
             return None
         if not os.path.exists(val):
@@ -343,8 +323,6 @@
         """Coerce a value with the help of an object, which is the object
         we are configuring.
         """
-        if self.allowNone and val == '':
-            return None
         try:
             val = int(val)
         except ValueError:
@@ -958,8 +936,8 @@
             ctx.remember(binding, IBinding)
             ctx.remember(configurable, IConfigurable)
 
-        bindingValidator = inevow.IInputValidator(binding)
-        rv = bindingValidator.validate(ctx, binding.boundTo, args)
+        bindingInputProcessor = inevow.IInputProcessor(binding)
+        rv = bindingInputProcessor.process(ctx, binding.boundTo, args)
         ctx.remember(rv, inevow.IHand)
         ctx.remember('%r success.' % bindingName, inevow.IStatusMessage)
         return rv
Index: nevow/freeparking.py
===================================================================
--- nevow/freeparking.py	(revision 231)
+++ nevow/freeparking.py	(working copy)
@@ -29,10 +29,10 @@
         return result
 
     
-class GroupBindingValidator(compy.Adapter):
-    __implements__ =inevow.IInputValidator,
+class GroupBindingInputProcessor(compy.Adapter):
+    __implements__ =inevow.IInputProcessor,
 
-    def validate(self, context, boundTo, data):
+    def process(self, context, boundTo, data):
         ## THE SPEC: self.original.typedValue.interface.__spec__
         spec = self.original.typedValue.interface.__spec__
         resultList = [None] * len(spec)
@@ -41,11 +41,11 @@
         failures = {}
         waiters = []
         for i, sub in enumerate(spec):
-            def _validate():
-                # note, _validate only works because it is called IMMEDIATELY
+            def _process():
+                # note, _process only works because it is called IMMEDIATELY
                 # in the loop, watch out for confusing behavior if it is called
                 # later when 'i' has changed
-                resulti = resultList[i] =inevow.IInputValidator(sub).validate(context, boundTo, data, autoConfigure = False)
+                resulti = resultList[i] =inevow.IInputProcessor(sub).process(context, boundTo, data, autoConfigure = False)
                 # Merge the valid value in case another fails
                 results.update(resulti)
             def _except(e):
@@ -60,7 +60,7 @@
                 results.update(pf)
                 # Merge the error message
                 failures.update(e.errors)
-            maybe = exceptblock(_validate, _except, formless.ValidateError)
+            maybe = exceptblock(_process, _except, formless.ValidateError)
             if isinstance(maybe, Deferred):
                 waiters.append(maybe)
         def _finish(ignored):
@@ -73,11 +73,11 @@
         return DeferredList(waiters).addBoth(_finish)
 
 
-class MethodBindingValidator(compy.Adapter):
-    __implements__ =inevow.IInputValidator,
+class MethodBindingInputProcessor(compy.Adapter):
+    __implements__ =inevow.IInputProcessor,
 
-    def validate(self, context, boundTo, data, autoConfigure = True):
-        """Knows how to validate a dictionary of lists
+    def process(self, context, boundTo, data, autoConfigure = True):
+        """Knows how to process a dictionary of lists
         where the dictionary may contain a key with the same
         name as some of the arguments to the MethodBinding
         instance.
@@ -94,7 +94,7 @@
             try:
                 context = context.with(faketag)
                 context.remember(binding, formless.IBinding)
-                results[name] =inevow.IInputValidator(binding.typedValue).validate(context, boundTo, data.get(name, ['']))
+                results[name] =inevow.IInputProcessor(binding.typedValue).process(context, boundTo, data.get(name, ['']))
             except formless.InputError, e:
                 results[name] = data.get(name, [''])[0]
                 failures[name] = e.reason
@@ -112,11 +112,11 @@
         return results
 
 
-class PropertyBindingValidator(compy.Adapter):
-    __implements__ =inevow.IInputValidator,
+class PropertyBindingInputProcessor(compy.Adapter):
+    __implements__ =inevow.IInputProcessor,
 
-    def validate(self, context, boundTo, data, autoConfigure = True):
-        """Knows how to validate a dictionary of lists
+    def process(self, context, boundTo, data, autoConfigure = True):
+        """Knows how to process a dictionary of lists
         where the dictionary may contain a key with the
         same name as the property binding's name.
         """
@@ -124,7 +124,7 @@
         context.remember(binding, formless.IBinding)
         result = {}
         try:
-            result[binding.name] =inevow.IInputValidator(binding.typedValue).validate(context, boundTo, data.get(binding.name, ['']))
+            result[binding.name] =inevow.IInputProcessor(binding.typedValue).process(context, boundTo, data.get(binding.name, ['']))
         except formless.InputError, e:
             result[binding.name] = data.get(binding.name, [''])
             raise formless.ValidateError({binding.name: e.reason}, e.reason, result)
@@ -134,56 +134,74 @@
         return result
 
 
-class TypedValidator(compy.Adapter):
-    __implements__ =inevow.IInputValidator,
+class TypedInputProcessor(compy.Adapter):
+    __implements__ =inevow.IInputProcessor,
 
-    def validate(self, context, boundTo, data):
+    def process(self, context, boundTo, data):
         """data is a list of strings at this point
         """
         typed = self.original
+
+        if data[0] == '':
+            if typed.required:
+                raise formless.InputError(typed.requiredFailMessage)
+            else:
+                return None
         if hasattr(typed, 'coerceWithBinding'):
             return typed.coerceWithBinding(data[0], boundTo)
         return typed.coerce(data[0])
 
 
-class PasswordValidator(compy.Adapter):
-    __implements__ =inevow.IInputValidator,
+class PasswordInputProcessor(compy.Adapter):
+    __implements__ =inevow.IInputProcessor,
 
-    def validate(self, context, boundTo, data):
+    def process(self, context, boundTo, data):
         """Password needs to look at two passwords in the data,
         """
+        typed = self.original
         pw1 = data[0]
         args = context.locate(inevow.IRequest).args
         binding = context.locate(formless.IBinding)
         pw2 = args.get("%s____2" % binding.name, [''])[0]
-        if pw1 != pw2:
+
+        if pw1 == pw2 == '':
+            if typed.required:
+                raise formless.InputError(typed.requiredFailMessage)
+            else:
+                return None
+        elif pw1 != pw2:
             raise formless.InputError("Passwords do not match. Please reenter.")
         return self.original.coerce(data[0])
 
 
-class RequestValidator(compy.Adapter):
-    __implements__ =inevow.IInputValidator,
+class RequestInputProcessor(compy.Adapter):
+    __implements__ =inevow.IInputProcessor,
 
-    def validate(self, context, boundTo, data):
+    def process(self, context, boundTo, data):
         return context.locate(inevow.IRequest)
 
 
-class ContextValidator(compy.Adapter):
-    __implements__ =inevow.IInputValidator,
+class ContextInputProcessor(compy.Adapter):
+    __implements__ =inevow.IInputProcessor,
 
-    def validate(self, context, boundTo, data):
+    def process(self, context, boundTo, data):
         return context
 
 
-class UploadValidator(compy.Adapter):
-    __implements__ =inevow.IInputValidator,
-    def validate(self, context, boundTo, data):
+class UploadInputProcessor(compy.Adapter):
+    __implements__ =inevow.IInputProcessor,
+    def process(self, context, boundTo, data):
         bind = context.locate(formless.IBinding)
         # TOTAL HACK: this comes from outer space
         fields = context.locate(inevow.IRequest).fields
         try:
             field = fields[bind.name]
-            return self.original.coerce(field)
+            # TODO: Is this the appropriate test?  What is field set to?
+            if field == '':
+                if typed.required:
+                    raise formless.InputError(typed.requiredFailMessage)
+                else:
+                    return None
+            return typed.coerce(field)
         except KeyError:
             return ''
-
Index: nevow/__init__.py
===================================================================
--- nevow/__init__.py	(revision 231)
+++ nevow/__init__.py	(working copy)
@@ -116,15 +116,15 @@
 
     #
 
-nevow.freeparking.GroupBindingValidator    nevow.formless.GroupBinding     nevow.inevow.IInputValidator
-nevow.freeparking.MethodBindingValidator   nevow.formless.MethodBinding    nevow.inevow.IInputValidator
-nevow.freeparking.PropertyBindingValidator nevow.formless.Property         nevow.inevow.IInputValidator
-nevow.freeparking.TypedValidator           nevow.formless.ITyped           nevow.inevow.IInputValidator
-nevow.freeparking.PasswordValidator        nevow.formless.Password         nevow.inevow.IInputValidator
-nevow.freeparking.RequestValidator         nevow.formless.Request          nevow.inevow.IInputValidator
-nevow.freeparking.ContextValidator         nevow.formless.Context          nevow.inevow.IInputValidator
-nevow.freeparking.ListValidator            nevow.formless.List             nevow.inevow.IInputValidator
-nevow.freeparking.UploadValidator          nevow.formless.FileUpload       nevow.inevow.IInputValidator
+nevow.freeparking.GroupBindingInputProcessor    nevow.formless.GroupBinding     nevow.inevow.IInputProcessor
+nevow.freeparking.MethodBindingInputProcessor   nevow.formless.MethodBinding    nevow.inevow.IInputProcessor
+nevow.freeparking.PropertyBindingInputProcessor nevow.formless.Property         nevow.inevow.IInputProcessor
+nevow.freeparking.TypedInputProcessor           nevow.formless.ITyped           nevow.inevow.IInputProcessor
+nevow.freeparking.PasswordInputProcessor        nevow.formless.Password         nevow.inevow.IInputProcessor
+nevow.freeparking.RequestInputProcessor         nevow.formless.Request          nevow.inevow.IInputProcessor
+nevow.freeparking.ContextInputProcessor         nevow.formless.Context          nevow.inevow.IInputProcessor
+nevow.freeparking.ListInputProcessor            nevow.formless.List             nevow.inevow.IInputProcessor
+nevow.freeparking.UploadInputProcessor          nevow.formless.FileUpload       nevow.inevow.IInputProcessor
 
     #
 
Index: nevow/inevow.py
===================================================================
--- nevow/inevow.py	(revision 231)
+++ nevow/inevow.py	(working copy)
@@ -7,17 +7,17 @@
 from nevow import compy
 
 
-class IInputValidator(compy.Interface):
+class IInputProcessor(compy.Interface):
     """handle a post for a given binding
     """
-    def validate(self, context, boundTo, data):
+    def process(self, context, boundTo, data):
         """do something to boundTo in response to some data
 
         return a status message if everything's ok, and raise a
         formless.ValidateError if there is a problem
         """
-    ## TODO should probably make a distinction between a Typed validator
-    ## and a Binding validator; probably would make the code cleaner
+    ## TODO should probably make a distinction between a Typed input processor
+    ## and a Binding input processor; probably would make the code cleaner
 
 
 class IConfigurableFactory(compy.Interface):
@@ -26,7 +26,7 @@
     - Implements IConfigurable directly
     - Implements a TypedInterface, thus providing enough information
       about the types of objects needed to allow the user to change
-      the object as long as the input is validated
+      the object as long as the input is processed
     """
     def locateConfigurable(self, context, name):
         """Return the configurable that responds to the name.


More information about the Twisted-web mailing list