morbo.validators

This package provides a simple validation system as well as a number of validators and converters. You’ll probably find it’s usage familiar.

Validators generally perform one task: check that a value meets a certain specification. Sometimes they also perform a conversion. For instance, a time validator might validate that a value is in one of a number of formats and then convert the value to, say, a datetime instance or a POSIX timestamp.

This package tries to supply a number of flexible validators but doesn’t even come close to the kitchen sink. Most applications will have their own validation requirements and the thinking here is to make it as easy as possible to create new validators.

So, creating validators is straightforward. Subclass Validator and implement the validate() method. That’s all that’s required. Here is an example that will only validate yummy things. Optionally, it will convert to yumminess level:

class Yummy(Validator):
    yumminess = {
        'Pizza': 1.5,
        'Pie': 2.75,
        'Steak': 5.58,
        'Sushi': 14.62,
        'Duck Confit': 28.06
    }
    
    def __init__(self, should_convert=False, **kwargs):
        self.should_convert = should_convert
        super(Yummy, self).__init__(**kwargs)
        
        
    def validate(self, value):
        if value not in self.yumminess:
            raise InvalidError('Yumminess not known for "%s"' % value)
        
        if self.should_convert:
            return self.yumminess[value]
        
        return value

It’s a convention, but not a requirement, to put error values in the class like so:

class Yummy(Validator):
    NOT_YUMMY = "This is not yummy"
    
    def validate(self, value):
        if not self.is_yummy(value):
            raise InvalidError(self.NOT_YUMMY)

Then we can do things based on the type of error, if we so desire:

yummy_validator = Yummy()
try:
    yummy_validator.validate('Fried Okra')
    print "Fried Okra is yummy!"
except InvalidError, e:
    if e.message == Yummy.NOT_YUMMY:
        print "Fried Okra is not yummy"
    else:
        print "There is something wront with Fried Okra"
exception morbo.validators.InvalidError

This exception is thrown by all validators to indicate that data is invalid:

v = SomeValidator()
try:
    v.validate("puppies")
except InvalidError, e:
    print "Oh no! Something's wrong! %s" % e.message
exception morbo.validators.InvalidGroupError(errors)

This exception represents a group of invalid errors. It takes a dict where the keys are, presumably, the names of items that were invalid and the values are the errors for their respective items:

def check_stuff_out(stuff):
    validator = SomeValidator()
    errors = {}
    
    for k,v in stuff.items():
        try:
            validator.validate(v)
        except InvalidError, e:
            errors[k] = e
    
    if errors:
        raise InvalidGroupError(errors)
class morbo.validators.Validator(optional=False, default_value=None)

This is the base validator class. Don’t use it unless you are subclassing to create your own validator.

class morbo.validators.GroupValidator(**kwargs)

Validates a a dict of key => validator:

v = GroupValidator(foo=Text(), bar=TypeOf(int))
v.validate({'foo':'oof', 'bar':23}) # ok
v.validate(5) # nope
v.validate('foo':'gobot', 'bar':'pizza') # nope

# unspecified keys are filtered
v.validate({'foo': 'a', 'goo': 'b', 'bar':17})
# ok -> {'foo': 'a', 'bar':17}

# optional validators are, well, optional
v = GroupValidator(foo=Text(), bar=TypeOf(int, optional=True, default_value=8))
v.validate({'foo':'ice cream'}) # -> {'foo':'ice cream', 'bar': 8}
class morbo.validators.Text(minlength=None, maxlength=None, **kwargs)

Passes text, optionally checking length:

v = Text(minlength=2,maxlength=7)
v.validate("foo") # ok
v.validate(23) # oops
v.validate("apron hats") # oops
v.validate("f") # oops
class morbo.validators.Email(optional=False, default_value=None)

Passes email addresses that meet the guidelines in RFC 3696:

v = Email()
v.validate("foo@example.com") # ok
v.validate("foo.bar_^&!baz@example.com") # ok
v.validate("@example.com") # oops!
class morbo.validators.DateTime(default_format='%x %X', use_timelib=True, use_dateutil=True, **kwargs)

Validates many representations of date & time and converts to datetime.datetime. It will use timelib if available, next it will try dateutil.parser. If neither is found, it will use datetime.strptime() with some predefined format string. Int or float timestamps will also be accepted and converted:

# assuming we have timelib
v = DateTime()
v.validate("today") # datetime.datetime(2011, 9, 17, 0, 0, 0)
v.validate("12:06am") # datetime.datetime(2011, 9, 17, 0, 6)
v.validate(datetime.now()) # datetime.datetime(2011, 9, 17, 0, 7)
v.validate(1316232496.342259) # datetime.datetime(2011, 9, 17, 4, 8, 16, 342259)
v.validate("baloon torches") # oops!
class morbo.validators.Bool(optional=False, default_value=None)

Passes and converts most representations of True and False:

b = Bool()
b.validate("true") # True
b.validate(1) # True
b.validate("yes") # True
b.validate(True) # True
b.validate("false") # False, etc.
class morbo.validators.BoundingBox(optional=False, default_value=None)

Passes a geographical bounding box of the form SWNE, e.g., 37.73,-122.48,37.78,-122.37. It will accept a list or a comma-separated string:

v = BoundingBox()
b.validate([42.75804,-85.0031, 42.76409, -84.9861]) # ok
b.validate("42.75804,-85.0031, 42.76409, -84.9861") # -> [42.75804,-85.0031, 42.76409, -84.9861]
class morbo.validators.LatLng(optional=False, default_value=None)

Passes a geographical point in for form of a list, tuple or comma-separated string:

v = LatLng()
v.validate("42.76066, -84.9929") # ok -> (42.76066, -84.9929)
v.validate((42.76066, -84.9929)) # ok
v.validate("234,56756.453") # oops
class morbo.validators.Enum(*values, **kwargs)

Passes anything that evaluates equal to one of a list of values:

v = Enum('a', 'b', 'c')
v.validate('a') # ok
v.validate('d') # nope!
class morbo.validators.TypeOf(*types, **kwargs)

Passes any value of a specified type:

v = TypeOf(float)
v.validate(0.4) # ok
v.validate(1) # nope

# more than one type is ok too
v = TypeOf(int, float, complex)
v.validate(5) # ok
v.validate(5.5) # ok
v.validate(complex(5,5)) # ok
class morbo.validators.URL(**kwargs)

Passes a URL using guidelines from RFC 3696:

v = URL()
v.validate('http://www.example.com') # ok
v.validate('https://www.example.com:8000/foo/bar?smelly_ones=true#dsfg') # ok
v.validate('http://www.example.com/foo;foo') # nope

# You can also set which schemes to match
v = URL(schemes=('gopher',))
v.validate('gopher://example.com/') # ok
v.validate('http://example.com/') # nope!

Regex from http://daringfireball.net/2010/07/improved_regex_for_matching_urls.

class morbo.validators.OneOf(*validators, **kwargs)

Passes values that pass one of a list of validators:

v = OneOf(URL(), Enum('a', 'b'))
v.validate('b') # ok
v.validate('http://www.example.com') # ok
v.validate(23) # nope
class morbo.validators.ListOf(validator, **kwargs)

Passes a list of values that pass a validator:

v = ListOf(TypeOf(int))
v.validate([1,2,3]) # ok
v.validate([1,2,"3"]) # nope
v.validate(1) # nope
class morbo.validators.Anything(optional=False, default_value=None)

Passes anything