[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