0001"""
0002Validator for repeating items.
0003"""
0004
0005from api import NoDefault, Invalid
0006from compound import CompoundValidator, to_python, from_python
0007try:
0008    from sets import Set
0009except ImportError:
0010    # We only use it for type information now:
0011    Set = None
0012
0013__all__ = ['ForEach']
0014
0015class ForEach(CompoundValidator):
0016
0017    """
0018    Use this to apply a validator/converter to each item in a list.
0019
0020    For instance::
0021
0022        ForEach(AsInt(), InList([1, 2, 3]))
0023
0024    Will take a list of values and try to convert each of them to
0025    an integer, and then check if each integer is 1, 2, or 3.  Using
0026    multiple arguments is equivalent to::
0027
0028        ForEach(All(AsInt(), InList([1, 2, 3])))
0029
0030    Use convert_to_list=True if you want to force the input to be a
0031    list.  This will turn non-lists into one-element lists, and None
0032    into the empty list.  This tries to detect sequences by iterating
0033    over them (except strings, which aren't considered sequences).
0034
0035    ForEach will try to convert the entire list, even if errors are
0036    encountered.  If errors are encountered, they will be collected
0037    and a single Invalid exception will be raised at the end (with
0038    error_list set).
0039
0040    If the incoming value is a Set, then we return a Set.
0041    """
0042
0043    convert_to_list = True
0044    if_empty = NoDefault
0045    if_missing = []
0046    repeating = True
0047
0048    def attempt_convert(self, value, state, validate):
0049        if self.convert_to_list:
0050            value = self._convert_to_list(value)
0051        if self.if_empty is not NoDefault and not value:
0052            return self.if_empty
0053        if self.not_empty and not value:
0054            if validate is from_python and self.accept_python:
0055                return []
0056            raise Invalid(
0057                self.message('empty', state),
0058                value, state)
0059        new_list = []
0060        errors = []
0061        all_good = True
0062        is_set = isinstance(value, Set)
0063        if state is not None:
0064            previous_index = getattr(state, 'index', NoDefault)
0065            previous_full_list = getattr(state, 'full_list', NoDefault)
0066            index = 0
0067            state.full_list = value
0068        try:
0069            for sub_value in value:
0070                if state:
0071                    state.index = index
0072                    index += 1
0073                good_pass = True
0074                for validator in self.validators:
0075                    try:
0076                        sub_value = validate(validator, sub_value, state)
0077                    except Invalid, e:
0078                        errors.append(e)
0079                        all_good = False
0080                        good_pass = False
0081                        break
0082                if good_pass:
0083                    errors.append(None)
0084                new_list.append(sub_value)
0085            if all_good:
0086                if is_set:
0087                    new_list = Set(new_list)
0088                return new_list
0089            else:
0090                raise Invalid(
0091                    'Errors:\n%s' % '\n'.join([unicode(e) for e in errors if e]),
0092                    value,
0093                    state,
0094                    error_list=errors)
0095        finally:
0096            if state is not None:
0097                if previous_index is NoDefault:
0098                    try:
0099                        del state.index
0100                    except AttributeError:
0101                        pass
0102                else:
0103                    state.index = previous_index
0104                if previous_full_list is NoDefault:
0105                    try:
0106                        del state.full_list
0107                    except AttributeError:
0108                        pass
0109                else:
0110                    state.full_list = previous_full_list
0111
0112    def _convert_to_list(self, value):
0113        if isinstance(value, (str, unicode)):
0114            return [value]
0115        elif value is None:
0116            return []
0117        elif isinstance(value, (list, tuple)):
0118            return value
0119        try:
0120            for n in value:
0121                break
0122            return value
0123        ## @@: Should this catch any other errors?:
0124        except TypeError:
0125            return [value]