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