formencode.api
Covered: 131 lines
Missed: 265 lines
Skipped 93 lines
Percent: 33 %
  1
"""
  2
Core classes for validation.
  3
"""
  5
import declarative
  6
import textwrap
  7
import re
  8
import os
  9
try:
 10
    from pkg_resources import resource_filename
 11
except ImportError:
 12
    resource_filename = None
 14
__all__ = ['NoDefault', 'Invalid', 'Validator', 'Identity',
 15
           'FancyValidator', 'is_validator']
 17
import gettext
 19
def get_localedir():
 20
    """
 21
    Retrieve the location of locales.
 23
    If we're built as an egg, we need to find the resource within the egg.
 24
    Otherwise, we need to look for the locales on the filesystem or in the
 25
    system message catalog.
 26
    """
 27
    locale_dir = ''
 29
    if resource_filename is not None:
 30
        try:
 31
            locale_dir = resource_filename(__name__, "/i18n")
 32
        except NotImplementedError:
 34
            pass
 35
    if not hasattr(os, 'access'):
 37
        return os.path.join(os.path.dirname(__file__), 'i18n')
 38
    if os.access(locale_dir, os.R_OK | os.X_OK):
 40
        return locale_dir
 43
    locale_dir = os.path.join(os.path.dirname(__file__), 'i18n')
 44
    if not os.access(locale_dir, os.R_OK | os.X_OK):
 46
        locale_dir = os.path.normpath('/usr/share/locale')
 48
    return locale_dir
 50
def set_stdtranslation(domain="FormEncode", languages=None, \
 51
                       localedir = get_localedir()):
 53
    t = gettext.translation(domain=domain, \
 54
                            languages=languages, \
 55
                            localedir=localedir, fallback=True)
 56
    global _stdtrans
 57
    _stdtrans = t.ugettext
 59
set_stdtranslation()
 61
def _(s): return s # dummy i18n translation function, nothing is translated here.
 67
class NoDefault:
 68
    pass
 70
def is_validator(obj):
 71
    return (isinstance(obj, Validator) or
 72
            (isinstance(obj, type) and
 73
             issubclass(obj, Validator)))
 75
class Invalid(Exception):
 77
    """
 78
    This is raised in response to invalid input.  It has several
 79
    public attributes:
 81
    msg:
 82
        The message, *without* values substituted.  For instance, if
 83
        you want HTML quoting of values, you can apply that.
 84
    substituteArgs:
 85
        The arguments (a dictionary) to go with `msg`.
 86
    str(self):
 87
        The message describing the error, with values substituted.
 88
    value:
 89
        The offending (invalid) value.
 90
    state:
 91
        The state that went with this validator.  This is an
 92
        application-specific object.
 93
    error_list:
 94
        If this was a compound validator that takes a repeating value,
 95
        and sub-validator(s) had errors, then this is a list of those
 96
        exceptions.  The list will be the same length as the number of
 97
        values -- valid values will have None instead of an exception.
 98
    error_dict:
 99
        Like `error_list`, but for dictionary compound validators.
100
    """
102
    def __init__(self, msg,
103
                 value, state, error_list=None, error_dict=None):
104
        Exception.__init__(self, msg)
105
        self.msg = msg
106
        self.value = value
107
        self.state = state
108
        self.error_list = error_list
109
        self.error_dict = error_dict
110
        assert (not self.error_list or not self.error_dict), (
111
                "Errors shouldn't have both error dicts and lists "
112
                "(error %s has %s and %s)"
113
                % (self, self.error_list, self.error_dict))
115
    def __str__(self):
116
        val = self.msg
119
        return val    
121
    def __unicode__(self):
122
        if isinstance(self.msg, unicode):
123
            return self.msg
124
        elif isinstance(self.msg, str):
125
            return self.msg.decode('utf8')
126
        else:
127
            return unicode(self.msg)
129
    def unpack_errors(self, encode_variables=False, dict_char='.',
130
                      list_char='-'):
131
        """
132
        Returns the error as a simple data structure -- lists,
133
        dictionaries, and strings.
135
        If ``encode_variables`` is true, then this will return a flat
136
        dictionary, encoded with variable_encode
137
        """
138
        if self.error_list:
139
            assert not encode_variables, (
140
                "You can only encode dictionary errors")
141
            assert not self.error_dict
142
            result = []
143
            for item in self.error_list:
144
                if not item:
145
                    result.append(item)
146
                else:
147
                    result.append(item.unpack_errors())
148
            return result
149
        elif self.error_dict:
150
            result = {}
151
            for name, item in self.error_dict.items():
152
                if isinstance(item, (str, unicode)):
153
                    result[name] = item
154
                else:
155
                    result[name] = item.unpack_errors()
156
            if encode_variables:
157
                import variabledecode
158
                result = variabledecode.variable_encode(result, add_repetitions=False,
159
                                                        dict_char=dict_char,
160
                                                        list_char=list_char)
161
                for key in result.keys():
162
                    if not result[key]:
163
                        del result[key]
164
            return result
165
        else:
166
            assert not encode_variables, (
167
                "You can only encode dictionary errors")
168
            return self.msg
175
class Validator(declarative.Declarative):
177
    """
178
    The base class of most validators.  See `IValidator` for more, and
179
    `FancyValidator` for the more common (and more featureful) class.
180
    """
182
    _messages = {}
183
    if_missing = NoDefault
184
    repeating = False
185
    compound = False
186
    gettextargs = {}
187
    use_builtins_gettext = True #In case you dont want to use __builtins__._
190
    __singletonmethods__ = ('to_python', 'from_python', 'message', 'all_messages',
191
                            'subvalidators')
193
    def __classinit__(cls, new_attrs):
194
        if new_attrs.has_key('messages'):
195
            cls._messages = cls._messages.copy()
196
            cls._messages.update(cls.messages)
197
            del cls.messages
198
        cls._initialize_docstring()
200
    def __init__(self, *args, **kw):
201
        if kw.has_key('messages'):
202
            self._messages = self._messages.copy()
203
            self._messages.update(kw['messages'])
204
            del kw['messages']
205
        declarative.Declarative.__init__(self, *args, **kw)
207
    def to_python(self, value, state=None):
208
        return value
210
    def from_python(self, value, state=None):
211
        return value
213
    def message(self, msgName, state, **kw):
215
        try:
216
            trans = state._
217
        except AttributeError:
218
            try:
219
                if self.use_builtins_gettext:
220
                    import __builtin__
221
                    trans = __builtin__._
223
                else:
224
                    trans = _stdtrans
226
            except AttributeError:
227
                trans = _stdtrans
229
        if not callable(trans):
230
            trans = _stdtrans
233
        msg = self._messages[msgName]
234
        msg = trans(msg, **self.gettextargs)
235
        try:
236
            return msg % kw
237
        except KeyError, e:
238
            raise KeyError(
239
                "Key not found (%s) for %r=%r %% %r (from: %s)"
240
                % (e, msgName, self._messages.get(msgName), kw,
241
                   ', '.join(self._messages.keys())))
243
    def all_messages(self):
244
        """
245
        Return a dictionary of all the messages of this validator, and
246
        any subvalidators if present.  Keys are message names, values
247
        may be a message or list of messages.  This is really just
248
        intended for documentation purposes, to show someone all the
249
        messages that a validator or compound validator (like Schemas)
250
        can produce.
252
        @@: Should this produce a more structured set of messages, so
253
        that messages could be unpacked into a rendered form to see
254
        the placement of all the messages?  Well, probably so.
255
        """
256
        msgs = self._messages.copy()
257
        for v in self.subvalidators():
258
            inner = v.all_messages()
259
            for key, msg in inner:
260
                if key in msgs:
261
                    if msgs[key] == msg:
262
                        continue
263
                    if isinstance(msgs[key], list):
264
                        msgs[key].append(msg)
265
                    else:
266
                        msgs[key] = [msgs[key], msg]
267
                else:
268
                    msgs[key] = msg
269
        return msgs
271
    def subvalidators(self):
272
        """
273
        Return any validators that this validator contains.  This is
274
        not useful for functional, except to inspect what values are
275
        available.  Specifically the ``.all_messages()`` method uses
276
        this to accumulate all possible messages.
277
        """
278
        return []
281
    def _initialize_docstring(cls):
282
        """
283
        This changes the class's docstring to include information
284
        about all the messages this validator uses.
285
        """
286
        doc = cls.__doc__ or ''
287
        doc = [textwrap.dedent(doc).rstrip()]
288
        messages = cls._messages.items()
289
        messages.sort()
290
        doc.append('\n\n**Messages**\n\n')
291
        for name, default in messages:
292
            default = re.sub(r'(%\(.*?\)[rsifcx])', r'``\1``', default)
293
            doc.append('``'+name+'``:\n')
294
            doc.append('  '+default+'\n\n')
295
        cls.__doc__ = ''.join(doc)
296
    _initialize_docstring = classmethod(_initialize_docstring)
298
class _Identity(Validator):
299
    def __repr__(self):
300
        return 'validators.Identity'
301
Identity = _Identity()
303
class FancyValidator(Validator):
305
    """
306
    FancyValidator is the (abstract) superclass for various validators
307
    and converters.  A subclass can validate, convert, or do both.
308
    There is no formal distinction made here.
310
    Validators have two important external methods:
312
    * .to_python(value, state):
313
      Attempts to convert the value.  If there is a problem, or the
314
      value is not valid, an Invalid exception is raised.  The
315
      argument for this exception is the (potentially HTML-formatted)
316
      error message to give the user.
318
    * .from_python(value, state):
319
      Reverses to_python.
321
    There are five important methods for subclasses to override,
322
    however none of these *have* to be overridden, only the ones that
323
    are appropriate for the validator:
325
    * __init__():
326
      if the `declarative.Declarative` model doesn't work for this.
328
    * .validate_python(value, state):
329
      This should raise an error if necessary.  The value is a Python
330
      object, either the result of to_python, or the input to
331
      from_python.
333
    * .validate_other(value, state):
334
      Validates the source, before to_python, or after from_python.
335
      It's more common to use `.validate_python()` however.
337
    * ._to_python(value, state):
338
      This returns the converted value, or raises an Invalid
339
      exception if there is an error.  The argument to this exception
340
      should be the error message.
342
    * ._from_python(value, state):
343
      Should undo .to_python() in some reasonable way, returning
344
      a string.
346
    Validators should have no internal state besides the
347
    values given at instantiation.  They should be reusable and
348
    reentrant.
350
    All subclasses can take the arguments/instance variables:
352
    * if_empty:
353
      If set, then this value will be returned if the input evaluates
354
      to false (empty list, empty string, None, etc), but not the 0 or
355
      False objects.  This only applies to ``.to_python()``.
357
    * not_empty:
358
      If true, then if an empty value is given raise an error.
359
      (Both with ``.to_python()`` and also ``.from_python()``
360
      if ``.validate_python`` is true).
362
    * strip:
363
      If true and the input is a string, strip it (occurs before empty
364
      tests).
366
    * if_invalid:
367
      If set, then when this validator would raise Invalid during
368
      ``.to_python()``, instead return this value.
370
    * if_invalid_python:
371
      If set, when the Python value (converted with
372
      ``.from_python()``) is invalid, this value will be returned.
374
    * accept_python:
375
      If True (the default), then ``.validate_python()`` and
376
      ``.validate_other()`` will not be called when
377
      ``.from_python()`` is used.
378
    """
380
    if_invalid = NoDefault
381
    if_invalid_python = NoDefault
382
    if_empty = NoDefault
383
    not_empty = False
384
    accept_python = True
385
    strip = False
387
    messages = {
388
        'empty': _("Please enter a value"),
389
        'badType': _("The input must be a string (not a %(type)s: %(value)r)"),
390
        'noneType': _("The input must be a string (not None)"),
391
        }
393
    def to_python(self, value, state=None):
394
        try:
395
            if self.strip and isinstance(value, (str, unicode)):
396
                value = value.strip()
397
            elif hasattr(value, 'mixed'):
399
                value = value.mixed()
400
            if self.is_empty(value):
401
                if self.not_empty:
402
                    raise Invalid(self.message('empty', state), value, state)
403
                else:
404
                    if self.if_empty is not NoDefault:
405
                        return self.if_empty
406
                    else:
407
                        return self.empty_value(value)
408
            vo = self.validate_other
409
            if vo and vo is not self._validate_noop:
410
                vo(value, state)
411
            tp = self._to_python
412
            if tp:
413
                value = tp(value, state)
414
            vp = self.validate_python
415
            if vp and vp is not self._validate_noop:
416
                vp(value, state)
417
            return value
418
        except Invalid:
419
            if self.if_invalid is NoDefault:
420
                raise
421
            else:
422
                return self.if_invalid
424
    def from_python(self, value, state=None):
425
        try:
426
            if self.strip and isinstance(value, (str, unicode)):
427
                value = value.strip()
428
            if not self.accept_python:
429
                if self.is_empty(value):
430
                    if self.not_empty:
431
                        raise Invalid(self.message('empty', state),
432
                                      value, state)
433
                    else:
434
                        return self.empty_value(value)
435
                vp = self.validate_python
436
                if vp and vp is not self._validate_noop:
437
                    vp(value, state)
438
                fp = self._from_python
439
                if fp:
440
                    value = fp(value, state)
441
                vo = self.validate_other
442
                if vo and vo is not self._validate_noop:
443
                    vo(value, state)
444
                return value
445
            else:
446
                if self.is_empty(value):
447
                    return self.empty_value(value)
448
                fp = self._from_python
449
                if fp:
450
                    value = self._from_python(value, state)
451
                return value
452
        except Invalid:
453
            if self.if_invalid_python is NoDefault:
454
                raise
455
            else:
456
                return self.if_invalid_python
458
    def is_empty(self, value):
460
        return value is None or value == '' or (
461
            isinstance(value, (list, tuple, dict)) and not value)
463
    def empty_value(self, value):
464
        return None
466
    def assert_string(self, value, state):
467
        if not isinstance(value, (str, unicode)):
468
            raise Invalid(self.message('badType', state,
469
                                       type=type(value), value=value),
470
                          value, state)
472
    def base64encode(self, value):
473
        """
474
        Encode a string in base64, stripping whitespace and removing
475
        newlines.
476
        """
477
        return value.encode('base64').strip().replace('\n', '')
479
    def _validate_noop(self, value, state):
480
        """
481
        A validation method that doesn't do anything.
482
        """
483
        pass
485
    validate_python = validate_other = _validate_noop
486
    _to_python = None
487
    _from_python = None