mirror of
https://github.com/djohnlewis/stackdump
synced 2025-01-22 22:51:36 +00:00
152 lines
4.6 KiB
Python
152 lines
4.6 KiB
Python
"""
|
|
Validator for repeating items.
|
|
"""
|
|
|
|
import warnings
|
|
|
|
try:
|
|
set
|
|
except NameError: # Python < 2.4
|
|
from sets import Set as set
|
|
|
|
filters = warnings.filters[:]
|
|
warnings.simplefilter('ignore', DeprecationWarning)
|
|
warnings.filters = filters
|
|
|
|
|
|
from api import NoDefault, Invalid
|
|
from compound import CompoundValidator, from_python
|
|
|
|
__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
|
|
_if_missing = ()
|
|
|
|
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 []
|
|
|
|
class _IfMissing(object):
|
|
def __get__(self, obj, type=None):
|
|
if obj is None:
|
|
return []
|
|
elif obj._if_missing is ForEach._if_missing:
|
|
return []
|
|
else:
|
|
return obj._if_missing
|
|
def __set__(self, obj, value):
|
|
obj._if_missing = value
|
|
def __delete__(self, obj):
|
|
obj._if_missing = NoDefault
|
|
|
|
if_missing = _IfMissing()
|
|
del _IfMissing
|
|
|
|
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]
|