0001"""
0002Core classes for validation.
0003"""
0004
0005import declarative
0006import textwrap
0007import re
0008import os
0009try:
0010    from pkg_resources import resource_filename
0011except ImportError:
0012    resource_filename = None
0013
0014__all__ = ['NoDefault', 'Invalid', 'Validator', 'Identity',
0015           'FancyValidator', 'is_validator']
0016
0017import gettext
0018
0019def get_localedir():
0020    if resource_filename is not None:
0021        try:
0022            return resource_filename(__name__, "/i18n")
0023        except NotImplementedError:
0024            # resource_filename doesn't work with non-egg zip files
0025            pass
0026    return os.path.join(os.path.dirname(__file__), 'i18n')
0027
0028def set_stdtranslation(domain="FormEncode", languages=None,                          localedir = get_localedir()):
0030
0031    t = gettext.translation(domain=domain,                               languages=languages,                               localedir=localedir, fallback=True)
0034    global _stdtrans
0035    _stdtrans = t.ugettext
0036
0037set_stdtranslation()
0038
0039def _(s): return s # dummy i18n translation function, nothing is translated here.
0040                   # Instead this is actually done in api.Validator.message.
0041                   # The surrounding _("string") of the strings is only for extracting
0042                   # the strings automatically
0043                   # if you run pygettext with this source comment this function out temporarly 
0044
0045class NoDefault:
0046    pass
0047
0048def is_validator(obj):
0049    return (isinstance(obj, Validator) or
0050            (isinstance(obj, type) and
0051             issubclass(obj, Validator)))
0052
0053class Invalid(Exception):
0054
0055    """
0056    This is raised in response to invalid input.  It has several
0057    public attributes:
0058
0059    msg:
0060        The message, *without* values substituted.  For instance, if
0061        you want HTML quoting of values, you can apply that.
0062    substituteArgs:
0063        The arguments (a dictionary) to go with `msg`.
0064    str(self):
0065        The message describing the error, with values substituted.
0066    value:
0067        The offending (invalid) value.
0068    state:
0069        The state that went with this validator.  This is an
0070        application-specific object.
0071    error_list:
0072        If this was a compound validator that takes a repeating value,
0073        and sub-validator(s) had errors, then this is a list of those
0074        exceptions.  The list will be the same length as the number of
0075        values -- valid values will have None instead of an exception.
0076    error_dict:
0077        Like `error_list`, but for dictionary compound validators.
0078    """
0079
0080    def __init__(self, msg,
0081                 value, state, error_list=None, error_dict=None):
0082        Exception.__init__(self, msg)
0083        self.msg = msg
0084        self.value = value
0085        self.state = state
0086        self.error_list = error_list
0087        self.error_dict = error_dict
0088        assert (not self.error_list or not self.error_dict), (
0089                "Errors shouldn't have both error dicts and lists "
0090                "(error %s has %s and %s)"
0091                % (self, self.error_list, self.error_dict))
0092
0093    def __str__(self):
0094        val = self.msg
0095        #if self.value:
0096        #    val += " (value: %s)" % repr(self.value)
0097        return val
0098
0099    def unpack_errors(self, encode_variables=False, dict_char='.',
0100                      list_char='-'):
0101        """
0102        Returns the error as a simple data structure -- lists,
0103        dictionaries, and strings.
0104        
0105        If ``encode_variables`` is true, then this will return a flat
0106        dictionary, encoded with variable_encode
0107        """
0108        if self.error_list:
0109            assert not encode_variables, (
0110                "You can only encode dictionary errors")
0111            assert not self.error_dict
0112            result = []
0113            for item in self.error_list:
0114                if not item:
0115                    result.append(item)
0116                else:
0117                    result.append(item.unpack_errors())
0118            return result
0119        elif self.error_dict:
0120            result = {}
0121            for name, item in self.error_dict.items():
0122                if isinstance(item, (str, unicode)):
0123                    result[name] = item
0124                else:
0125                    result[name] = item.unpack_errors()
0126            if encode_variables:
0127                import variabledecode
0128                result = variabledecode.variable_encode(result, add_repetitions=False,
0129                                                        dict_char=dict_char,
0130                                                        list_char=list_char)
0131                for key in result.keys():
0132                    if not result[key]:
0133                        del result[key]
0134            return result
0135        else:
0136            assert not encode_variables, (
0137                "You can only encode dictionary errors")
0138            return self.msg
0139
0140
0141############################################################
0142## Base Classes
0143############################################################
0144
0145class Validator(declarative.Declarative):
0146
0147    """
0148    The base class of most validators.  See `IValidator` for more, and
0149    `FancyValidator` for the more common (and more featureful) class.
0150    """
0151
0152    _messages = {}
0153    if_missing = NoDefault
0154    repeating = False
0155    compound = False
0156    gettextargs = {}
0157    use_builtins_gettext = True #In case you dont want to use __builtins__._
0158                                #altough it may be definied, set this to False
0159
0160    __singletonmethods__ = ('to_python', 'from_python', 'message', 'all_messages',
0161                            'subvalidators')
0162
0163    def __classinit__(cls, new_attrs):
0164        if new_attrs.has_key('messages'):
0165            cls._messages = cls._messages.copy()
0166            cls._messages.update(cls.messages)
0167            del cls.messages
0168        cls._initialize_docstring()
0169
0170    def __init__(self, *args, **kw):
0171        if kw.has_key('messages'):
0172            self._messages = self._messages.copy()
0173            self._messages.update(kw['messages'])
0174            del kw['messages']
0175        declarative.Declarative.__init__(self, *args, **kw)
0176
0177    def to_python(self, value, state=None):
0178        return value
0179
0180    def from_python(self, value, state=None):
0181        return value
0182
0183    def message(self, msgName, state, **kw):
0184        #determine translation function
0185        try:
0186            trans = state._
0187        except AttributeError:
0188            try:
0189                if self.use_builtins_gettext:
0190                    import __builtin__
0191                    trans = __builtin__._
0192
0193                else:
0194                    trans = _stdtrans
0195
0196            except AttributeError:
0197                trans = _stdtrans
0198
0199        if not callable(trans):
0200            trans = _stdtrans
0201
0202
0203        try:
0204            return trans(self._messages[msgName], **self.gettextargs) % kw
0205        except KeyError, e:
0206            raise KeyError(
0207                "Key not found (%s) for %r=%r %% %r (from: %s)"
0208                % (e, msgName, self._messages.get(msgName), kw,
0209                   ', '.join(self._messages.keys())))
0210
0211    def all_messages(self):
0212        """
0213        Return a dictionary of all the messages of this validator, and
0214        any subvalidators if present.  Keys are message names, values
0215        may be a message or list of messages.  This is really just
0216        intended for documentation purposes, to show someone all the
0217        messages that a validator or compound validator (like Schemas)
0218        can produce.
0219
0220        @@: Should this produce a more structured set of messages, so
0221        that messages could be unpacked into a rendered form to see
0222        the placement of all the messages?  Well, probably so.
0223        """
0224        msgs = self._messages.copy()
0225        for v in self.subvalidators():
0226            inner = v.all_messages()
0227            for key, msg in inner:
0228                if key in msgs:
0229                    if msgs[key] == msg:
0230                        continue
0231                    if isinstance(msgs[key], list):
0232                        msgs[key].append(msg)
0233                    else:
0234                        msgs[key] = [msgs[key], msg]
0235                else:
0236                    msgs[key] = msg
0237        return msgs
0238
0239    def subvalidators(self):
0240        """
0241        Return any validators that this validator contains.  This is
0242        not useful for functional, except to inspect what values are
0243        available.  Specifically the ``.all_messages()`` method uses
0244        this to accumulate all possible messages.
0245        """
0246        return []
0247
0248    #@classmethod
0249    def _initialize_docstring(cls):
0250        """
0251        This changes the class's docstring to include information
0252        about all the messages this validator uses.
0253        """
0254        doc = cls.__doc__ or ''
0255        doc = [textwrap.dedent(doc).rstrip()]
0256        messages = cls._messages.items()
0257        messages.sort()
0258        doc.append('\n\n**Messages**\n\n')
0259        for name, default in messages:
0260            default = re.sub(r'(%\(.*?\)[rsifcx])', r'``\1``', default)
0261            doc.append('``'+name+'``:\n')
0262            doc.append('  '+default+'\n\n')
0263        cls.__doc__ = ''.join(doc)
0264    _initialize_docstring = classmethod(_initialize_docstring)
0265
0266class _Identity(Validator):
0267    def __repr__(self):
0268        return 'validators.Identity'
0269Identity = _Identity()
0270
0271class FancyValidator(Validator):
0272
0273    """
0274    FancyValidator is the (abstract) superclass for various validators
0275    and converters.  A subclass can validate, convert, or do both.
0276    There is no formal distinction made here.
0277
0278    Validators have two important external methods:
0279    
0280    * .to_python(value, state):
0281      Attempts to convert the value.  If there is a problem, or the
0282      value is not valid, an Invalid exception is raised.  The
0283      argument for this exception is the (potentially HTML-formatted)
0284      error message to give the user.
0285
0286    * .from_python(value, state):
0287      Reverses to_python.
0288
0289    There are five important methods for subclasses to override,
0290    however none of these *have* to be overridden, only the ones that
0291    are appropriate for the validator:
0292    
0293    * __init__():
0294      if the `declarative.Declarative` model doesn't work for this.
0295
0296    * .validate_python(value, state):
0297      This should raise an error if necessary.  The value is a Python
0298      object, either the result of to_python, or the input to
0299      from_python.
0300
0301    * .validate_other(value, state):
0302      Validates the source, before to_python, or after from_python.
0303      It's more common to use `.validate_python()` however.
0304
0305    * ._to_python(value, state):
0306      This returns the converted value, or raises an Invalid
0307      exception if there is an error.  The argument to this exception
0308      should be the error message.
0309
0310    * ._from_python(value, state):
0311      Should undo .to_python() in some reasonable way, returning
0312      a string.
0313
0314    Validators should have no internal state besides the
0315    values given at instantiation.  They should be reusable and
0316    reentrant.
0317
0318    All subclasses can take the arguments/instance variables:
0319    
0320    * if_empty:
0321      If set, then this value will be returned if the input evaluates
0322      to false (empty list, empty string, None, etc), but not the 0 or
0323      False objects.  This only applies to ``.to_python()``.
0324      
0325    * not_empty:
0326      If true, then if an empty value is given raise an error.
0327      (Both with ``.to_python()`` and also ``.from_python()``
0328      if ``.validate_python`` is true).
0329
0330    * strip:
0331      If true and the input is a string, strip it (occurs before empty
0332      tests).
0333
0334    * if_invalid:
0335      If set, then when this validator would raise Invalid during
0336      ``.to_python()``, instead return this value.
0337      
0338    * if_invalid_python:
0339      If set, when the Python value (converted with
0340      ``.from_python()``) is invalid, this value will be returned.
0341
0342    * accept_python:
0343      If True (the default), then ``.validate_python()`` and
0344      ``.validate_other()`` will not be called when
0345      ``.from_python()`` is used.
0346    """
0347
0348    if_invalid = NoDefault
0349    if_invalid_python = NoDefault
0350    if_empty = NoDefault
0351    not_empty = False
0352    accept_python = True
0353    strip = False
0354
0355    messages = {
0356        'empty': _("Please enter a value"),
0357        'badType': _("The input must be a string (not a %(type)s: %(value)r)"),
0358        'noneType': _("The input must be a string (not None)"),
0359        }
0360
0361    def to_python(self, value, state=None):
0362        try:
0363            if self.strip and isinstance(value, (str, unicode)):
0364                value = value.strip()
0365            elif hasattr(value, 'mixed'):
0366                # Support Paste's MultiDict
0367                value = value.mixed()
0368            if self.is_empty(value):
0369                if self.not_empty:
0370                    raise Invalid(self.message('empty', state), value, state)
0371                else:
0372                    if self.if_empty is not NoDefault:
0373                        return self.if_empty
0374                    else:
0375                        return self.empty_value(value)
0376            vo = self.validate_other
0377            if vo and vo is not self._validate_noop:
0378                vo(value, state)
0379            tp = self._to_python
0380            if tp:
0381                value = tp(value, state)
0382            vp = self.validate_python
0383            if vp and vp is not self._validate_noop:
0384                vp(value, state)
0385            return value
0386        except Invalid:
0387            if self.if_invalid is NoDefault:
0388                raise
0389            else:
0390                return self.if_invalid
0391
0392    def from_python(self, value, state=None):
0393        try:
0394            if self.strip and isinstance(value, (str, unicode)):
0395                value = value.strip()
0396            if not self.accept_python:
0397                if self.is_empty(value):
0398                    if self.not_empty:
0399                        raise Invalid(self.message('empty', state),
0400                                      value, state)
0401                    else:
0402                        return self.empty_value(value)
0403                vp = self.validate_python
0404                if vp and vp is not self._validate_noop:
0405                    vp(value, state)
0406                fp = self._from_python
0407                if fp:
0408                    value = fp(value, state)
0409                vo = self.validate_other
0410                if vo and vo is not self._validate_noop:
0411                    vo(value, state)
0412                return value
0413            else:
0414                if self.is_empty(value):
0415                    return self.empty_value(value)
0416                fp = self._from_python
0417                if fp:
0418                    value = self._from_python(value, state)
0419                return value
0420        except Invalid:
0421            if self.if_invalid_python is NoDefault:
0422                raise
0423            else:
0424                return self.if_invalid_python
0425
0426    def is_empty(self, value):
0427        # None and '' are "empty"
0428        return value is None or value == '' or (
0429            isinstance(value, (list, tuple, dict)) and not value)
0430
0431    def empty_value(self, value):
0432        return None
0433
0434    def assert_string(self, value, state):
0435        if not isinstance(value, (str, unicode)):
0436            raise Invalid(self.message('badType', state,
0437                                       type=type(value), value=value),
0438                          value, state)
0439
0440    def base64encode(self, value):
0441        """
0442        Encode a string in base64, stripping whitespace and removing
0443        newlines.
0444        """
0445        return value.encode('base64').strip().replace('\n', '')
0446
0447    def _validate_noop(self, value, state):
0448        """
0449        A validation method that doesn't do anything.
0450        """
0451        pass
0452
0453    validate_python = validate_other = _validate_noop
0454    _to_python = None
0455    _from_python = None