1"""
2Core classes for validation.
3"""
4
5from . import declarative
6import gettext
7import os
8import re
9import textwrap
10import warnings
11
12try:
13    from pkg_resources import resource_filename
14except ImportError:
15    resource_filename = None
16
17__all__ = ['NoDefault', 'Invalid', 'Validator', 'Identity',
18           'FancyValidator', 'is_empty', 'is_validator']
19
20
21def get_localedir():
22    """Retrieve the location of locales.
23
24    If we're built as an egg, we need to find the resource within the egg.
25    Otherwise, we need to look for the locales on the filesystem or in the
26    system message catalog.
27
28    """
29    locale_dir = ''
30    # Check the egg first
31    if resource_filename is not None:
32        try:
33            locale_dir = resource_filename(__name__, "/i18n")
34        except NotImplementedError:
35            # resource_filename doesn't work with non-egg zip files
36            pass
37    if not hasattr(os, 'access'):
38        # This happens on Google App Engine
39        return os.path.join(os.path.dirname(__file__), 'i18n')
40    if os.access(locale_dir, os.R_OK | os.X_OK):
41        # If the resource is present in the egg, use it
42        return locale_dir
43
44    # Otherwise, search the filesystem
45    locale_dir = os.path.join(os.path.dirname(__file__), 'i18n')
46    if not os.access(locale_dir, os.R_OK | os.X_OK):
47        # Fallback on the system catalog
48        locale_dir = os.path.normpath('/usr/share/locale')
49
50    return locale_dir
51
52
53def set_stdtranslation(domain="FormEncode", languages=None,
54                       localedir=get_localedir()):
55
56    t = gettext.translation(domain=domain,
57                            languages=languages,
58                            localedir=localedir, fallback=True)
59    global _stdtrans
60    try:
61        _stdtrans = t.ugettext
62    except AttributeError:  # Python 3
63        _stdtrans = t.gettext
64
65set_stdtranslation()
66
67# Dummy i18n translation function, nothing is translated here.
68# Instead this is actually done in api.Validator.message.
69# The surrounding _('string') of the strings is only for extracting
70# the strings automatically.
71# If you run pygettext with this source comment this function out temporarily.
72_ = lambda s: s
73
74
75def deprecation_warning(old, new=None, stacklevel=3):
76    """Show a deprecation warning."""
77    msg = '%s is deprecated' % old
78    if new:
79        msg += '; use %s instead' % new
80    warnings.warn(msg, DeprecationWarning, stacklevel=stacklevel)
81
82
83def deprecated(old=None, new=None):
84    """A decorator which can be used to mark functions as deprecated."""
85    def outer(func):
86        def inner(*args, **kwargs):
87            deprecation_warning(old or func.__name__, new)
88            return func(*args, **kwargs)
89        return inner
90    return outer
91
92
93class NoDefault(object):
94    """A dummy value used for parameters with no default."""
95
96
97def is_empty(value):
98    """Check whether the given value should be considered "empty"."""
99    return value is None or value == '' or (
100        isinstance(value, (list, tuple, dict)) and not value)
101
102
103def is_validator(obj):
104    """Check whether obj is a Validator instance or class."""
105    return (isinstance(obj, Validator) or
106        (isinstance(obj, type) and issubclass(obj, Validator)))
107
108
109class Invalid(Exception):
110
111    """
112    This is raised in response to invalid input.  It has several
113    public attributes:
114
115    ``msg``:
116        The message, *without* values substituted.  For instance, if
117        you want HTML quoting of values, you can apply that.
118    ``substituteArgs``:
119        The arguments (a dictionary) to go with ``msg``.
120    ``str(self)``:
121        The message describing the error, with values substituted.
122    ``value``:
123        The offending (invalid) value.
124    ``state``:
125        The state that went with this validator.  This is an
126        application-specific object.
127    ``error_list``:
128        If this was a compound validator that takes a repeating value,
129        and sub-validator(s) had errors, then this is a list of those
130        exceptions.  The list will be the same length as the number of
131        values -- valid values will have None instead of an exception.
132    ``error_dict``:
133        Like ``error_list``, but for dictionary compound validators.
134    """
135
136    def __init__(self, msg,
137                 value, state, error_list=None, error_dict=None):
138        Exception.__init__(self, msg, value, state, error_list, error_dict)
139        self.msg = msg
140        self.value = value
141        self.state = state
142        self.error_list = error_list
143        self.error_dict = error_dict
144        assert (not self.error_list or not self.error_dict), (
145                "Errors shouldn't have both error dicts and lists "
146                "(error %s has %s and %s)"
147                % (self, self.error_list, self.error_dict))
148
149    def __str__(self):
150        val = self.msg
151        return val
152
153    if unicode is not str:  # Python 2
154
155        def __unicode__(self):
156            if isinstance(self.msg, unicode):
157                return self.msg
158            elif isinstance(self.msg, str):
159                return self.msg.decode('utf8')
160            else:
161                return unicode(self.msg)
162
163    def unpack_errors(self, encode_variables=False, dict_char='.',
164                      list_char='-'):
165        """
166        Returns the error as a simple data structure -- lists,
167        dictionaries, and strings.
168
169        If ``encode_variables`` is true, then this will return a flat
170        dictionary, encoded with variable_encode
171        """
172        if self.error_list:
173            assert not encode_variables, (
174                "You can only encode dictionary errors")
175            assert not self.error_dict
176            return [item.unpack_errors() if item else item
177                for item in self.error_list]
178        if self.error_dict:
179            result = {}
180            for name, item in self.error_dict.iteritems():
181                result[name] = item if isinstance(
182                    item, basestring) else item.unpack_errors()
183            if encode_variables:
184                from . import variabledecode
185                result = variabledecode.variable_encode(
186                    result, add_repetitions=False,
187                    dict_char=dict_char, list_char=list_char)
188                for key in result.keys():
189                    if not result[key]:
190                        del result[key]
191            return result
192        assert not encode_variables, (
193            "You can only encode dictionary errors")
194        return self.msg
195
196
197############################################################
198## Base Classes
199############################################################
200
201class Validator(declarative.Declarative):
202
203    """
204    The base class of most validators.  See ``IValidator`` for more, and
205    ``FancyValidator`` for the more common (and more featureful) class.
206    """
207
208    _messages = {}
209    if_missing = NoDefault
210    repeating = False
211    compound = False
212    accept_iterator = False
213    gettextargs = {}
214    # In case you don't want to use __builtins__._
215    # although it may be defined, set use_builtins_gettext to False:
216    use_builtins_gettext = True
217
218    __singletonmethods__ = (
219        'to_python', 'from_python', 'message', 'all_messages', 'subvalidators')
220
221    @staticmethod
222    def __classinit__(cls, new_attrs):
223        if 'messages' in new_attrs:
224            cls._messages = cls._messages.copy()
225            cls._messages.update(cls.messages)
226            del cls.messages
227        cls._initialize_docstring()
228
229    def __init__(self, *args, **kw):
230        if 'messages' in kw:
231            self._messages = self._messages.copy()
232            self._messages.update(kw.pop('messages'))
233        declarative.Declarative.__init__(self, *args, **kw)
234
235    def to_python(self, value, state=None):
236        return value
237
238    def from_python(self, value, state=None):
239        return value
240
241    def message(self, msgName, state, **kw):
242        # determine translation function
243        try:
244            trans = state._
245        except AttributeError:
246            try:
247                if self.use_builtins_gettext:
248                    import __builtin__
249                    trans = __builtin__._
250                else:
251                    trans = _stdtrans
252            except AttributeError:
253                trans = _stdtrans
254
255        if not callable(trans):
256            trans = _stdtrans
257
258        msg = self._messages[msgName]
259        msg = trans(msg, **self.gettextargs)
260
261        try:
262            return msg % kw
263        except KeyError as e:
264            raise KeyError(
265                "Key not found (%s) for %r=%r %% %r (from: %s)"
266                % (e, msgName, self._messages.get(msgName), kw,
267                   ', '.join(self._messages)))
268
269    def all_messages(self):
270        """
271        Return a dictionary of all the messages of this validator, and
272        any subvalidators if present.  Keys are message names, values
273        may be a message or list of messages.  This is really just
274        intended for documentation purposes, to show someone all the
275        messages that a validator or compound validator (like Schemas)
276        can produce.
277
278        @@: Should this produce a more structured set of messages, so
279        that messages could be unpacked into a rendered form to see
280        the placement of all the messages?  Well, probably so.
281        """
282        msgs = self._messages.copy()
283        for v in self.subvalidators():
284            inner = v.all_messages()
285            for key, msg in inner:
286                if key in msgs:
287                    if msgs[key] == msg:
288                        continue
289                    if isinstance(msgs[key], list):
290                        msgs[key].append(msg)
291                    else:
292                        msgs[key] = [msgs[key], msg]
293                else:
294                    msgs[key] = msg
295        return msgs
296
297    def subvalidators(self):
298        """
299        Return any validators that this validator contains.  This is
300        not useful for functional, except to inspect what values are
301        available.  Specifically the ``.all_messages()`` method uses
302        this to accumulate all possible messages.
303        """
304        return []
305
306    @classmethod
307    def _initialize_docstring(cls):
308        """
309        This changes the class's docstring to include information
310        about all the messages this validator uses.
311        """
312        doc = cls.__doc__ or ''
313        doc = [textwrap.dedent(doc).rstrip()]
314        messages = sorted(cls._messages.iteritems())
315        doc.append('\n\n**Messages**\n\n')
316        for name, default in messages:
317            default = re.sub(r'(%\(.*?\)[rsifcx])', r'``\1``', default)
318            doc.append('``' + name + '``:\n')
319            doc.append('  ' + default + '\n\n')
320        cls.__doc__ = ''.join(doc)
321
322
323class _Identity(Validator):
324
325    def __repr__(self):
326        return 'validators.Identity'
327
328Identity = _Identity()
329
330
331class FancyValidator(Validator):
332
333    """
334    FancyValidator is the (abstract) superclass for various validators
335    and converters.  A subclass can validate, convert, or do both.
336    There is no formal distinction made here.
337
338    Validators have two important external methods:
339
340    ``.to_python(value, state)``:
341      Attempts to convert the value.  If there is a problem, or the
342      value is not valid, an Invalid exception is raised.  The
343      argument for this exception is the (potentially HTML-formatted)
344      error message to give the user.
345
346    ``.from_python(value, state)``:
347      Reverses ``.to_python()``.
348
349    These two external methods make use of the following four
350    important internal methods that can be overridden.  However,
351    none of these *have* to be overridden, only the ones that
352    are appropriate for the validator.
353
354    ``._convert_to_python(value, state)``:
355      This method converts the source to a Python value.  It returns
356      the converted value, or raises an Invalid exception if the
357      conversion cannot be done.  The argument to this exception
358      should be the error message.  Contrary to ``.to_python()`` it is
359      only meant to convert the value, not to fully validate it.
360
361    ``._convert_from_python(value, state)``:
362      Should undo ``._convert_to_python()`` in some reasonable way, returning
363      a string.
364
365    ``._validate_other(value, state)``:
366      Validates the source, before ``._convert_to_python()``, or after
367      ``._convert_from_python()``.  It's usually more convenient to use
368      ``._validate_python()`` however.
369
370    ``._validate_python(value, state)``:
371      Validates a Python value, either the result of ``._convert_to_python()``,
372      or the input to ``._convert_from_python()``.
373
374    You should make sure that all possible validation errors are
375    raised in at least one these four methods, not matter which.
376
377    Subclasses can also override the ``__init__()`` method
378    if the ``declarative.Declarative`` model doesn't work for this.
379
380    Validators should have no internal state besides the
381    values given at instantiation.  They should be reusable and
382    reentrant.
383
384    All subclasses can take the arguments/instance variables:
385
386    ``if_empty``:
387      If set, then this value will be returned if the input evaluates
388      to false (empty list, empty string, None, etc), but not the 0 or
389      False objects.  This only applies to ``.to_python()``.
390
391    ``not_empty``:
392      If true, then if an empty value is given raise an error.
393      (Both with ``.to_python()`` and also ``.from_python()``
394      if ``._validate_python`` is true).
395
396    ``strip``:
397      If true and the input is a string, strip it (occurs before empty
398      tests).
399
400    ``if_invalid``:
401      If set, then when this validator would raise Invalid during
402      ``.to_python()``, instead return this value.
403
404    ``if_invalid_python``:
405      If set, when the Python value (converted with
406      ``.from_python()``) is invalid, this value will be returned.
407
408    ``accept_python``:
409      If True (the default), then ``._validate_python()`` and
410      ``._validate_other()`` will not be called when
411      ``.from_python()`` is used.
412
413    These parameters are handled at the level of the external
414    methods ``.to_python()`` and ``.from_python`` already;
415    if you overwrite one of the internal methods, you usually
416    don't need to care about them.
417
418    """
419
420    if_invalid = NoDefault
421    if_invalid_python = NoDefault
422    if_empty = NoDefault
423    not_empty = False
424    accept_python = True
425    strip = False
426
427    messages = dict(
428        empty=_("Please enter a value"),
429        badType=_("The input must be a string (not a %(type)s: %(value)r)"),
430        noneType=_("The input must be a string (not None)"))
431
432    _inheritance_level = 0
433    _deprecated_methods = (
434        ('_to_python', '_convert_to_python'),
435        ('_from_python', '_convert_from_python'),
436        ('validate_python', '_validate_python'),
437        ('validate_other', '_validate_other'))
438
439    @staticmethod
440    def __classinit__(cls, new_attrs):
441        Validator.__classinit__(cls, new_attrs)
442        # account for deprecated methods
443        cls._inheritance_level += 1
444        if '_deprecated_methods' in new_attrs:
445            cls._deprecated_methods = cls._deprecated_methods + new_attrs[
446                '_deprecated_methods']
447        for old, new in cls._deprecated_methods:
448            if old in new_attrs:
449                if new not in new_attrs:
450                    deprecation_warning(old, new,
451                        stacklevel=cls._inheritance_level + 2)
452                    setattr(cls, new, new_attrs[old])
453            elif new in new_attrs:
454                    setattr(cls, old, deprecated(old=old, new=new)(
455                        new_attrs[new]))
456
457    def to_python(self, value, state=None):
458        try:
459            if self.strip and isinstance(value, basestring):
460                value = value.strip()
461            elif hasattr(value, 'mixed'):
462                # Support Paste's MultiDict
463                value = value.mixed()
464            if self.is_empty(value):
465                if self.not_empty:
466                    raise Invalid(self.message('empty', state), value, state)
467                if self.if_empty is not NoDefault:
468                    return self.if_empty
469                return self.empty_value(value)
470            vo = self._validate_other
471            if vo and vo is not self._validate_noop:
472                vo(value, state)
473            tp = self._convert_to_python
474            if tp:
475                value = tp(value, state)
476            vp = self._validate_python
477            if vp and vp is not self._validate_noop:
478                vp(value, state)
479        except Invalid:
480            value = self.if_invalid
481            if value is NoDefault:
482                raise
483        return value
484
485    def from_python(self, value, state=None):
486        try:
487            if self.strip and isinstance(value, basestring):
488                value = value.strip()
489            if not self.accept_python:
490                if self.is_empty(value):
491                    if self.not_empty:
492                        raise Invalid(self.message('empty', state),
493                                      value, state)
494                    return self.empty_value(value)
495                vp = self._validate_python
496                if vp and vp is not self._validate_noop:
497                    vp(value, state)
498                fp = self._convert_from_python
499                if fp:
500                    value = fp(value, state)
501                vo = self._validate_other
502                if vo and vo is not self._validate_noop:
503                    vo(value, state)
504            else:
505                if self.is_empty(value):
506                    return self.empty_value(value)
507                fp = self._convert_from_python
508                if fp:
509                    value = fp(value, state)
510        except Invalid:
511            value = self.if_invalid_python
512            if value is NoDefault:
513                raise
514        return value
515
516    def is_empty(self, value):
517        return is_empty(value)
518
519    def empty_value(self, value):
520        return None
521
522    def assert_string(self, value, state):
523        if not isinstance(value, basestring):
524            raise Invalid(self.message('badType', state,
525                                       type=type(value), value=value),
526                          value, state)
527
528    def base64encode(self, value):
529        """
530        Encode a string in base64, stripping whitespace and removing
531        newlines.
532        """
533        return value.encode('base64').strip().replace('\n', '')
534
535    def _validate_noop(self, value, state=None):
536        """
537        A validation method that doesn't do anything.
538        """
539        pass
540
541    _validate_python = _validate_other = _validate_noop
542    _convert_to_python = _convert_from_python = None
543