i18n-ize colander.
This commit is contained in:
		@@ -3,10 +3,20 @@ import itertools
 | 
			
		||||
import iso8601
 | 
			
		||||
import pprint
 | 
			
		||||
import re
 | 
			
		||||
import translationstring
 | 
			
		||||
 | 
			
		||||
_ = translationstring.TranslationStringFactory('colander')
 | 
			
		||||
 | 
			
		||||
class _missing(object):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
def interpolate(msgs):
 | 
			
		||||
    for s in msgs:
 | 
			
		||||
        if hasattr(s, 'interpolate'):
 | 
			
		||||
            yield s.interpolate()
 | 
			
		||||
        else:
 | 
			
		||||
            yield s
 | 
			
		||||
 | 
			
		||||
class Invalid(Exception):
 | 
			
		||||
    """
 | 
			
		||||
    An exception raised by data types and validators indicating that
 | 
			
		||||
@@ -113,7 +123,7 @@ class Invalid(Exception):
 | 
			
		||||
                exc.msg and msgs.append(exc.msg)
 | 
			
		||||
                keyname = exc._keyname()
 | 
			
		||||
                keyname and keyparts.append(keyname)
 | 
			
		||||
            errors['.'.join(keyparts)] = '; '.join(msgs)
 | 
			
		||||
            errors['.'.join(keyparts)] = '; '.join(interpolate(msgs))
 | 
			
		||||
        return errors
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
@@ -164,7 +174,7 @@ class Function(object):
 | 
			
		||||
    The default value for the ``message`` when not provided via the
 | 
			
		||||
    constructor is ``Invalid value``.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, function, message='Invalid value'):
 | 
			
		||||
    def __init__(self, function, message=_('Invalid value')):
 | 
			
		||||
        self.function = function
 | 
			
		||||
        self.message = message
 | 
			
		||||
 | 
			
		||||
@@ -189,7 +199,7 @@ class Regex(object):
 | 
			
		||||
    def __init__(self, regex, msg=None):
 | 
			
		||||
        self.match_object = re.compile(regex)
 | 
			
		||||
        if msg is None:
 | 
			
		||||
            self.msg = "String does not match expected pattern"  
 | 
			
		||||
            self.msg = _("String does not match expected pattern")
 | 
			
		||||
        else:
 | 
			
		||||
            self.msg = msg
 | 
			
		||||
 | 
			
		||||
@@ -204,7 +214,7 @@ class Email(Regex):
 | 
			
		||||
    """    
 | 
			
		||||
    def __init__(self, msg=None):
 | 
			
		||||
        if msg is None:
 | 
			
		||||
            msg = "Invalid email address"  
 | 
			
		||||
            msg = _("Invalid email address")
 | 
			
		||||
        super(Email, self).__init__(
 | 
			
		||||
            u'(?i)^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$', msg=msg)
 | 
			
		||||
 | 
			
		||||
@@ -219,23 +229,22 @@ class Range(object):
 | 
			
		||||
    :exc:`colander.Invalid` error when reporting a validation failure
 | 
			
		||||
    caused by a value not meeting the minimum.  If ``min_err`` is
 | 
			
		||||
    specified, it must be a string.  The string may contain the
 | 
			
		||||
    replacement targets ``%(min)s`` and ``%(val)s``, representing the
 | 
			
		||||
    replacement targets ``${min}`` and ``${val}``, representing the
 | 
			
		||||
    minimum value and the provided value respectively.  If it is not
 | 
			
		||||
    provided, it defaults to ``'%(val)s is less than minimum value
 | 
			
		||||
    %(min)s'``.
 | 
			
		||||
    provided, it defaults to ``'${val} is less than minimum value
 | 
			
		||||
    ${min}'``.
 | 
			
		||||
 | 
			
		||||
    ``max_err`` is used to form the ``msg`` of the
 | 
			
		||||
    :exc:`colander.Invalid` error when reporting a validation failure
 | 
			
		||||
    caused by a value exceeding the maximum.  If ``max_err`` is
 | 
			
		||||
    specified, it must be a string.  The string may contain the
 | 
			
		||||
    replacement targets ``%(max)s`` and ``%(val)s``, representing the
 | 
			
		||||
    replacement targets ``${max}`` and ``${val}``, representing the
 | 
			
		||||
    maximum value and the provided value respectively.  If it is not
 | 
			
		||||
    provided, it defaults to ``'%(val)s is greater than maximum value
 | 
			
		||||
    %(max)s'``.
 | 
			
		||||
    provided, it defaults to ``'${val} is greater than maximum value
 | 
			
		||||
    ${max}'``.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    min_err = '%(val)s is less than minimum value %(min)s'
 | 
			
		||||
    max_err = '%(val)s is greater than maximum value %(max)s'
 | 
			
		||||
    min_err = _('${val} is less than minimum value ${min}')
 | 
			
		||||
    max_err = _('${val} is greater than maximum value ${max}')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, min=None, max=None, min_err=None, max_err=None):
 | 
			
		||||
        self.min = min
 | 
			
		||||
@@ -248,11 +257,13 @@ class Range(object):
 | 
			
		||||
    def __call__(self, node, value):
 | 
			
		||||
        if self.min is not None:
 | 
			
		||||
            if value < self.min:
 | 
			
		||||
                raise Invalid(node,self.min_err % {'val':value, 'min':self.min})
 | 
			
		||||
                min_err = _(self.min_err, mapping={'val':value, 'min':self.min})
 | 
			
		||||
                raise Invalid(node, min_err)
 | 
			
		||||
 | 
			
		||||
        if self.max is not None:
 | 
			
		||||
            if value > self.max:
 | 
			
		||||
                raise Invalid(node,self.max_err % {'val':value, 'max':self.max})
 | 
			
		||||
                max_err = _(self.max_err, mapping={'val':value, 'max':self.max})
 | 
			
		||||
                raise Invalid(node, max_err)
 | 
			
		||||
 | 
			
		||||
class Length(object):
 | 
			
		||||
    """ Validator which succeeds if the value passed to it has a
 | 
			
		||||
@@ -265,26 +276,28 @@ class Length(object):
 | 
			
		||||
    def __call__(self, node, value):
 | 
			
		||||
        if self.min is not None:
 | 
			
		||||
            if len(value) < self.min:
 | 
			
		||||
                raise Invalid(
 | 
			
		||||
                    node,
 | 
			
		||||
                    'Shorter than minimum length %s' % self.min)
 | 
			
		||||
                min_err = _('Shorter than minimum length ${min}',
 | 
			
		||||
                            mapping={'min':self.min})
 | 
			
		||||
                raise Invalid(node, min_err)
 | 
			
		||||
 | 
			
		||||
        if self.max is not None:
 | 
			
		||||
            if len(value) > self.max:
 | 
			
		||||
                raise Invalid(
 | 
			
		||||
                    node,
 | 
			
		||||
                    'Longer than maximum length %s' % self.max)
 | 
			
		||||
                max_err = _('Longer than maximum length ${max}',
 | 
			
		||||
                            mapping={'max':self.max})
 | 
			
		||||
                raise Invalid(node, max_err)
 | 
			
		||||
 | 
			
		||||
class OneOf(object):
 | 
			
		||||
    """ Validator which succeeds if the value passed to it is one of
 | 
			
		||||
    a fixed set of values """
 | 
			
		||||
    def __init__(self, values):
 | 
			
		||||
        self.values = values
 | 
			
		||||
    def __init__(self, choices):
 | 
			
		||||
        self.choices = choices
 | 
			
		||||
 | 
			
		||||
    def __call__(self, node, value):
 | 
			
		||||
        if not value in self.values:
 | 
			
		||||
            raise Invalid(node, '"%s" is not one of %s' % (
 | 
			
		||||
                value, ', '.join(['%s' % x for x in self.values])))
 | 
			
		||||
        if not value in self.choices:
 | 
			
		||||
            choices = ', '.join(['%s' % x for x in self.choices])
 | 
			
		||||
            err = _('"${val}" is not one of ${choices}',
 | 
			
		||||
                    mapping={'val':value, 'choices':choices})
 | 
			
		||||
            raise Invalid(node, err)
 | 
			
		||||
 | 
			
		||||
class Mapping(object):
 | 
			
		||||
    """ A type which represents a mapping of names to nodes.
 | 
			
		||||
@@ -350,7 +363,10 @@ class Mapping(object):
 | 
			
		||||
        try:
 | 
			
		||||
            return dict(value)
 | 
			
		||||
        except Exception, e:
 | 
			
		||||
            raise Invalid(node, '%r is not a mapping type: %s' % (value, e))
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('"${val}" is not a mapping type: ${err}',
 | 
			
		||||
                          mapping = {'val':value, 'err':e})
 | 
			
		||||
                          )
 | 
			
		||||
 | 
			
		||||
    def _impl(self, node, value, callback, default_callback, unknown=None,
 | 
			
		||||
              partial=None):
 | 
			
		||||
@@ -375,7 +391,9 @@ class Mapping(object):
 | 
			
		||||
                        if not partial:
 | 
			
		||||
                            raise Invalid(
 | 
			
		||||
                                subnode,
 | 
			
		||||
                                '"%s" is required but missing' % subnode.name)
 | 
			
		||||
                                _('"${val}" is required but missing',
 | 
			
		||||
                                  mapping={'val':subnode.name})
 | 
			
		||||
                                )
 | 
			
		||||
                        else:
 | 
			
		||||
                            continue
 | 
			
		||||
                    result[name] = default_callback(subnode)
 | 
			
		||||
@@ -388,8 +406,11 @@ class Mapping(object):
 | 
			
		||||
 | 
			
		||||
        if unknown == 'raise':
 | 
			
		||||
            if value:
 | 
			
		||||
                raise Invalid(node,
 | 
			
		||||
                              'Unrecognized keys in mapping: %r' % value)
 | 
			
		||||
                raise Invalid(
 | 
			
		||||
                    node,
 | 
			
		||||
                    _('Unrecognized keys in mapping: "${val}"',
 | 
			
		||||
                      mapping={'val':value})
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
        elif unknown == 'preserve':
 | 
			
		||||
            result.update(value)
 | 
			
		||||
@@ -435,15 +456,20 @@ class Tuple(Positional):
 | 
			
		||||
    """
 | 
			
		||||
    def _validate(self, node, value):
 | 
			
		||||
        if not hasattr(value, '__iter__'):
 | 
			
		||||
            raise Invalid(node, '%r is not iterable' % value)
 | 
			
		||||
            raise Invalid(
 | 
			
		||||
                node,
 | 
			
		||||
                _('"${val}" is not iterable', mapping={'val':value})
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        valuelen, nodelen = len(value), len(node.children)
 | 
			
		||||
 | 
			
		||||
        if valuelen != nodelen:
 | 
			
		||||
            raise Invalid(
 | 
			
		||||
                node,
 | 
			
		||||
                ('%s has an incorrect number of elements '
 | 
			
		||||
                 '(expected %s, was %s)' % (value, nodelen, valuelen)))
 | 
			
		||||
                _('"${val}" has an incorrect number of elements '
 | 
			
		||||
                  '(expected ${exp}, was ${was})',
 | 
			
		||||
                  mapping={'val':value, 'exp':nodelen, 'was':valuelen})
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        return list(value)
 | 
			
		||||
 | 
			
		||||
@@ -510,7 +536,9 @@ class Sequence(Positional):
 | 
			
		||||
        if accept_scalar:
 | 
			
		||||
            return [value]
 | 
			
		||||
        else:
 | 
			
		||||
            raise Invalid(node, '%r is not iterable' % value)
 | 
			
		||||
            raise Invalid(node, _('"${val}" is not iterable',
 | 
			
		||||
                                  mapping={'val':value})
 | 
			
		||||
                          )
 | 
			
		||||
 | 
			
		||||
    def _impl(self, node, value, callback, accept_scalar):
 | 
			
		||||
        if accept_scalar is None:
 | 
			
		||||
@@ -612,10 +640,12 @@ class String(object):
 | 
			
		||||
            if not isinstance(value, unicode):
 | 
			
		||||
                value = unicode(str(value), self.encoding)
 | 
			
		||||
        except Exception, e:
 | 
			
		||||
            raise Invalid(node, '%r is not a string: %s' % (value, e))
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('${val} is not a string: %{err}',
 | 
			
		||||
                            mapping={'val':value, 'err':e}))
 | 
			
		||||
        if not value:
 | 
			
		||||
            if node.required:
 | 
			
		||||
                raise Invalid(node, 'Required')
 | 
			
		||||
                raise Invalid(node, _('Required'))
 | 
			
		||||
            value = node.default
 | 
			
		||||
        return value
 | 
			
		||||
 | 
			
		||||
@@ -629,7 +659,9 @@ class String(object):
 | 
			
		||||
            return result
 | 
			
		||||
        except Exception, e:
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          '%r cannot be serialized to str: %s' % (value, e))
 | 
			
		||||
                          _('"${val} cannot be serialized to str: ${err}',
 | 
			
		||||
                            mapping={'val':value, 'err':e})
 | 
			
		||||
                          )
 | 
			
		||||
 | 
			
		||||
Str = String
 | 
			
		||||
 | 
			
		||||
@@ -645,15 +677,21 @@ class Integer(object):
 | 
			
		||||
        except Exception:
 | 
			
		||||
            if value == '':
 | 
			
		||||
                if node.required:
 | 
			
		||||
                    raise Invalid(node, 'Required')
 | 
			
		||||
                    raise Invalid(node, _('Required'))
 | 
			
		||||
                return node.default
 | 
			
		||||
            raise Invalid(node, '"%s" is not a number' % value)
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('"${val}" is not a number',
 | 
			
		||||
                            mapping={'val':value})
 | 
			
		||||
                          )
 | 
			
		||||
 | 
			
		||||
    def serialize(self, node, value):
 | 
			
		||||
        try:
 | 
			
		||||
            return str(int(value))
 | 
			
		||||
        except Exception:
 | 
			
		||||
            raise Invalid(node, '"%s" is not a number' % value)
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('"${val}" is not a number',
 | 
			
		||||
                            mapping={'val':value}),
 | 
			
		||||
                          )
 | 
			
		||||
 | 
			
		||||
Int = Integer
 | 
			
		||||
 | 
			
		||||
@@ -669,15 +707,21 @@ class Float(object):
 | 
			
		||||
        except Exception:
 | 
			
		||||
            if value == '':
 | 
			
		||||
                if node.required:
 | 
			
		||||
                    raise Invalid(node, 'Required')
 | 
			
		||||
                    raise Invalid(node, _('Required'))
 | 
			
		||||
                return node.default
 | 
			
		||||
            raise Invalid(node, '"%s" is not a number' % value)
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('"${val}" is not a number',
 | 
			
		||||
                            mapping={'val':value})
 | 
			
		||||
                          )
 | 
			
		||||
 | 
			
		||||
    def serialize(self, node, value):
 | 
			
		||||
        try:
 | 
			
		||||
            return str(float(value))
 | 
			
		||||
        except Exception:
 | 
			
		||||
            raise Invalid(node, '"%s" is not a number' % value)
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('"${val}" is not a number',
 | 
			
		||||
                            mapping={'val':value}),
 | 
			
		||||
                          )
 | 
			
		||||
 | 
			
		||||
Int = Integer
 | 
			
		||||
 | 
			
		||||
@@ -699,10 +743,12 @@ class Boolean(object):
 | 
			
		||||
        try:
 | 
			
		||||
            value = str(value)
 | 
			
		||||
        except:
 | 
			
		||||
            raise Invalid(node, '%r is not a string' % value)
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('${val} is not a string', mapping={'val':value})
 | 
			
		||||
                          )
 | 
			
		||||
        if not value:
 | 
			
		||||
            if node.required:
 | 
			
		||||
                raise Invalid(node, 'Required')
 | 
			
		||||
                raise Invalid(node, _('Required'))
 | 
			
		||||
            value = node.default
 | 
			
		||||
        value = value.lower()
 | 
			
		||||
        if value in ('false', '0'):
 | 
			
		||||
@@ -759,7 +805,9 @@ class GlobalObject(object):
 | 
			
		||||
            if not self.package:
 | 
			
		||||
                raise Invalid(
 | 
			
		||||
                    node,
 | 
			
		||||
                    'relative name "%s" irresolveable without package' % value)
 | 
			
		||||
                    _('relative name "${val}" irresolveable without package',
 | 
			
		||||
                      mapping={'val':value})
 | 
			
		||||
                    )
 | 
			
		||||
            if value in ['.', ':']:
 | 
			
		||||
                value = self.package.__name__
 | 
			
		||||
            else:
 | 
			
		||||
@@ -774,7 +822,9 @@ class GlobalObject(object):
 | 
			
		||||
            if self.package is None:
 | 
			
		||||
                raise Invalid(
 | 
			
		||||
                    node,
 | 
			
		||||
                    'relative name "%s" irresolveable without package' % value)
 | 
			
		||||
                    _('relative name "${val}" irresolveable without package',
 | 
			
		||||
                      mapping={'val':value})
 | 
			
		||||
                    )
 | 
			
		||||
            name = module.split('.')
 | 
			
		||||
        else:
 | 
			
		||||
            name = value.split('.')
 | 
			
		||||
@@ -782,8 +832,9 @@ class GlobalObject(object):
 | 
			
		||||
                if module is None:
 | 
			
		||||
                    raise Invalid(
 | 
			
		||||
                        node,
 | 
			
		||||
                        'relative name "%s" irresolveable without package' %
 | 
			
		||||
                        value)
 | 
			
		||||
                        _('relative name "${val}" irresolveable without '
 | 
			
		||||
                          'package', mapping={'val':value})
 | 
			
		||||
                        )
 | 
			
		||||
                module = module.split('.')
 | 
			
		||||
                name.pop(0)
 | 
			
		||||
                while not name[0]:
 | 
			
		||||
@@ -805,7 +856,8 @@ class GlobalObject(object):
 | 
			
		||||
 | 
			
		||||
    def deserialize(self, node, value):
 | 
			
		||||
        if not isinstance(value, basestring):
 | 
			
		||||
            raise Invalid(node, '"%s" is not a string' % value)
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('"${val}" is not a string', mapping={'val':value}))
 | 
			
		||||
        try:
 | 
			
		||||
            if ':' in value:
 | 
			
		||||
                return self._pkg_resources_style(node, value)
 | 
			
		||||
@@ -813,13 +865,17 @@ class GlobalObject(object):
 | 
			
		||||
                return self._zope_dottedname_style(node, value)
 | 
			
		||||
        except ImportError:
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          'The dotted name "%s" cannot be imported' % value)
 | 
			
		||||
                          _('The dotted name "${name}" cannot be imported',
 | 
			
		||||
                            mapping={'name':value}))
 | 
			
		||||
 | 
			
		||||
    def serialize(self, node, value):
 | 
			
		||||
        try:
 | 
			
		||||
            return value.__name__
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            raise Invalid(node, '%r has no __name__' % value)
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('"${val}" has no __name__',
 | 
			
		||||
                            mapping={'val':value})
 | 
			
		||||
                          )
 | 
			
		||||
 | 
			
		||||
class DateTime(object):
 | 
			
		||||
    """ A type representing a Python ``datetime.datetime`` object.
 | 
			
		||||
@@ -839,10 +895,10 @@ class DateTime(object):
 | 
			
		||||
    You can adjust the error message reported by this class by
 | 
			
		||||
    changing its ``err_template`` attribute in a subclass on an
 | 
			
		||||
    instance of this class.  By default, the ``err_template``
 | 
			
		||||
    attribute is the string ``%(value)s cannot be parsed as an iso8601
 | 
			
		||||
    date: %(exc)s``.  This string is used as the interpolation subject
 | 
			
		||||
    of a dictionary composed of ``value`` and ``exc``.  ``value`` and
 | 
			
		||||
    ``exc`` are the unvalidatable value and the exception caused
 | 
			
		||||
    attribute is the string ``${value} cannot be parsed as an iso8601
 | 
			
		||||
    date: ${exc}``.  This string is used as the interpolation subject
 | 
			
		||||
    of a dictionary composed of ``val`` and ``err``.  ``val`` and
 | 
			
		||||
    ``err`` are the unvalidatable value and the exception caused
 | 
			
		||||
    trying to convert the value, respectively.
 | 
			
		||||
 | 
			
		||||
    For convenience, this type is also willing to coerce
 | 
			
		||||
@@ -859,7 +915,7 @@ class DateTime(object):
 | 
			
		||||
    The subnodes of the :class:`colander.SchemaNode` that wraps
 | 
			
		||||
    this type are ignored.
 | 
			
		||||
    """
 | 
			
		||||
    err_template =  '%(value)s cannot be parsed as an iso8601 date: %(exc)s'
 | 
			
		||||
    err_template =  _('${val} cannot be parsed as an iso8601 date: ${err}')
 | 
			
		||||
    def __init__(self, default_tzinfo=None):
 | 
			
		||||
        if default_tzinfo is None:
 | 
			
		||||
            default_tzinfo = iso8601.iso8601.Utc()
 | 
			
		||||
@@ -869,7 +925,10 @@ class DateTime(object):
 | 
			
		||||
        if type(value) is datetime.date: # cant use isinstance; dt subs date
 | 
			
		||||
            value = datetime.datetime.combine(value, datetime.time())
 | 
			
		||||
        if not isinstance(value, datetime.datetime):
 | 
			
		||||
            raise Invalid(node, '%r is not a datetime object' % value)
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('"${val}" is not a datetime object',
 | 
			
		||||
                            mapping={'val':value})
 | 
			
		||||
                          )
 | 
			
		||||
        if value.tzinfo is None:
 | 
			
		||||
            value = value.replace(tzinfo=self.default_tzinfo)
 | 
			
		||||
        return value.isoformat()
 | 
			
		||||
@@ -883,8 +942,8 @@ class DateTime(object):
 | 
			
		||||
                result = datetime.datetime(year, month, day,
 | 
			
		||||
                                           tzinfo=self.default_tzinfo)
 | 
			
		||||
            except Exception, e:
 | 
			
		||||
                raise Invalid(node, self.err_template % {'value':value,
 | 
			
		||||
                                                         'exc':e})
 | 
			
		||||
                raise Invalid(node, _(self.err_template,
 | 
			
		||||
                                      mapping={'val':value, 'err':e}))
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
class Date(object):
 | 
			
		||||
@@ -899,9 +958,9 @@ class Date(object):
 | 
			
		||||
    You can adjust the error message reported by this class by
 | 
			
		||||
    changing its ``err_template`` attribute in a subclass on an
 | 
			
		||||
    instance of this class.  By default, the ``err_template``
 | 
			
		||||
    attribute is the string ``%(value)s cannot be parsed as an iso8601
 | 
			
		||||
    date: %(exc)s``.  This string is used as the interpolation subject
 | 
			
		||||
    of a dictionary composed of ``value`` and ``exc``.  ``value`` and
 | 
			
		||||
    attribute is the string ``${val} cannot be parsed as an iso8601
 | 
			
		||||
    date: ${err}``.  This string is used as the interpolation subject
 | 
			
		||||
    of a dictionary composed of ``val`` and ``err``.  ``val`` and
 | 
			
		||||
    ``exc`` are the unvalidatable value and the exception caused
 | 
			
		||||
    trying to convert the value, respectively.
 | 
			
		||||
 | 
			
		||||
@@ -920,18 +979,21 @@ class Date(object):
 | 
			
		||||
    The subnodes of the :class:`colander.SchemaNode` that wraps
 | 
			
		||||
    this type are ignored.
 | 
			
		||||
    """
 | 
			
		||||
    err_template =  '%(value)s cannot be parsed as an iso8601 date: %(exc)s'
 | 
			
		||||
    err_template =  _('${val} cannot be parsed as an iso8601 date: ${err}')
 | 
			
		||||
    def serialize(self, node, value):
 | 
			
		||||
        if isinstance(value, datetime.datetime):
 | 
			
		||||
            value = value.date()
 | 
			
		||||
        if not isinstance(value, datetime.date):
 | 
			
		||||
            raise Invalid(node, '%r is not a date object' % value)
 | 
			
		||||
            raise Invalid(node,
 | 
			
		||||
                          _('"${val}" is not a date object',
 | 
			
		||||
                            mapping={'val':value})
 | 
			
		||||
                          )
 | 
			
		||||
        return value.isoformat()
 | 
			
		||||
 | 
			
		||||
    def deserialize(self, node, value):
 | 
			
		||||
        if not value:
 | 
			
		||||
            if node.required:
 | 
			
		||||
                raise Invalid(node, 'Required')
 | 
			
		||||
                raise Invalid(node, _('Required'))
 | 
			
		||||
            return node.default
 | 
			
		||||
        try:
 | 
			
		||||
            result = iso8601.parse_date(value)
 | 
			
		||||
@@ -941,8 +1003,10 @@ class Date(object):
 | 
			
		||||
                year, month, day = map(int, value.split('-', 2))
 | 
			
		||||
                result = datetime.date(year, month, day)
 | 
			
		||||
            except Exception, e:
 | 
			
		||||
                raise Invalid(node, self.err_template % {'value':value,
 | 
			
		||||
                                                         'exc':e})
 | 
			
		||||
                raise Invalid(node,
 | 
			
		||||
                              _(self.err_template,
 | 
			
		||||
                                mapping={'val':value, 'err':e})
 | 
			
		||||
                              )
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
class SchemaNode(object):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								colander/locale/colander.pot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								colander/locale/colander.pot
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
# Translations template for colander.
 | 
			
		||||
# Copyright (C) 2010 ORGANIZATION
 | 
			
		||||
# This file is distributed under the same license as the colander project.
 | 
			
		||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
 | 
			
		||||
#
 | 
			
		||||
#, fuzzy
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: colander 0.5.2\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
 | 
			
		||||
"POT-Creation-Date: 2010-04-25 21:06-0400\n"
 | 
			
		||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
			
		||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
			
		||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
			
		||||
"MIME-Version: 1.0\n"
 | 
			
		||||
"Content-Type: text/plain; charset=utf-8\n"
 | 
			
		||||
"Content-Transfer-Encoding: 8bit\n"
 | 
			
		||||
"Generated-By: Babel 0.9.5\n"
 | 
			
		||||
 | 
			
		||||
#: colander/__init__.py:177
 | 
			
		||||
msgid "Invalid value"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: colander/__init__.py:202
 | 
			
		||||
msgid "String does not match expected pattern"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: colander/__init__.py:217
 | 
			
		||||
msgid "Invalid email address"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: colander/__init__.py:246
 | 
			
		||||
msgid "${val} is less than minimum value ${min}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: colander/__init__.py:247
 | 
			
		||||
msgid "${val} is greater than maximum value ${max}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: colander/__init__.py:461
 | 
			
		||||
msgid "\"${val}\" is not iterable"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: colander/__init__.py:648 colander/__init__.py:680 colander/__init__.py:710
 | 
			
		||||
#: colander/__init__.py:751 colander/__init__.py:996
 | 
			
		||||
msgid "Required"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: colander/__init__.py:747
 | 
			
		||||
msgid "${val} is not a string"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: colander/__init__.py:860
 | 
			
		||||
msgid "\"${val}\" is not a string"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: colander/__init__.py:918 colander/__init__.py:982
 | 
			
		||||
msgid "${val} cannot be parsed as an iso8601 date: ${err}"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
@@ -189,7 +189,7 @@ class TestRange(unittest.TestCase):
 | 
			
		||||
    def test_min_failure(self):
 | 
			
		||||
        validator = self._makeOne(min=1)
 | 
			
		||||
        e = invalid_exc(validator, None, 0)
 | 
			
		||||
        self.assertEqual(e.msg, '0 is less than minimum value 1')
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(), '0 is less than minimum value 1')
 | 
			
		||||
 | 
			
		||||
    def test_min_failure_msg_override(self):
 | 
			
		||||
        validator = self._makeOne(min=1, min_err='wrong')
 | 
			
		||||
@@ -199,7 +199,8 @@ class TestRange(unittest.TestCase):
 | 
			
		||||
    def test_max_failure(self):
 | 
			
		||||
        validator = self._makeOne(max=1)
 | 
			
		||||
        e = invalid_exc(validator, None, 2)
 | 
			
		||||
        self.assertEqual(e.msg, '2 is greater than maximum value 1')
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(),
 | 
			
		||||
                         '2 is greater than maximum value 1')
 | 
			
		||||
 | 
			
		||||
    def test_max_failure_msg_override(self):
 | 
			
		||||
        validator = self._makeOne(max=1, max_err='wrong')
 | 
			
		||||
@@ -272,12 +273,12 @@ class TestLength(unittest.TestCase):
 | 
			
		||||
    def test_min_failure(self):
 | 
			
		||||
        validator = self._makeOne(min=1)
 | 
			
		||||
        e = invalid_exc(validator, None, '')
 | 
			
		||||
        self.assertEqual(e.msg, 'Shorter than minimum length 1')
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(), 'Shorter than minimum length 1')
 | 
			
		||||
 | 
			
		||||
    def test_max_failure(self):
 | 
			
		||||
        validator = self._makeOne(max=1)
 | 
			
		||||
        e = invalid_exc(validator, None, 'ab')
 | 
			
		||||
        self.assertEqual(e.msg, 'Longer than maximum length 1')
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(), 'Longer than maximum length 1')
 | 
			
		||||
 | 
			
		||||
class TestOneOf(unittest.TestCase):
 | 
			
		||||
    def _makeOne(self, values):
 | 
			
		||||
@@ -291,7 +292,8 @@ class TestOneOf(unittest.TestCase):
 | 
			
		||||
    def test_failure(self):
 | 
			
		||||
        validator = self._makeOne([1, 2])
 | 
			
		||||
        e = invalid_exc(validator, None, None)
 | 
			
		||||
        self.assertEqual(e.msg, '"None" is not one of 1, 2')
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(), '"None" is not one of 1, 2')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestMapping(unittest.TestCase):
 | 
			
		||||
    def _makeOne(self, *arg, **kw):
 | 
			
		||||
@@ -314,7 +316,7 @@ class TestMapping(unittest.TestCase):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.deserialize, node, None)
 | 
			
		||||
        self.failUnless(
 | 
			
		||||
            e.msg.startswith('None is not a mapping type'))
 | 
			
		||||
            e.msg.interpolate().startswith('"None" is not a mapping type'))
 | 
			
		||||
 | 
			
		||||
    def test_deserialize_no_subnodes(self):
 | 
			
		||||
        node = DummySchemaNode(None)
 | 
			
		||||
@@ -334,7 +336,9 @@ class TestMapping(unittest.TestCase):
 | 
			
		||||
        node.children = [DummySchemaNode(None, name='a')]
 | 
			
		||||
        typ = self._makeOne(unknown='raise')
 | 
			
		||||
        e = invalid_exc(typ.deserialize, node, {'a':1, 'b':2})
 | 
			
		||||
        self.assertEqual(e.msg, "Unrecognized keys in mapping: {'b': 2}")
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(),
 | 
			
		||||
                         "Unrecognized keys in mapping: \"{'b': 2}\"")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_deserialize_unknown_preserve(self):
 | 
			
		||||
        node = DummySchemaNode(None)
 | 
			
		||||
@@ -372,7 +376,8 @@ class TestMapping(unittest.TestCase):
 | 
			
		||||
            ]
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.deserialize, node, {'a':1})
 | 
			
		||||
        self.assertEqual(e.children[0].msg, '"b" is required but missing')
 | 
			
		||||
        self.assertEqual(e.children[0].msg.interpolate(),
 | 
			
		||||
                         '"b" is required but missing')
 | 
			
		||||
 | 
			
		||||
    def test_deserialize_subnode_partial(self):
 | 
			
		||||
        node = DummySchemaNode(None)
 | 
			
		||||
@@ -389,7 +394,7 @@ class TestMapping(unittest.TestCase):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.serialize, node, None)
 | 
			
		||||
        self.failUnless(
 | 
			
		||||
            e.msg.startswith('None is not a mapping type'))
 | 
			
		||||
            e.msg.interpolate().startswith('"None" is not a mapping type'))
 | 
			
		||||
 | 
			
		||||
    def test_serialize_no_subnodes(self):
 | 
			
		||||
        node = DummySchemaNode(None)
 | 
			
		||||
@@ -443,8 +448,8 @@ class TestTuple(unittest.TestCase):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.deserialize, node, None)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            e.msg,
 | 
			
		||||
            'None is not iterable')
 | 
			
		||||
            e.msg.interpolate(),
 | 
			
		||||
            '"None" is not iterable')
 | 
			
		||||
        self.assertEqual(e.node, node)
 | 
			
		||||
 | 
			
		||||
    def test_deserialize_no_subnodes(self):
 | 
			
		||||
@@ -465,16 +470,16 @@ class TestTuple(unittest.TestCase):
 | 
			
		||||
        node.children = [DummySchemaNode(None, name='a')]
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.deserialize, node, ('a','b'))
 | 
			
		||||
        self.assertEqual(e.msg,
 | 
			
		||||
           "('a', 'b') has an incorrect number of elements (expected 1, was 2)")
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(),
 | 
			
		||||
      "\"('a', 'b')\" has an incorrect number of elements (expected 1, was 2)")
 | 
			
		||||
 | 
			
		||||
    def test_deserialize_toosmall(self):
 | 
			
		||||
        node = DummySchemaNode(None)
 | 
			
		||||
        node.children = [DummySchemaNode(None, name='a')]
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.deserialize, node, ())
 | 
			
		||||
        self.assertEqual(e.msg,
 | 
			
		||||
           "() has an incorrect number of elements (expected 1, was 0)")
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(),
 | 
			
		||||
           '"()" has an incorrect number of elements (expected 1, was 0)')
 | 
			
		||||
 | 
			
		||||
    def test_deserialize_subnodes_raise(self):
 | 
			
		||||
        node = DummySchemaNode(None)
 | 
			
		||||
@@ -492,8 +497,8 @@ class TestTuple(unittest.TestCase):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.serialize, node, None)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            e.msg,
 | 
			
		||||
            'None is not iterable')
 | 
			
		||||
            e.msg.interpolate(),
 | 
			
		||||
            '"None" is not iterable')
 | 
			
		||||
        self.assertEqual(e.node, node)
 | 
			
		||||
 | 
			
		||||
    def test_serialize_no_subnodes(self):
 | 
			
		||||
@@ -514,16 +519,17 @@ class TestTuple(unittest.TestCase):
 | 
			
		||||
        node.children = [DummySchemaNode(None, name='a')]
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.serialize, node, ('a','b'))
 | 
			
		||||
        self.assertEqual(e.msg,
 | 
			
		||||
           "('a', 'b') has an incorrect number of elements (expected 1, was 2)")
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(),
 | 
			
		||||
     "\"('a', 'b')\" has an incorrect number of elements (expected 1, was 2)")
 | 
			
		||||
 | 
			
		||||
    def test_serialize_toosmall(self):
 | 
			
		||||
        node = DummySchemaNode(None)
 | 
			
		||||
        node.children = [DummySchemaNode(None, name='a')]
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.serialize, node, ())
 | 
			
		||||
        self.assertEqual(e.msg,
 | 
			
		||||
           "() has an incorrect number of elements (expected 1, was 0)")
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(),
 | 
			
		||||
           '"()" has an incorrect number of elements (expected 1, was 0)'
 | 
			
		||||
           )
 | 
			
		||||
 | 
			
		||||
    def test_serialize_subnodes_raise(self):
 | 
			
		||||
        node = DummySchemaNode(None)
 | 
			
		||||
@@ -552,8 +558,8 @@ class TestSequence(unittest.TestCase):
 | 
			
		||||
        node.children = [node]
 | 
			
		||||
        e = invalid_exc(typ.deserialize, node, None)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            e.msg,
 | 
			
		||||
            'None is not iterable')
 | 
			
		||||
            e.msg.interpolate(),
 | 
			
		||||
            '"None" is not iterable')
 | 
			
		||||
        self.assertEqual(e.node, node)
 | 
			
		||||
 | 
			
		||||
    def test_deserialize_not_iterable_accept_scalar(self):
 | 
			
		||||
@@ -592,8 +598,8 @@ class TestSequence(unittest.TestCase):
 | 
			
		||||
        node.children = [node]
 | 
			
		||||
        e = invalid_exc(typ.serialize, node, None)
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            e.msg,
 | 
			
		||||
            'None is not iterable')
 | 
			
		||||
            e.msg.interpolate(),
 | 
			
		||||
            '"None" is not iterable')
 | 
			
		||||
        self.assertEqual(e.node, node)
 | 
			
		||||
 | 
			
		||||
    def test_serialize_not_iterable_accept_scalar(self):
 | 
			
		||||
@@ -894,14 +900,15 @@ class TestGlobalObject(unittest.TestCase):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ._zope_dottedname_style, None, '.')
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            e.msg,
 | 
			
		||||
            e.msg.interpolate(),
 | 
			
		||||
            'relative name "." irresolveable without package')
 | 
			
		||||
 | 
			
		||||
    def test_zope_dottedname_style_resolve_relative_nocurrentpackage(self):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ._zope_dottedname_style, None, '.whatever')
 | 
			
		||||
        self.assertEqual(
 | 
			
		||||
            e.msg, 'relative name ".whatever" irresolveable without package')
 | 
			
		||||
            e.msg.interpolate(),
 | 
			
		||||
            'relative name ".whatever" irresolveable without package')
 | 
			
		||||
 | 
			
		||||
    def test_zope_dottedname_style_irrresolveable_relative(self):
 | 
			
		||||
        import colander.tests
 | 
			
		||||
@@ -971,7 +978,7 @@ class TestGlobalObject(unittest.TestCase):
 | 
			
		||||
    def test_deserialize_not_a_string(self):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.deserialize, None, None)
 | 
			
		||||
        self.assertEqual(e.msg, '"None" is not a string')
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(), '"None" is not a string')
 | 
			
		||||
 | 
			
		||||
    def test_deserialize_using_pkgresources_style(self):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
@@ -986,7 +993,7 @@ class TestGlobalObject(unittest.TestCase):
 | 
			
		||||
    def test_deserialize_style_raises(self):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.deserialize, None, 'cant.be.found')
 | 
			
		||||
        self.assertEqual(e.msg,
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(),
 | 
			
		||||
                         'The dotted name "cant.be.found" cannot be imported')
 | 
			
		||||
 | 
			
		||||
    def test_serialize_ok(self):
 | 
			
		||||
@@ -998,7 +1005,7 @@ class TestGlobalObject(unittest.TestCase):
 | 
			
		||||
    def test_serialize_fail(self):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        e = invalid_exc(typ.serialize, None, None)
 | 
			
		||||
        self.assertEqual(e.msg, 'None has no __name__')
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(), '"None" has no __name__')
 | 
			
		||||
 | 
			
		||||
class TestDateTime(unittest.TestCase):
 | 
			
		||||
    def _makeOne(self, *arg, **kw):
 | 
			
		||||
@@ -1020,7 +1027,8 @@ class TestDateTime(unittest.TestCase):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        node = DummySchemaNode(None)
 | 
			
		||||
        e = invalid_exc(typ.serialize, node, 'garbage')
 | 
			
		||||
        self.assertEqual(e.msg, "'garbage' is not a datetime object")
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(),
 | 
			
		||||
                         '"garbage" is not a datetime object')
 | 
			
		||||
 | 
			
		||||
    def test_serialize_with_date(self):
 | 
			
		||||
        import datetime
 | 
			
		||||
@@ -1093,7 +1101,7 @@ class TestDate(unittest.TestCase):
 | 
			
		||||
        typ = self._makeOne()
 | 
			
		||||
        node = DummySchemaNode(None)
 | 
			
		||||
        e = invalid_exc(typ.serialize, node, 'garbage')
 | 
			
		||||
        self.assertEqual(e.msg, "'garbage' is not a date object")
 | 
			
		||||
        self.assertEqual(e.msg.interpolate(), '"garbage" is not a date object')
 | 
			
		||||
 | 
			
		||||
    def test_serialize_with_date(self):
 | 
			
		||||
        import datetime
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								setup.cfg
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								setup.cfg
									
									
									
									
									
								
							@@ -8,3 +8,23 @@ nocapture=1
 | 
			
		||||
cover-package=colander
 | 
			
		||||
cover-erase=1
 | 
			
		||||
 | 
			
		||||
[compile_catalog]
 | 
			
		||||
directory = colander/locale
 | 
			
		||||
domain = colander
 | 
			
		||||
statistics = true
 | 
			
		||||
 | 
			
		||||
[extract_messages]
 | 
			
		||||
add_comments = TRANSLATORS:
 | 
			
		||||
output_file = colander/locale/colander.pot
 | 
			
		||||
width = 80
 | 
			
		||||
 | 
			
		||||
[init_catalog]
 | 
			
		||||
domain = colander
 | 
			
		||||
input_file = colander/locale/colander.pot
 | 
			
		||||
output_dir = colander/locale
 | 
			
		||||
 | 
			
		||||
[update_catalog]
 | 
			
		||||
domain = colander
 | 
			
		||||
input_file = colander/locale/colander.pot
 | 
			
		||||
output_dir = colander/locale
 | 
			
		||||
previous = true
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								setup.py
									
									
									
									
									
								
							@@ -21,7 +21,7 @@ here = os.path.abspath(os.path.dirname(__file__))
 | 
			
		||||
README = open(os.path.join(here, 'README.txt')).read()
 | 
			
		||||
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
 | 
			
		||||
 | 
			
		||||
requires = ['iso8601']
 | 
			
		||||
requires = ['iso8601', 'translationstring']
 | 
			
		||||
 | 
			
		||||
setup(name='colander',
 | 
			
		||||
      version='0.5.2',
 | 
			
		||||
@@ -43,5 +43,9 @@ setup(name='colander',
 | 
			
		||||
      tests_require = requires,
 | 
			
		||||
      install_requires = requires,
 | 
			
		||||
      test_suite="colander",
 | 
			
		||||
      message_extractors = { ".": [
 | 
			
		||||
            ("**.py",   "chameleon_python", None ),
 | 
			
		||||
            ("**.pt",   "chameleon_xml", None ),
 | 
			
		||||
            ]},
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user