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
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
0040
0041
0042
0043
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
0096
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
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
0158
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
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
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
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
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