<gh_stars>0
'''
valid - helper routines for validating/cleaning data from untrusted inputs

The pattern and reason for this module are quite simple.  Make it easy to
define an expected structure and clean incoming data based on that structure.

To write a validator, you need only be able to write a function that raises
ValueError with a valuable message on data that is not valid.  Then using
expect(), you can reuse that function to properly validate more complex data
structures.  If a value is valid, but needs transformation (ie: '1' -> 1,
convert from string 1 to integer 1) simply return the cleaned value from the
function.  A simple validator looks like this:

    def validator(value, expect):
        ... raise ValueError if there are issues
        ... if you need to recall expect on a new structure, then:
        ...     expect(expected, newvalue)
        return value

Ideally, you can simply use something like this:

    from krankshaft import valid

    try:
        clean = api.expect(
            {
                'count': valid.int,
                'name': valid.string_max_length(100),
                'text': valid.string,
            },
            data
        )
    except api.ValueIssue as exc:
        print str(exc)

The above validates many things:

- that data has only 3 keys in the top-level dictionary
- that the values of each of those keys comply with their given validator

The clean dictionary (because you expected a dictionary) is then clean and ready
to be used by your program safely.

A ValueIssue will be raised with the ValueError message of each offending value
in data. (it does not stop at the first offending value)
'''

from . import util
from .exceptions import ExpectedIssue, KrankshaftError, ValueIssue
from django.core import validators
from django.core.exceptions import ValidationError
from django.core.files import File
from django.db import models
from django.template.defaultfilters import slugify
from django.utils import dateparse

class ExpecterHelper(object):
    '''
    This helper encapsulates the ability to recursively call expect from within
    a validator.

    Meant to be used exactly like the except function but allows access to
    the entire expecter.

        expect = ExpecterHelper(self, opts)
        expect(expected, data)
    '''
    def __init__(self, expecter, opts):
        self.expecter = expecter
        self.opts = opts

    def __call__(self, expected, data):
        return self.expect(expected, data, **self.opts)

    def __getattr__(self, name):
        return getattr(self.expecter, name)

class Expecter(object):
    '''
    The expect() runtime and handling of specific data structures.

    An Expecter simply contains some configuration options along with methods
    to handle specific data structures (dict, list).  This can be expanded or
    changed via subclasses which is about the only reason this exists
    as a class vs a function.

    Some specific details about how this implementation works.  The expect()
    function is the standard entry point for general users.

        from krankshaft import valid
        expecter = valid.Expecter()
        expecter.expect(valid.int, '1') # returns integer 1

    The expect function works recursively to dive through data structures so
    that the simple validators can be used within them and offer a way to
    define the expected data structure exactly.

    For dictionaries:

        expecter.expect({'key': valid.int}, {'key': '1'}) # returns {'key': 1}

    As you can see, the same valid.int validator is being used, but this time
    within a dict data structure.  The expect routine handles anything not
    callable (`hasattr(obj, '__call__')`) as a data structure that needs to
    be handled.  In the dict case, it iterates over the keys shared between
    the expected dict and the given data dict and applies the expected dict
    value (the validator) to the data dict value.  If a failure occurs for
    a specific key, the routine keeps track of the error but continues on to
    the next key.  In this way, all errors will be discovered versus only the
    first error.

    When errors are found, a `expecter.ValueIssue` is raised with all errors
    encountered.

    The expect dictionary logic is by default very strict about what keys exist
    in the given data.  By default, the keys in the given data must match
    exactly those in the expected dictionary.  You can lift those restrictions
    in a few ways.

        expecter = valid.Expecter(strict_dict=False)
        expecter = valid.Expecter(ignore_extra_keys=True)
        expecter = valid.Expecter(ignore_missing_keys=True)

        expecter.expect({...}, {...}, strict_dict=False)
        expecter.expect({...}, {...}, ignore_extra_keys=True)
        expecter.expect({...}, {...}, ignore_missing_keys=True)

    Either you configure the `Expecter` to handle dictionaries that why be
    default, or you override the option at the call site.  The `strict_dict`
    option is a quick way to make both extra/missing keys True or False.  So
    `strict_dict=False` would make `ignore_extra_keys` and `ignore_missing_keys`
    both True.

    Lists get some special non-intuitive (at first) treatment compared to
    dictionaries.  Let's start with the most obvious case:

        expecter.expect([valid.int, valid.int], ['1', 2]) # returns [1, 2]

    Makes sense right?  `expect()` iterates over the list and applies the
    validator to each matching member of the given data list.  And as you'd
    expect, if the given data list had more or less members than the expected
    list, it will blow up.

    Another list case, but a little more confusing:

        expecter.expect([], [1, 'a', None]) # returns [1, 'a', None]

    An empty list accepts all lists, since no validator is given.  An empty
    list is accepted as well.

    The last list case which may be the most confusing at first is this:

        expecter.expect([valid.int], [1, '2']) # returns [1, 2]

    A single validator in a list is special in that that validator is applied
    to all given members of the given data list.  It accepts zero or more
    values in the given data list.  It makes it easy to validate a list of
    homegenus elements which is very typical.

    This makes it a bit tough to validate a list of one or more members.  But
    there is a special validator for that.  The `list_n_or_more` is the
    validator you want:

        expecter.expect(valid.list_n_or_more(valid.int, 1), ['1', 2])

    The above will guarantee at least one member exists and that all members
    are `valid.int`s.

    As for tuple's, they behave just like lists.
    '''
    ExpectedIssue = ExpectedIssue
    ExpecterHelper = ExpecterHelper
    ValueIssue = ValueIssue

    defaults = {
        'ignore_extra_keys': False,
        'ignore_missing_keys': False,
    }

    field_to_validator = {} # setup at end of module

    def __init__(self, **opts):
        self.field_to_validator = self.field_to_validator.copy()
        self.opts = self.options(opts, self.defaults)

    def expect(self, expected, data, **opts):
        '''expect({'key': valid.int}, {'key': '1'}) -> {'key': 1}

        Clean and validate a given data structure using an expected structure.

        You may pass valid options directly here via opts.
        '''
        opts = self.options(opts, self.opts)

        if hasattr(expected, '__call__'):
            try:
                data = expected(data, self.ExpecterHelper(self, opts))

            except ValueError as exc:
                raise self.ValueIssue([self.format_exc(exc)])

            else:
                return data

        if expected.__class__ is not data.__class__:
            raise self.ValueIssue([
                'Unexpected type, expected %s: %s'
                % (expected.__class__, data.__class__)
            ])

        method = getattr(self, 'expect_' + expected.__class__.__name__, None)
        if not method:
            raise self.ExpectedIssue(
                'Your expected data structure is unhandled for type: %s'
                % expected.__class__
            )

        return method(expected, data, opts=opts)

    def expect_dict(self, expected, data, opts):
        '''
        Dictionary data structure handling with expect().

        You probably dont want to use this directly.
        '''
        clean = {}
        data_keys_set = set(data.keys())
        errors = {}
        expected_keys = expected.keys()
        expected_keys_set = set(expected_keys)

        processed = False
        if len(expected_keys) == 1 and hasattr(expected_keys[0], '__call__'):
            processed = True
            for key in data_keys_set:
                # validate key
                try:
                    self.expect(expected_keys[0], key, **opts)
                except self.ValueIssue as exc:
                    errors.setdefault(key, []).extend(exc.errors)

                # validate value
                try:
                    clean[key] = \
                        self.expect(expected_keys[0], data[key], **opts)
                except self.ValueIssue as exc:
                    errors.setdefault(key, []).extend(exc.errors)

        elif (
            not (opts['ignore_extra_keys'] and opts['ignore_missing_keys'])
            and expected_keys_set != data_keys_set
        ):
            extra_keys = data_keys_set - expected_keys_set
            missing_keys = expected_keys_set - data_keys_set
            if not opts['ignore_extra_keys'] and extra_keys:
                errors.setdefault('__nonkeyerrors__', []) \
                    .append('Extra keys: %s' % ', '.join(list(extra_keys)))

            if not opts['ignore_missing_keys'] and missing_keys:
                errors.setdefault('__nonkeyerrors__', []) \
                    .append('Missing keys: %s' % ', '.join(list(missing_keys)))

        if not processed:
            processed = True
            for key in (expected_keys_set & data_keys_set):
                try:
                    clean[key] = self.expect(expected[key], data[key], **opts)

                except self.ValueIssue as exc:
                    errors[key] = exc.errors

        if errors:
            raise self.ValueIssue(errors)

        return clean

    def expect_list(self, expected, data, opts):
        '''
        List data structure handling with expect().

        You probably dont want to use this directly.
        '''
        clean = []
        errors = {}

        if len(expected) == 0:
            return data[:]

        elif len(expected) == 1:
            for i, value in enumerate(data):
                try:
                    clean.append(self.expect(expected[0], value, **opts))
                except self.ValueIssue as exc:
                    errors[i] = exc.errors

        elif len(expected) == len(data):
            for i, (cleaner, d) in enumerate(zip(expected, data)):
                try:
                    clean.append(self.expect(cleaner, d, **opts))
                except self.ValueIssue as exc:
                    errors[i] = exc.errors

        else:
            errors['__nonindexerrors__'] = [
                'Expected list of length %s, saw %s' % (
                    len(expected),
                    len(data),
                )
            ]

        if errors:
            raise self.ValueIssue(errors)

        return clean

    def expect_tuple(self, expected, data, opts):
        '''
        Tuple data structure handling with expect().

        You probably dont want to use this directly.
        '''
        return tuple(self.expect_list(expected, data, opts))

    def format_exc(self, exc):
        '''format_exc(exception) -> '...'

        Format an exception to be encoded into the error data structure.
        '''
        return str(exc)

    def from_field(self, field):
        '''from_field(model.field.field) -> validator or expected data structure

        Return a validator or an expected data structure (passable to expect())
        given a django model field.
        '''
        if field.__class__ in (
            models.ForeignKey,
            models.OneToOneField,
            models.ManyToManyField
        ):
            other_field = field
            while hasattr(other_field, 'rel') and other_field.rel:
                other_field = other_field.rel.get_related_field()

            validator = self.field_to_validator[other_field.__class__]

        else:
            try:
                validator = self.field_to_validator[field.__class__]
            except KeyError:
                raise self.ExpectedIssue(
                    'Unable to resolve "%s" to a validator, register your model'
                    ' field with api.expecter.register(Field, valid.validator)'
                    % str(field)
                )

        # apply django validators early (or late in the validation stack)
        # this way if a django validator cannot handle a blank value but we
        # properly handle it with our blank validator, it wont blow up in
        # django validator incorrectly
        if field.validators:
            validator = django_validator(validator, *field.validators)

        if not field.null:
            validator = no_none(validator)

        if field.primary_key:
            validator = blank_not(validator)

        elif field.blank:
            validator = blank(validator)

        elif not field.choices:
            # choices overrides what values are allowed, if the blank value
            # is a valid choice, let it make that decision
            validator = blank_not(validator)

        if field.choices:
            validator = choice(validator, tuple([
                value
                for value, display in field.choices
            ]))

        if hasattr(field, 'max_length') and field.max_length:
            if isinstance(field, models.FileField):
                validator = max_length(validator, field.max_length,
                    accessor=lambda value: \
                        len(field.generate_filename(value, value.name))
                )

            else:
                validator = max_length(validator, field.max_length)

        if field.__class__ is models.ManyToManyField:
            if field.blank:
                return [validator]

            else:
                return list_n_or_more(validator, 1)

        return validator

    def options(self, opts, defaults):
        '''
        Validate and set default options.
        '''
        return util.valid(
            util.defaults(self.shortcuts(opts), defaults),
            self.defaults.keys()
        )

    def register(self, field, validator):
        '''register(Field, valid.validator)

        Register a validator to be used for a given Field.
        '''
        self.field_to_validator[field] = validator

    def shortcuts(self, opts):
        '''
        Handle option shortcuts like `strict_dict`.
        '''
        if 'strict_dict' in opts:
            strict_dict = opts.pop('strict_dict')
            opts['ignore_extra_keys'] = not strict_dict
            opts['ignore_missing_keys'] = not strict_dict
        return opts

#
# validator helpers
#

def blank(validator):
    '''blank(validator) -> validator_blank

    Short circuit a validator and return the empty value if an empty value is
    given.
    '''
    def blank_validator(value, expect):
        if value == '':
            return value
        return validator(value, expect)
    return wraps(blank_validator, validator, '_blank')

def blank_not(validator):
    '''blank_not(validator) -> validator_blank_not

    Short circuit a validator and raise ValueError if an empty value is given.
    '''
    def blank_not_validator(value, expect):
        if value == '':
            raise ValueError(
                '%s does not accept blank values'
                % blank_not_validator.__name__
            )
        return validator(value, expect)
    return wraps(blank_not_validator, validator, '_blank_not')

def no_none(validator):
    '''or_none(validator) -> validator_no_none

    Given a validator returns ValueError when the given value is invalid,
    wrap it in such a way that it will properly handle being given None and
    raise ValueError.
    '''
    def no_none_validator(value, expect):
        if value is None:
            raise ValueError(
                '%s does not accept None'
                % no_none_validator.__name__
            )
        return validator(value, expect)
    return wraps(no_none_validator, validator, '_no_none')

def or_none(validator):
    '''or_none(validator) -> validator_or_none

    Given a validator returns ValueError when the given value is invalid,
    wrap it in such a way that it will properly handle being given None and
    return None.
    '''
    def or_none_validator(value, expect):
        return None if value is None else validator(value, expect)
    return wraps(or_none_validator, validator, '_or_none')

def primitive(validator):
    '''primitive(int) -> int

    Upgrade a primitive validator to a validator that accepts the expect
    argument.
    '''
    def primitive_validator(value, expect):
        return validator(value)

    return wraps(primitive_validator, validator)

def wraps(wrapper, wrapped, suffix='', prefix=''):
    '''wraps(wrapper, validator)

    Wrap a validator in another validator.
    '''
    wrapper.__name__ = prefix + wrapped.__name__ + suffix

    return wrapper

#
# validator factories
#

def choice(validator, choices):
    '''choice(valid.string, ['a', 'b', 'c']) -> string_choices

    Wrap validator that also validates the returned value is in the list of
    valid choices.
    '''
    def choice_validator(value, expect):
        value = validator(value, expect)
        if value is not None and value not in choices:
            raise ValueError('The value is not a valid choice: %s' % value)
        return value

    return wraps(choice_validator, validator, '_choices')

def csv(validator, separator=','):
    '''csv(valid.int) -> int_csv

    Wrap validator so that it validates a list separated by commas.
    '''
    def csv_validator(data, expect):
        data = string_or_none(data, expect)

        if data is not None:
            clean = expect(
                [validator],
                [member for member in data.split(',') if member],
            )
            return ','.join([
                str(member)
                for member in clean
            ])

        return data

    return wraps(csv_validator, validator, '_csv')

def django_validator(validator, *validators):
    '''django_validator(valid.string, *field.validators) -> django_validator

    Use django validators as extra validation to a value.
    '''
    def django_validator_validator(value, expect):
        value = validator(value, expect)

        if value is not None:
            for django_is_valid in validators:
                try:
                    django_is_valid(value)
                except ValidationError as exc:
                    raise ValueError(exc.messages[0])

        return value

    return wraps(django_validator_validator, validator, '_django_validator')

def list_n_or_more(validator, n):
    '''list_n_or_more(valid.int, 1) -> list_1_or_more_int

    Wrap a validator that also validates the returned list has one or more
    members.
    '''
    if n < 0:
        raise KrankshaftError(
            'list_n_or_more only accepts values >= 0, not %s' % n
        )

    def list_n_or_more_validator(data, expect):
        clean = None
        errors = {}
        try:
            clean = expect([validator], data)
        except expect.ValueIssue as exc:
            errors = exc.errors

        if clean is not None and len(clean) < n:
            errors.setdefault('__nonindexerrors__', []) \
                .append(
                    'Expected list with %s or more elements, saw %s'
                    % (n, len(data))
                )

        if errors:
            raise expect.ValueIssue(errors)

        return clean

    return wraps(list_n_or_more_validator, validator,
        prefix='list_%s_or_more_' % n
    )

def max_length(validator, n, accessor=None):
    '''max_length(valid.string, 20) -> string_max_length_20

    Wrap a validator that also validates the returned value is less than the
    given max length.
    '''
    if accessor is None:
        accessor = len

    def max_length_validator(value, expect):
        value = validator(value, expect)
        if value is not None and accessor(value) > n:
            raise ValueError(
                'The value is greater than max length %s: %s'
                % (n, accessor(value))
            )
        return value

    return wraps(max_length_validator, validator, '_max_length_%s' % n)

def range(validator, low, high):
    '''range(valid.int, 0, 10) -> int_range_0_to_10

    Wrap validator that also validates the returned value lies within a given
    range.

    You can create an unbounded range by using None as either bound.
    '''
    def range_validator(value, expect):
        value = validator(value, expect)
        if value is not None and not (
            low <= value <= high
            if low is not None and high is not None
            else low <= value if low is not None
            else value <= high
        ):
            raise ValueError(
                'The value is not within the range %s <= %s <= %s'
                % (low, value, high)
            )
        return value

    return wraps(range_validator, validator, '_range_%s_to_%s' % (low, high))

float_range = lambda low, high: range(float, low, high)
float_or_none_range = lambda low, high: range(float_or_none, low, high)

int_range = lambda low, high: range(int, low, high)
int_or_none_range = lambda low, high: range(int_or_none, low, high)

string_max_length = lambda n: max_length(string, n)
string_or_none_max_length = lambda n: max_length(string_or_none, n)

#
# primitive validators
#

float = no_none(primitive(__builtins__['float']))
float_or_none = or_none(primitive(__builtins__['float']))

int = no_none(primitive(__builtins__['int']))
int_csv = no_none(csv(int))
int_or_none = or_none(primitive(__builtins__['int']))
int_csv_or_none = csv(int)

def string_or_none(value, expect):
    if value is not None:
        if not isinstance(value, basestring):
            raise ValueError('Expected string, saw: %s' % type(value))

    return value
string = no_none(string_or_none)

#
# complex validators
#

def bool_or_none(value, expect):
    '''bool_or_none('yes') -> True

    Take a truthy/falsey string and make it a boolean.
    '''
    truthy = ('1', 'true', 'yes')
    def str_or_none_lower(value, expect):
        return str(value).lower() if value is not None else None
    value = choice(
        str_or_none_lower,
        ('0', 'false', 'no', 'null') + truthy
    )(value, expect)

    if value is None:
        return None
    else:
        return value in truthy
bool = no_none(bool_or_none)

def date_or_none(value, expect):
    value = string_or_none(value, expect)

    if value is not None:
        clean = dateparse.parse_date(value)
        if clean is None:
            raise ValueError('Could not parse date: %s' % value)
        value = clean

    return value
date = no_none(date_or_none)

def datetime_or_none(value, expect):
    value = string_or_none(value, expect)

    if value is not None:
        clean = dateparse.parse_datetime(value)
        if clean is None:
            raise ValueError('Could not parse datetime: %s' % value)
        value = clean

    return value
datetime = no_none(datetime_or_none)

def dict_or_none(value, expect):
    if value is not None:
        if not isinstance(value, __builtins__['dict']):
            raise ValueError('Not a dict: %s' % value)
    return value
dict = no_none(dict_or_none)

def django_file_or_none(value, expect):
    if value is not None and not isinstance(value, File):
        raise ValueError('No django file found')
    return value
django_file = no_none(django_file_or_none)

def django_image_or_none(value, expect):
    value = django_file_or_none(value, expect)

    if value is not None:
        try:
            from PIL import Image
            image = Image.open(value)
            image.load()

            value.seek(0)

            image = Image.open(value)
            image.verify()

            value.seek(0)

        except Exception:
            raise ValueError('No valid django image (file) found')

    return value
django_image = no_none(django_image_or_none)

email = django_validator(string, validators.validate_email)
email_or_none = django_validator(string_or_none, validators.validate_email)

def none(value, expect):
    if value is not None:
        raise ValueError('Value is expected to be None')
    return value

def slug_or_none(value, expect):
    value = string_or_none(value, expect)
    if value is not None:
        value = slugify(value)
    return value
slug = no_none(slug_or_none)

def time_or_none(value, expect):
    value = string_or_none(value, expect)

    if value is not None:
        clean = dateparse.parse_time(value)
        if clean is None:
            raise ValueError('Could not parse time: %s' % value)
        value = clean

    return value
time = no_none(time_or_none)


Expecter.field_to_validator = {
    models.AutoField                    : int_or_none_range(1, 2147483647),
    models.BigIntegerField              : int_or_none_range(-9223372036854775808, 9223372036854775807),
    models.BooleanField                 : bool_or_none,
    models.CharField                    : string_or_none,
    models.CommaSeparatedIntegerField   : int_csv_or_none,
    models.DateField                    : date_or_none,
    models.DateTimeField                : datetime_or_none,
    models.DecimalField                 : string_or_none,
    models.EmailField                   : email_or_none,
    models.FileField                    : django_file_or_none,
    models.FilePathField                : string_or_none,
    models.FloatField                   : float_or_none,
    models.GenericIPAddressField        : string_or_none,
    models.IPAddressField               : string_or_none,
    models.ImageField                   : django_image_or_none,
    models.IntegerField                 : int_or_none_range(-2147483648, 2147483647),
    models.NullBooleanField             : bool_or_none,
    models.PositiveIntegerField         : int_or_none_range(0, 2147483647),
    models.PositiveSmallIntegerField    : int_or_none_range(0, 32767),
    models.SlugField                    : slug_or_none,
    models.SmallIntegerField            : int_or_none_range(-32768, 32767),
    models.TextField                    : string_or_none,
    models.TimeField                    : time_or_none,
    models.URLField                     : string_or_none,
}

'''
valid - helper routines for validating/cleaning data from untrusted inputs

The pattern and reason for this module are quite simple.  Make it easy to
define an expected structure and clean incoming data based on that structure.

To write a validator, you need only be able to write a function that raises
ValueError with a valuable message on data that is not valid.  Then using
expect(), you can reuse that function to properly validate more complex data
structures.  If a value is valid, but needs transformation (ie: '1' -> 1,
convert from string 1 to integer 1) simply return the cleaned value from the
function.  A simple validator looks like this:

    def validator(value, expect):
        ... raise ValueError if there are issues
        ... if you need to recall expect on a new structure, then:
        ...     expect(expected, newvalue)
        return value

Ideally, you can simply use something like this:

    from krankshaft import valid

    try:
        clean = api.expect(
            {
                'count': valid.int,
                'name': valid.string_max_length(100),
                'text': valid.string,
            },
            data
        )
    except api.ValueIssue as exc:
        print str(exc)

The above validates many things:

- that data has only 3 keys in the top-level dictionary
- that the values of each of those keys comply with their given validator

The clean dictionary (because you expected a dictionary) is then clean and ready
to be used by your program safely.

A ValueIssue will be raised with the ValueError message of each offending value
in data. (it does not stop at the first offending value)
'''

from . import util
from .exceptions import ExpectedIssue, KrankshaftError, ValueIssue
from django.core import validators
from django.core.exceptions import ValidationError
from django.core.files import File
from django.db import models
from django.template.defaultfilters import slugify
from django.utils import dateparse

class ExpecterHelper(object):
    '''
    This helper encapsulates the ability to recursively call expect from within
    a validator.

    Meant to be used exactly like the except function but allows access to
    the entire expecter.

        expect = ExpecterHelper(self, opts)
        expect(expected, data)
    '''
    def __init__(self, expecter, opts):
        self.expecter = expecter
        self.opts = opts

    def __call__(self, expected, data):
        return self.expect(expected, data, **self.opts)

    def __getattr__(self, name):
        return getattr(self.expecter, name)

class Expecter(object):
    '''
    The expect() runtime and handling of specific data structures.

    An Expecter simply contains some configuration options along with methods
    to handle specific data structures (dict, list).  This can be expanded or
    changed via subclasses which is about the only reason this exists
    as a class vs a function.

    Some specific details about how this implementation works.  The expect()
    function is the standard entry point for general users.

        from krankshaft import valid
        expecter = valid.Expecter()
        expecter.expect(valid.int, '1') # returns integer 1

    The expect function works recursively to dive through data structures so
    that the simple validators can be used within them and offer a way to
    define the expected data structure exactly.

    For dictionaries:

        expecter.expect({'key': valid.int}, {'key': '1'}) # returns {'key': 1}

    As you can see, the same valid.int validator is being used, but this time
    within a dict data structure.  The expect routine handles anything not
    callable (`hasattr(obj, '__call__')`) as a data structure that needs to
    be handled.  In the dict case, it iterates over the keys shared between
    the expected dict and the given data dict and applies the expected dict
    value (the validator) to the data dict value.  If a failure occurs for
    a specific key, the routine keeps track of the error but continues on to
    the next key.  In this way, all errors will be discovered versus only the
    first error.

    When errors are found, a `expecter.ValueIssue` is raised with all errors
    encountered.

    The expect dictionary logic is by default very strict about what keys exist
    in the given data.  By default, the keys in the given data must match
    exactly those in the expected dictionary.  You can lift those restrictions
    in a few ways.

        expecter = valid.Expecter(strict_dict=False)
        expecter = valid.Expecter(ignore_extra_keys=True)
        expecter = valid.Expecter(ignore_missing_keys=True)

        expecter.expect({...}, {...}, strict_dict=False)
        expecter.expect({...}, {...}, ignore_extra_keys=True)
        expecter.expect({...}, {...}, ignore_missing_keys=True)

    Either you configure the `Expecter` to handle dictionaries that why be
    default, or you override the option at the call site.  The `strict_dict`
    option is a quick way to make both extra/missing keys True or False.  So
    `strict_dict=False` would make `ignore_extra_keys` and `ignore_missing_keys`
    both True.

    Lists get some special non-intuitive (at first) treatment compared to
    dictionaries.  Let's start with the most obvious case:

        expecter.expect([valid.int, valid.int], ['1', 2]) # returns [1, 2]

    Makes sense right?  `expect()` iterates over the list and applies the
    validator to each matching member of the given data list.  And as you'd
    expect, if the given data list had more or less members than the expected
    list, it will blow up.

    Another list case, but a little more confusing:

        expecter.expect([], [1, 'a', None]) # returns [1, 'a', None]

    An empty list accepts all lists, since no validator is given.  An empty
    list is accepted as well.

    The last list case which may be the most confusing at first is this:

        expecter.expect([valid.int], [1, '2']) # returns [1, 2]

    A single validator in a list is special in that that validator is applied
    to all given members of the given data list.  It accepts zero or more
    values in the given data list.  It makes it easy to validate a list of
    homegenus elements which is very typical.

    This makes it a bit tough to validate a list of one or more members.  But
    there is a special validator for that.  The `list_n_or_more` is the
    validator you want:

        expecter.expect(valid.list_n_or_more(valid.int, 1), ['1', 2])

    The above will guarantee at least one member exists and that all members
    are `valid.int`s.

    As for tuple's, they behave just like lists.
    '''
    ExpectedIssue = ExpectedIssue
    ExpecterHelper = ExpecterHelper
    ValueIssue = ValueIssue

    defaults = {
        'ignore_extra_keys': False,
        'ignore_missing_keys': False,
    }

    field_to_validator = {} # setup at end of module

    def __init__(self, **opts):
        self.field_to_validator = self.field_to_validator.copy()
        self.opts = self.options(opts, self.defaults)

    def expect(self, expected, data, **opts):
        '''expect({'key': valid.int}, {'key': '1'}) -> {'key': 1}

        Clean and validate a given data structure using an expected structure.

        You may pass valid options directly here via opts.
        '''
        opts = self.options(opts, self.opts)

        if hasattr(expected, '__call__'):
            try:
                data = expected(data, self.ExpecterHelper(self, opts))

            except ValueError as exc:
                raise self.ValueIssue([self.format_exc(exc)])

            else:
                return data

        if expected.__class__ is not data.__class__:
            raise self.ValueIssue([
                'Unexpected type, expected %s: %s'
                % (expected.__class__, data.__class__)
            ])

        method = getattr(self, 'expect_' + expected.__class__.__name__, None)
        if not method:
            raise self.ExpectedIssue(
                'Your expected data structure is unhandled for type: %s'
                % expected.__class__
            )

        return method(expected, data, opts=opts)

    def expect_dict(self, expected, data, opts):
        '''
        Dictionary data structure handling with expect().

        You probably dont want to use this directly.
        '''
        clean = {}
        data_keys_set = set(data.keys())
        errors = {}
        expected_keys = expected.keys()
        expected_keys_set = set(expected_keys)

        processed = False
        if len(expected_keys) == 1 and hasattr(expected_keys[0], '__call__'):
            processed = True
            for key in data_keys_set:
                # validate key
                try:
                    self.expect(expected_keys[0], key, **opts)
                except self.ValueIssue as exc:
                    errors.setdefault(key, []).extend(exc.errors)

                # validate value
                try:
                    clean[key] = \
                        self.expect(expected_keys[0], data[key], **opts)
                except self.ValueIssue as exc:
                    errors.setdefault(key, []).extend(exc.errors)

        elif (
            not (opts['ignore_extra_keys'] and opts['ignore_missing_keys'])
            and expected_keys_set != data_keys_set
        ):
            extra_keys = data_keys_set - expected_keys_set
            missing_keys = expected_keys_set - data_keys_set
            if not opts['ignore_extra_keys'] and extra_keys:
                errors.setdefault('__nonkeyerrors__', []) \
                    .append('Extra keys: %s' % ', '.join(list(extra_keys)))

            if not opts['ignore_missing_keys'] and missing_keys:
                errors.setdefault('__nonkeyerrors__', []) \
                    .append('Missing keys: %s' % ', '.join(list(missing_keys)))

        if not processed:
            processed = True
            for key in (expected_keys_set & data_keys_set):
                try:
                    clean[key] = self.expect(expected[key], data[key], **opts)

                except self.ValueIssue as exc:
                    errors[key] = exc.errors

        if errors:
            raise self.ValueIssue(errors)

        return clean

    def expect_list(self, expected, data, opts):
        '''
        List data structure handling with expect().

        You probably dont want to use this directly.
        '''
        clean = []
        errors = {}

        if len(expected) == 0:
            return data[:]

        elif len(expected) == 1:
            for i, value in enumerate(data):
                try:
                    clean.append(self.expect(expected[0], value, **opts))
                except self.ValueIssue as exc:
                    errors[i] = exc.errors

        elif len(expected) == len(data):
            for i, (cleaner, d) in enumerate(zip(expected, data)):
                try:
                    clean.append(self.expect(cleaner, d, **opts))
                except self.ValueIssue as exc:
                    errors[i] = exc.errors

        else:
            errors['__nonindexerrors__'] = [
                'Expected list of length %s, saw %s' % (
                    len(expected),
                    len(data),
                )
            ]

        if errors:
            raise self.ValueIssue(errors)

        return clean

    def expect_tuple(self, expected, data, opts):
        '''
        Tuple data structure handling with expect().

        You probably dont want to use this directly.
        '''
        return tuple(self.expect_list(expected, data, opts))

    def format_exc(self, exc):
        '''format_exc(exception) -> '...'

        Format an exception to be encoded into the error data structure.
        '''
        return str(exc)

    def from_field(self, field):
        '''from_field(model.field.field) -> validator or expected data structure

        Return a validator or an expected data structure (passable to expect())
        given a django model field.
        '''
        if field.__class__ in (
            models.ForeignKey,
            models.OneToOneField,
            models.ManyToManyField
        ):
            other_field = field
            while hasattr(other_field, 'rel') and other_field.rel:
                other_field = other_field.rel.get_related_field()

            validator = self.field_to_validator[other_field.__class__]

        else:
            try:
                validator = self.field_to_validator[field.__class__]
            except KeyError:
                raise self.ExpectedIssue(
                    'Unable to resolve "%s" to a validator, register your model'
                    ' field with api.expecter.register(Field, valid.validator)'
                    % str(field)
                )

        # apply django validators early (or late in the validation stack)
        # this way if a django validator cannot handle a blank value but we
        # properly handle it with our blank validator, it wont blow up in
        # django validator incorrectly
        if field.validators:
            validator = django_validator(validator, *field.validators)

        if not field.null:
            validator = no_none(validator)

        if field.primary_key:
            validator = blank_not(validator)

        elif field.blank:
            validator = blank(validator)

        elif not field.choices:
            # choices overrides what values are allowed, if the blank value
            # is a valid choice, let it make that decision
            validator = blank_not(validator)

        if field.choices:
            validator = choice(validator, tuple([
                value
                for value, display in field.choices
            ]))

        if hasattr(field, 'max_length') and field.max_length:
            if isinstance(field, models.FileField):
                validator = max_length(validator, field.max_length,
                    accessor=lambda value: \
                        len(field.generate_filename(value, value.name))
                )

            else:
                validator = max_length(validator, field.max_length)

        if field.__class__ is models.ManyToManyField:
            if field.blank:
                return [validator]

            else:
                return list_n_or_more(validator, 1)

        return validator

    def options(self, opts, defaults):
        '''
        Validate and set default options.
        '''
        return util.valid(
            util.defaults(self.shortcuts(opts), defaults),
            self.defaults.keys()
        )

    def register(self, field, validator):
        '''register(Field, valid.validator)

        Register a validator to be used for a given Field.
        '''
        self.field_to_validator[field] = validator

    def shortcuts(self, opts):
        '''
        Handle option shortcuts like `strict_dict`.
        '''
        if 'strict_dict' in opts:
            strict_dict = opts.pop('strict_dict')
            opts['ignore_extra_keys'] = not strict_dict
            opts['ignore_missing_keys'] = not strict_dict
        return opts

#
# validator helpers
#

def blank(validator):
    '''blank(validator) -> validator_blank

    Short circuit a validator and return the empty value if an empty value is
    given.
    '''
    def blank_validator(value, expect):
        if value == '':
            return value
        return validator(value, expect)
    return wraps(blank_validator, validator, '_blank')

def blank_not(validator):
    '''blank_not(validator) -> validator_blank_not

    Short circuit a validator and raise ValueError if an empty value is given.
    '''
    def blank_not_validator(value, expect):
        if value == '':
            raise ValueError(
                '%s does not accept blank values'
                % blank_not_validator.__name__
            )
        return validator(value, expect)
    return wraps(blank_not_validator, validator, '_blank_not')

def no_none(validator):
    '''or_none(validator) -> validator_no_none

    Given a validator returns ValueError when the given value is invalid,
    wrap it in such a way that it will properly handle being given None and
    raise ValueError.
    '''
    def no_none_validator(value, expect):
        if value is None:
            raise ValueError(
                '%s does not accept None'
                % no_none_validator.__name__
            )
        return validator(value, expect)
    return wraps(no_none_validator, validator, '_no_none')

def or_none(validator):
    '''or_none(validator) -> validator_or_none

    Given a validator returns ValueError when the given value is invalid,
    wrap it in such a way that it will properly handle being given None and
    return None.
    '''
    def or_none_validator(value, expect):
        return None if value is None else validator(value, expect)
    return wraps(or_none_validator, validator, '_or_none')

def primitive(validator):
    '''primitive(int) -> int

    Upgrade a primitive validator to a validator that accepts the expect
    argument.
    '''
    def primitive_validator(value, expect):
        return validator(value)

    return wraps(primitive_validator, validator)

def wraps(wrapper, wrapped, suffix='', prefix=''):
    '''wraps(wrapper, validator)

    Wrap a validator in another validator.
    '''
    wrapper.__name__ = prefix + wrapped.__name__ + suffix

    return wrapper

#
# validator factories
#

def choice(validator, choices):
    '''choice(valid.string, ['a', 'b', 'c']) -> string_choices

    Wrap validator that also validates the returned value is in the list of
    valid choices.
    '''
    def choice_validator(value, expect):
        value = validator(value, expect)
        if value is not None and value not in choices:
            raise ValueError('The value is not a valid choice: %s' % value)
        return value

    return wraps(choice_validator, validator, '_choices')

def csv(validator, separator=','):
    '''csv(valid.int) -> int_csv

    Wrap validator so that it validates a list separated by commas.
    '''
    def csv_validator(data, expect):
        data = string_or_none(data, expect)

        if data is not None:
            clean = expect(
                [validator],
                [member for member in data.split(',') if member],
            )
            return ','.join([
                str(member)
                for member in clean
            ])

        return data

    return wraps(csv_validator, validator, '_csv')

def django_validator(validator, *validators):
    '''django_validator(valid.string, *field.validators) -> django_validator

    Use django validators as extra validation to a value.
    '''
    def django_validator_validator(value, expect):
        value = validator(value, expect)

        if value is not None:
            for django_is_valid in validators:
                try:
                    django_is_valid(value)
                except ValidationError as exc:
                    raise ValueError(exc.messages[0])

        return value

    return wraps(django_validator_validator, validator, '_django_validator')

def list_n_or_more(validator, n):
    '''list_n_or_more(valid.int, 1) -> list_1_or_more_int

    Wrap a validator that also validates the returned list has one or more
    members.
    '''
    if n < 0:
        raise KrankshaftError(
            'list_n_or_more only accepts values >= 0, not %s' % n
        )

    def list_n_or_more_validator(data, expect):
        clean = None
        errors = {}
        try:
            clean = expect([validator], data)
        except expect.ValueIssue as exc:
            errors = exc.errors

        if clean is not None and len(clean) < n:
            errors.setdefault('__nonindexerrors__', []) \
                .append(
                    'Expected list with %s or more elements, saw %s'
                    % (n, len(data))
                )

        if errors:
            raise expect.ValueIssue(errors)

        return clean

    return wraps(list_n_or_more_validator, validator,
        prefix='list_%s_or_more_' % n
    )

def max_length(validator, n, accessor=None):
    '''max_length(valid.string, 20) -> string_max_length_20

    Wrap a validator that also validates the returned value is less than the
    given max length.
    '''
    if accessor is None:
        accessor = len

    def max_length_validator(value, expect):
        value = validator(value, expect)
        if value is not None and accessor(value) > n:
            raise ValueError(
                'The value is greater than max length %s: %s'
                % (n, accessor(value))
            )
        return value

    return wraps(max_length_validator, validator, '_max_length_%s' % n)

def range(validator, low, high):
    '''range(valid.int, 0, 10) -> int_range_0_to_10

    Wrap validator that also validates the returned value lies within a given
    range.

    You can create an unbounded range by using None as either bound.
    '''
    def range_validator(value, expect):
        value = validator(value, expect)
        if value is not None and not (
            low <= value <= high
            if low is not None and high is not None
            else low <= value if low is not None
            else value <= high
        ):
            raise ValueError(
                'The value is not within the range %s <= %s <= %s'
                % (low, value, high)
            )
        return value

    return wraps(range_validator, validator, '_range_%s_to_%s' % (low, high))

float_range = lambda low, high: range(float, low, high)
float_or_none_range = lambda low, high: range(float_or_none, low, high)

int_range = lambda low, high: range(int, low, high)
int_or_none_range = lambda low, high: range(int_or_none, low, high)

string_max_length = lambda n: max_length(string, n)
string_or_none_max_length = lambda n: max_length(string_or_none, n)

#
# primitive validators
#

float = no_none(primitive(__builtins__['float']))
float_or_none = or_none(primitive(__builtins__['float']))

int = no_none(primitive(__builtins__['int']))
int_csv = no_none(csv(int))
int_or_none = or_none(primitive(__builtins__['int']))
int_csv_or_none = csv(int)

def string_or_none(value, expect):
    if value is not None:
        if not isinstance(value, basestring):
            raise ValueError('Expected string, saw: %s' % type(value))

    return value
string = no_none(string_or_none)

#
# complex validators
#

def bool_or_none(value, expect):
    '''bool_or_none('yes') -> True

    Take a truthy/falsey string and make it a boolean.
    '''
    truthy = ('1', 'true', 'yes')
    def str_or_none_lower(value, expect):
        return str(value).lower() if value is not None else None
    value = choice(
        str_or_none_lower,
        ('0', 'false', 'no', 'null') + truthy
    )(value, expect)

    if value is None:
        return None
    else:
        return value in truthy
bool = no_none(bool_or_none)

def date_or_none(value, expect):
    value = string_or_none(value, expect)

    if value is not None:
        clean = dateparse.parse_date(value)
        if clean is None:
            raise ValueError('Could not parse date: %s' % value)
        value = clean

    return value
date = no_none(date_or_none)

def datetime_or_none(value, expect):
    value = string_or_none(value, expect)

    if value is not None:
        clean = dateparse.parse_datetime(value)
        if clean is None:
            raise ValueError('Could not parse datetime: %s' % value)
        value = clean

    return value
datetime = no_none(datetime_or_none)

def dict_or_none(value, expect):
    if value is not None:
        if not isinstance(value, __builtins__['dict']):
            raise ValueError('Not a dict: %s' % value)
    return value
dict = no_none(dict_or_none)

def django_file_or_none(value, expect):
    if value is not None and not isinstance(value, File):
        raise ValueError('No django file found')
    return value
django_file = no_none(django_file_or_none)

def django_image_or_none(value, expect):
    value = django_file_or_none(value, expect)

    if value is not None:
        try:
            from PIL import Image
            image = Image.open(value)
            image.load()

            value.seek(0)

            image = Image.open(value)
            image.verify()

            value.seek(0)

        except Exception:
            raise ValueError('No valid django image (file) found')

    return value
django_image = no_none(django_image_or_none)

email = django_validator(string, validators.validate_email)
email_or_none = django_validator(string_or_none, validators.validate_email)

def none(value, expect):
    if value is not None:
        raise ValueError('Value is expected to be None')
    return value

def slug_or_none(value, expect):
    value = string_or_none(value, expect)
    if value is not None:
        value = slugify(value)
    return value
slug = no_none(slug_or_none)

def time_or_none(value, expect):
    value = string_or_none(value, expect)

    if value is not None:
        clean = dateparse.parse_time(value)
        if clean is None:
            raise ValueError('Could not parse time: %s' % value)
        value = clean

    return value
time = no_none(time_or_none)


Expecter.field_to_validator = {
    models.AutoField                    : int_or_none_range(1, 2147483647),
    models.BigIntegerField              : int_or_none_range(-9223372036854775808, 9223372036854775807),
    models.BooleanField                 : bool_or_none,
    models.CharField                    : string_or_none,
    models.CommaSeparatedIntegerField   : int_csv_or_none,
    models.DateField                    : date_or_none,
    models.DateTimeField                : datetime_or_none,
    models.DecimalField                 : string_or_none,
    models.EmailField                   : email_or_none,
    models.FileField                    : django_file_or_none,
    models.FilePathField                : string_or_none,
    models.FloatField                   : float_or_none,
    models.GenericIPAddressField        : string_or_none,
    models.IPAddressField               : string_or_none,
    models.ImageField                   : django_image_or_none,
    models.IntegerField                 : int_or_none_range(-2147483648, 2147483647),
    models.NullBooleanField             : bool_or_none,
    models.PositiveIntegerField         : int_or_none_range(0, 2147483647),
    models.PositiveSmallIntegerField    : int_or_none_range(0, 32767),
    models.SlugField                    : slug_or_none,
    models.SmallIntegerField            : int_or_none_range(-32768, 32767),
    models.TextField                    : string_or_none,
    models.TimeField                    : time_or_none,
    models.URLField                     : string_or_none,
}
valid - helper routines for validating/cleaning data from untrusted inputs  The pattern and reason for this module are quite simple.  Make it easy to define an expected structure and clean incoming data based on that structure.  To write a validator, you need only be able to write a function that raises ValueError with a valuable message on data that is not valid.  Then using expect(), you can reuse that function to properly validate more complex data structures.  If a value is valid, but needs transformation (ie: '1' -> 1, convert from string 1 to integer 1) simply return the cleaned value from the function.  A simple validator looks like this:      def validator(value, expect):         ... raise ValueError if there are issues         ... if you need to recall expect on a new structure, then:         ...     expect(expected, newvalue)         return value  Ideally, you can simply use something like this:      from krankshaft import valid      try:         clean = api.expect(             {                 'count': valid.int,                 'name': valid.string_max_length(100),                 'text': valid.string,             },             data         )     except api.ValueIssue as exc:         print str(exc)  The above validates many things:  - that data has only 3 keys in the top-level dictionary - that the values of each of those keys comply with their given validator  The clean dictionary (because you expected a dictionary) is then clean and ready to be used by your program safely.  A ValueIssue will be raised with the ValueError message of each offending value in data. (it does not stop at the first offending value) This helper encapsulates the ability to recursively call expect from within     a validator.      Meant to be used exactly like the except function but allows access to     the entire expecter.          expect = ExpecterHelper(self, opts)         expect(expected, data) The expect() runtime and handling of specific data structures.      An Expecter simply contains some configuration options along with methods     to handle specific data structures (dict, list).  This can be expanded or     changed via subclasses which is about the only reason this exists     as a class vs a function.      Some specific details about how this implementation works.  The expect()     function is the standard entry point for general users.          from krankshaft import valid         expecter = valid.Expecter()         expecter.expect(valid.int, '1') # returns integer 1      The expect function works recursively to dive through data structures so     that the simple validators can be used within them and offer a way to     define the expected data structure exactly.      For dictionaries:          expecter.expect({'key': valid.int}, {'key': '1'}) # returns {'key': 1}      As you can see, the same valid.int validator is being used, but this time     within a dict data structure.  The expect routine handles anything not     callable (`hasattr(obj, '__call__')`) as a data structure that needs to     be handled.  In the dict case, it iterates over the keys shared between     the expected dict and the given data dict and applies the expected dict     value (the validator) to the data dict value.  If a failure occurs for     a specific key, the routine keeps track of the error but continues on to     the next key.  In this way, all errors will be discovered versus only the     first error.      When errors are found, a `expecter.ValueIssue` is raised with all errors     encountered.      The expect dictionary logic is by default very strict about what keys exist     in the given data.  By default, the keys in the given data must match     exactly those in the expected dictionary.  You can lift those restrictions     in a few ways.          expecter = valid.Expecter(strict_dict=False)         expecter = valid.Expecter(ignore_extra_keys=True)         expecter = valid.Expecter(ignore_missing_keys=True)          expecter.expect({...}, {...}, strict_dict=False)         expecter.expect({...}, {...}, ignore_extra_keys=True)         expecter.expect({...}, {...}, ignore_missing_keys=True)      Either you configure the `Expecter` to handle dictionaries that why be     default, or you override the option at the call site.  The `strict_dict`     option is a quick way to make both extra/missing keys True or False.  So     `strict_dict=False` would make `ignore_extra_keys` and `ignore_missing_keys`     both True.      Lists get some special non-intuitive (at first) treatment compared to     dictionaries.  Let's start with the most obvious case:          expecter.expect([valid.int, valid.int], ['1', 2]) # returns [1, 2]      Makes sense right?  `expect()` iterates over the list and applies the     validator to each matching member of the given data list.  And as you'd     expect, if the given data list had more or less members than the expected     list, it will blow up.      Another list case, but a little more confusing:          expecter.expect([], [1, 'a', None]) # returns [1, 'a', None]      An empty list accepts all lists, since no validator is given.  An empty     list is accepted as well.      The last list case which may be the most confusing at first is this:          expecter.expect([valid.int], [1, '2']) # returns [1, 2]      A single validator in a list is special in that that validator is applied     to all given members of the given data list.  It accepts zero or more     values in the given data list.  It makes it easy to validate a list of     homegenus elements which is very typical.      This makes it a bit tough to validate a list of one or more members.  But     there is a special validator for that.  The `list_n_or_more` is the     validator you want:          expecter.expect(valid.list_n_or_more(valid.int, 1), ['1', 2])      The above will guarantee at least one member exists and that all members     are `valid.int`s.      As for tuple's, they behave just like lists. # setup at end of module expect({'key': valid.int}, {'key': '1'}) -> {'key': 1}          Clean and validate a given data structure using an expected structure.          You may pass valid options directly here via opts. Dictionary data structure handling with expect().          You probably dont want to use this directly. # validate key # validate value List data structure handling with expect().          You probably dont want to use this directly. Tuple data structure handling with expect().          You probably dont want to use this directly. format_exc(exception) -> '...'          Format an exception to be encoded into the error data structure. from_field(model.field.field) -> validator or expected data structure          Return a validator or an expected data structure (passable to expect())         given a django model field. # apply django validators early (or late in the validation stack) # this way if a django validator cannot handle a blank value but we # properly handle it with our blank validator, it wont blow up in # django validator incorrectly # choices overrides what values are allowed, if the blank value # is a valid choice, let it make that decision Validate and set default options. register(Field, valid.validator)          Register a validator to be used for a given Field. Handle option shortcuts like `strict_dict`. # # validator helpers # blank(validator) -> validator_blank      Short circuit a validator and return the empty value if an empty value is     given. blank_not(validator) -> validator_blank_not      Short circuit a validator and raise ValueError if an empty value is given. or_none(validator) -> validator_no_none      Given a validator returns ValueError when the given value is invalid,     wrap it in such a way that it will properly handle being given None and     raise ValueError. or_none(validator) -> validator_or_none      Given a validator returns ValueError when the given value is invalid,     wrap it in such a way that it will properly handle being given None and     return None. primitive(int) -> int      Upgrade a primitive validator to a validator that accepts the expect     argument. wraps(wrapper, validator)      Wrap a validator in another validator. # # validator factories # choice(valid.string, ['a', 'b', 'c']) -> string_choices      Wrap validator that also validates the returned value is in the list of     valid choices. csv(valid.int) -> int_csv      Wrap validator so that it validates a list separated by commas. django_validator(valid.string, *field.validators) -> django_validator      Use django validators as extra validation to a value. list_n_or_more(valid.int, 1) -> list_1_or_more_int      Wrap a validator that also validates the returned list has one or more     members. max_length(valid.string, 20) -> string_max_length_20      Wrap a validator that also validates the returned value is less than the     given max length. range(valid.int, 0, 10) -> int_range_0_to_10      Wrap validator that also validates the returned value lies within a given     range.      You can create an unbounded range by using None as either bound. # # primitive validators # # # complex validators # bool_or_none('yes') -> True      Take a truthy/falsey string and make it a boolean.