1# -*- coding: UTF-8 -*- 2# 3# The MIT License 4# 5# Copyright (c) 2011-2012, 2014 Felix Schwarz <felix.schwarz@oss.schwarz.eu> 6# 7# Permission is hereby granted, free of charge, to any person obtaining a copy 8# of this software and associated documentation files (the "Software"), to deal 9# in the Software without restriction, including without limitation the rights 10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11# copies of the Software, and to permit persons to whom the Software is 12# furnished to do so, subject to the following conditions: 13# 14# The above copyright notice and this permission notice shall be included in 15# all copies or substantial portions of the Software. 16# 17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23# THE SOFTWARE. 24 25from pycerberus.api import NoValueSet, Validator 26from pycerberus.errors import InvalidArgumentsError, InvalidDataError 27from pycerberus.i18n import _ 28 29 30__all__ = ['ForEach'] 31 32 33class ForEach(Validator): 34 """Apply a validator to every item of an iterable (like map). Also you 35 can specify the allowed min/max number of items in that iterable.""" 36 37 def __init__(self, validator, min_length=0, max_length=NoValueSet, **kwargs): 38 self._validator = self._init_validator(validator) 39 self._min_length = min_length 40 self._max_length = max_length 41 if (self._min_length is not None) and (self._max_length is not NoValueSet): 42 if self._min_length > self._max_length: 43 values = tuple(map(repr, [self._min_length, self._max_length])) 44 message = 'min_length must be smaller or equal to max_length (%s > %s)' % values 45 raise InvalidArgumentsError(message) 46 kwargs.setdefault('default', ()) 47 self.super.__init__(**kwargs) 48 49 def messages(self): 50 return { 51 'invalid_type': _(u'Validator got unexpected input (expected string, got "%(classname)s").'), 52 'too_short': _(u'More than %(min)d items required.'), 53 'too_long': _(u'Less than %(max)d items required.'), 54 } 55 56 def convert(self, values, context): 57 exceptions = [] 58 if not self._is_iterable(values): 59 self.raise_error('invalid_type', values, context, classname=values.__class__.__name__) 60 if self._min_length and len(values) < self._min_length: 61 self.raise_error('too_short', values, context, min=self._min_length) 62 if self._max_length != NoValueSet and len(values) > self._max_length: 63 self.raise_error('too_long', values, context, max=self._max_length) 64 65 validated = [] 66 for value in values: 67 try: 68 validated.append(self._validator.process(value, context)) 69 except InvalidDataError as e: 70 exceptions.append(e) 71 else: 72 exceptions.append(None) 73 if list(filter(None, exceptions)): 74 self._raise_exception(exceptions, context) 75 return tuple(validated) 76 77 def _is_iterable(self, value): 78 try: 79 iter(value) 80 except TypeError: 81 return False 82 return True 83 84 def _init_validator(self, validator): 85 if isinstance(validator, type): 86 validator = validator() 87 return validator 88 89 def _raise_exception(self, exceptions, context): 90 first_error = list(filter(None, exceptions))[0].details() 91 # can't use self.exception() as all values are already filled 92 raise InvalidDataError(first_error.msg(), first_error.value(), first_error.key(), 93 context, error_list=exceptions) 94 95 96