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