"""
Validator for repeating items.
"""

from api import NoDefault, Invalid
from compound import CompoundValidator, to_python, from_python
try:
    from sets import Set
except ImportError:
    # We only use it for type information now:
    Set = None

__all__ = ['ForEach']

class ForEach(CompoundValidator):

    """
    Use this to apply a validator/converter to each item in a list.

    For instance::

        ForEach(AsInt(), InList([1, 2, 3]))

    Will take a list of values and try to convert each of them to
    an integer, and then check if each integer is 1, 2, or 3.  Using
    multiple arguments is equivalent to::

        ForEach(All(AsInt(), InList([1, 2, 3])))

    Use convert_to_list=True if you want to force the input to be a
    list.  This will turn non-lists into one-element lists, and None
    into the empty list.  This tries to detect sequences by iterating
    over them (except strings, which aren't considered sequences).

    ForEach will try to convert the entire list, even if errors are
    encountered.  If errors are encountered, they will be collected
    and a single Invalid exception will be raised at the end (with
    error_list set).

    If the incoming value is a Set, then we return a Set.
    """

    convert_to_list = True
    if_empty = NoDefault
    repeating = True
    
    def attempt_convert(self, value, state, validate):
        if self.convert_to_list:
            value = self._convert_to_list(value)
        if self.if_empty is not NoDefault and not value:
            return self.if_empty
        if self.not_empty and not value:
            if validate is from_python and self.accept_python:
                return []
            raise Invalid(
                self.message('empty', state),
                value, state)
        new_list = []
        errors = []
        all_good = True
        is_set = isinstance(value, Set)
        if state is not None:
            previous_index = getattr(state, 'index', NoDefault)
            previous_full_list = getattr(state, 'full_list', NoDefault)
            index = 0
            state.full_list = value
        try:
            for sub_value in value:
                if state:
                    state.index = index
                    index += 1
                good_pass = True
                for validator in self.validators:
                    try:
                        sub_value = validate(validator, sub_value, state)
                    except Invalid, e:
                        errors.append(e)
                        all_good = False
                        good_pass = False
                        break
                if good_pass:
                    errors.append(None)
                new_list.append(sub_value)
            if all_good:
                if is_set:
                    new_list = Set(new_list)
                return new_list
            else:
                raise Invalid(
                    'Errors:\n%s' % '\n'.join([unicode(e) for e in errors if e]),
                    value,
                    state,
                    error_list=errors)
        finally:
            if state is not None:
                if previous_index is NoDefault:
                    try:
                        del state.index
                    except AttributeError:
                        pass
                else:
                    state.index = previous_index
                if previous_full_list is NoDefault:
                    try:
                        del state.full_list
                    except AttributeError:
                        pass
                else:
                    state.full_list = previous_full_list

    def empty_value(self, value):
        return []

    @property
    def if_missing(self):
        return []

    def _convert_to_list(self, value):
        if isinstance(value, (str, unicode)):
            return [value]
        elif value is None:
            return []
        elif isinstance(value, (list, tuple)):
            return value
        try:
            for n in value:
                break
            return value
        ## @@: Should this catch any other errors?:
        except TypeError:
            return [value]

@@ -1308,24 +1308,52 @@
 ting = True%0A
+    _if_missing = NoDefault%0A
     %0A    def
@@ -3763,17 +3763,132 @@
     
-@property
+def _if_missing__get(self):%0A        if self._if_missing is NoDefault:%0A            return %5B%5D%0A        return self._if_missing%0A
 %0A   
@@ -3892,16 +3892,17 @@
     def 
+_
 if_missi
@@ -3907,33 +3907,211 @@
 sing
-(self):%0A        return %5B%5D
+__set(self, value):%0A        self._if_missing = value%0A%0A    def _if_missing__del(self):%0A        self._if_missing = NoDefault%0A%0A    if_missing = property(_if_missing__get, _if_missing__set, _if_missing__del)
 %0A%0A  

Make ForEach(if_missing=None) work (previous commit had made it unsettable)