Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/FormEncode/formencode/api.py
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')
0161
0162    def __classinit__(cls, new_attrs):
0163        if new_attrs.has_key('messages'):
0164            cls._messages = cls._messages.copy()
0165            cls._messages.update(cls.messages)
0166            del cls.messages
0167        cls._initialize_docstring()
0168
0169    def __init__(self, *args, **kw):
0170        if kw.has_key('messages'):
0171            self._messages = self._messages.copy()
0172            self._messages.update(kw['messages'])
0173            del kw['messages']
0174        declarative.Declarative.__init__(self, *args, **kw)
0175
0176    def to_python(self, value, state=None):
0177        return value
0178
0179    def from_python(self, value, state=None):
0180        return value
0181
0182    def message(self, msgName, state, **kw):
0183        #determine translation function
0184        try:
0185            trans = state._
0186        except AttributeError:
0187            try:
0188                if self.use_builtins_gettext:
0189                    import __builtin__
0190                    trans = __builtin__._
0191
0192                else:
0193                    trans = _stdtrans
0194
0195            except AttributeError:
0196                trans = _stdtrans
0197
0198        if not callable(trans):
0199            trans = _stdtrans
0200
0201
0202        try:
0203            return trans(self._messages[msgName], **self.gettextargs) % kw
0204        except KeyError, e:
0205            raise KeyError(
0206                "Key not found (%s) for %r=%r %% %r (from: %s)"
0207                % (e, msgName, self._messages.get(msgName), kw,
0208                   ', '.join(self._messages.keys())))
0209
0210    def all_messages(self):
0211        """
0212        Return a dictionary of all the messages of this validator, and
0213        any subvalidators if present.  Keys are message names, values
0214        may be a message or list of messages.  This is really just
0215        intended for documentation purposes, to show someone all the
0216        messages that a validator or compound validator (like Schemas)
0217        can produce.
0218
0219        @@: Should this produce a more structured set of messages, so
0220        that messages could be unpacked into a rendered form to see
0221        the placement of all the messages?  Well, probably so.
0222        """
0223        msgs = self._messages.copy()
0224        for v in self.subvalidators():
0225            inner = v.all_messages()
0226            for key, msg in inner:
0227                if key in msgs:
0228                    if msgs[key] == msg:
0229                        continue
0230                    if isinstance(msgs[key], list):
0231                        msgs[key].append(msg)
0232                    else:
0233                        msgs[key] = [msgs[key], msg]
0234                else:
0235                    msgs[key] = msg
0236        return msgs
0237
0238    def subvalidators(self):
0239        """
0240        Return any validators that this validator contains.  This is
0241        not useful for functional, except to inspect what values are
0242        available.  Specifically the ``.all_messages()`` method uses
0243        this to accumulate all possible messages.
0244        """
0245        return []
0246
0247    #@classmethod
0248    def _initialize_docstring(cls):
0249        """
0250        This changes the class's docstring to include information
0251        about all the messages this validator uses.
0252        """
0253        doc = cls.__doc__ or ''
0254        doc = [textwrap.dedent(doc).rstrip()]
0255        messages = cls._messages.items()
0256        messages.sort()
0257        doc.append('\n\n**Messages**\n\n')
0258        for name, default in messages:
0259            default = re.sub(r'(%\(.*?\)[rsifcx])', r'``\1``', default)
0260            doc.append('``'+name+'``:\n')
0261            doc.append('  '+default+'\n\n')
0262        cls.__doc__ = ''.join(doc)
0263    _initialize_docstring = classmethod(_initialize_docstring)
0264
0265class _Identity(Validator):
0266    def __repr__(self):
0267        return 'validators.Identity'
0268Identity = _Identity()
0269
0270class FancyValidator(Validator):
0271
0272    """
0273    FancyValidator is the (abstract) superclass for various validators
0274    and converters.  A subclass can validate, convert, or do both.
0275    There is no formal distinction made here.
0276
0277    Validators have two important external methods:
0278    
0279    * .to_python(value, state):
0280      Attempts to convert the value.  If there is a problem, or the
0281      value is not valid, an Invalid exception is raised.  The
0282      argument for this exception is the (potentially HTML-formatted)
0283      error message to give the user.
0284
0285    * .from_python(value, state):
0286      Reverses to_python.
0287
0288    There are five important methods for subclasses to override,
0289    however none of these *have* to be overridden, only the ones that
0290    are appropriate for the validator:
0291    
0292    * __init__():
0293      if the `declarative.Declarative` model doesn't work for this.
0294
0295    * .validate_python(value, state):
0296      This should raise an error if necessary.  The value is a Python
0297      object, either the result of to_python, or the input to
0298      from_python.
0299
0300    * .validate_other(value, state):
0301      Validates the source, before to_python, or after from_python.
0302      It's more common to use `.validate_python()` however.
0303
0304    * ._to_python(value, state):
0305      This returns the converted value, or raises an Invalid
0306      exception if there is an error.  The argument to this exception
0307      should be the error message.
0308
0309    * ._from_python(value, state):
0310      Should undo .to_python() in some reasonable way, returning
0311      a string.
0312
0313    Validators should have no internal state besides the
0314    values given at instantiation.  They should be reusable and
0315    reentrant.
0316
0317    All subclasses can take the arguments/instance variables:
0318    
0319    * if_empty:
0320      If set, then this value will be returned if the input evaluates
0321      to false (empty list, empty string, None, etc), but not the 0 or
0322      False objects.  This only applies to ``.to_python()``.
0323      
0324    * not_empty:
0325      If true, then if an empty value is given raise an error.
0326      (Both with ``.to_python()`` and also ``.from_python()``
0327      if ``.validate_python`` is true).
0328
0329    * strip:
0330      If true and the input is a string, strip it (occurs before empty
0331      tests).
0332
0333    * if_invalid:
0334      If set, then when this validator would raise Invalid during
0335      ``.to_python()``, instead return this value.
0336      
0337    * if_invalid_python:
0338      If set, when the Python value (converted with
0339      ``.from_python()``) is invalid, this value will be returned.
0340
0341    * accept_python:
0342      If True (the default), then ``.validate_python()`` and
0343      ``.validate_other()`` will not be called when
0344      ``.from_python()`` is used.
0345    """
0346
0347    if_invalid = NoDefault
0348    if_invalid_python = NoDefault
0349    if_empty = NoDefault
0350    not_empty = False
0351    accept_python = True
0352    strip = False
0353
0354    messages = {
0355        'empty': _("Please enter a value"),
0356        'badType': _("The input must be a string (not a %(type)s: %(value)r)"),
0357        'noneType': _("The input must be a string (not None)"),
0358        }
0359
0360    def to_python(self, value, state=None):
0361        try:
0362            if self.strip and isinstance(value, (str, unicode)):
0363                value = value.strip()
0364            elif hasattr(value, 'mixed'):
0365                # Support Paste's MultiDict
0366                value = value.mixed()
0367            if self.is_empty(value):
0368                if self.not_empty:
0369                    raise Invalid(self.message('empty', state), value, state)
0370                else:
0371                    if self.if_empty is not NoDefault:
0372                        return self.if_empty
0373                    else:
0374                        return self.empty_value(value)
0375            vo = self.validate_other
0376            if vo and vo is not self._validate_noop:
0377                vo(value, state)
0378            tp = self._to_python
0379            if tp:
0380                value = tp(value, state)
0381            vp = self.validate_python
0382            if vp and vp is not self._validate_noop:
0383                vp(value, state)
0384            return value
0385        except Invalid:
0386            if self.if_invalid is NoDefault:
0387                raise
0388            else:
0389                return self.if_invalid
0390
0391    def from_python(self, value, state=None):
0392        try:
0393            if self.strip and isinstance(value, (str, unicode)):
0394                value = value.strip()
0395            if not self.accept_python:
0396                if self.is_empty(value):
0397                    if self.not_empty:
0398                        raise Invalid(self.message('empty', state),
0399                                      value, state)
0400                    else:
0401                        return self.empty_value(value)
0402                vp = self.validate_python
0403                if vp and vp is not self._validate_noop:
0404                    vp(value, state)
0405                fp = self._from_python
0406                if fp:
0407                    value = fp(value, state)
0408                vo = self.validate_other
0409                if vo and co is not self._validate_noop:
0410                    vo(value, state)
0411                return value
0412            else:
0413                if self.is_empty(value):
0414                    return self.empty_value(value)
0415                fp = self._from_python
0416                if fp:
0417                    value = self._from_python(value, state)
0418                return value
0419        except Invalid:
0420            if self.if_invalid_python is NoDefault:
0421                raise
0422            else:
0423                return self.if_invalid_python
0424
0425    def is_empty(self, value):
0426        # None and '' are "empty"
0427        return value is None or value == ''
0428
0429    def empty_value(self, value):
0430        return None
0431
0432    def assert_string(self, value, state):
0433        if not isinstance(value, (str, unicode)):
0434            raise Invalid(self.message('badType', state,
0435                                       type=type(value), value=value),
0436                          value, state)
0437
0438    def base64encode(self, value):
0439        """
0440        Encode a string in base64, stripping whitespace and removing
0441        newlines.
0442        """
0443        return value.encode('base64').strip().replace('\n', '')
0444
0445    def _validate_noop(self, value, state):
0446        """
0447        A validation method that doesn't do anything.
0448        """
0449        pass
0450
0451    validate_python = validate_other = _validate_noop
0452    _to_python = None
0453    _from_python = None

Top