thirdparty/google_appengine/google/appengine/api/validation.py
changeset 109 620f9b141567
child 149 f2e327a7c5de
equal deleted inserted replaced
108:261778de26ff 109:620f9b141567
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2007 Google Inc.
       
     4 #
       
     5 # Licensed under the Apache License, Version 2.0 (the "License");
       
     6 # you may not use this file except in compliance with the License.
       
     7 # You may obtain a copy of the License at
       
     8 #
       
     9 #     http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    11 # Unless required by applicable law or agreed to in writing, software
       
    12 # distributed under the License is distributed on an "AS IS" BASIS,
       
    13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       
    14 # See the License for the specific language governing permissions and
       
    15 # limitations under the License.
       
    16 #
       
    17 
       
    18 """Validation tools for generic object structures.
       
    19 
       
    20 This library is used for defining classes with constrained attributes.
       
    21 Attributes are defined on the class which contains them using validators.
       
    22 Although validators can be defined by any client of this library, a number
       
    23 of standard validators are provided here.
       
    24 
       
    25 Validators can be any callable that takes a single parameter which checks
       
    26 the new value before it is assigned to the attribute.  Validators are
       
    27 permitted to modify a received value so that it is appropriate for the
       
    28 attribute definition.  For example, using int as a validator will cast
       
    29 a correctly formatted string to a number, or raise an exception if it
       
    30 can not.  This is not recommended, however.  the correct way to use a
       
    31 validator that ensure the correct type is to use the Type validator.
       
    32 
       
    33 This validation library is mainly intended for use with the YAML object
       
    34 builder.  See yaml_object.py.
       
    35 """
       
    36 
       
    37 
       
    38 
       
    39 
       
    40 
       
    41 import re
       
    42 
       
    43 import google
       
    44 import yaml
       
    45 
       
    46 
       
    47 class Error(Exception):
       
    48   """Base class for all package errors."""
       
    49 
       
    50 
       
    51 class AttributeDefinitionError(Error):
       
    52   """An error occurred in the definition of class attributes."""
       
    53 
       
    54 
       
    55 class ValidationError(Error):
       
    56   """Base class for raising exceptions during validation."""
       
    57 
       
    58   def __init__(self, message, cause=None):
       
    59     """Initialize exception."""
       
    60     if hasattr(cause, 'args'):
       
    61       Error.__init__(self, message, *cause.args)
       
    62     else:
       
    63       Error.__init__(self, message)
       
    64     self.message = message
       
    65     self.cause = cause
       
    66 
       
    67   def __str__(self):
       
    68     return str(self.message)
       
    69 
       
    70 
       
    71 class MissingAttribute(ValidationError):
       
    72   """Raised when a required attribute is missing from object."""
       
    73 
       
    74 
       
    75 def AsValidator(validator):
       
    76   """Wrap various types as instances of a validator.
       
    77 
       
    78   Used to allow shorthand for common validator types.  It
       
    79   converts the following types to the following Validators.
       
    80 
       
    81     strings -> Regex
       
    82     type -> Type
       
    83     collection -> Options
       
    84     Validator -> Its self!
       
    85 
       
    86   Args:
       
    87     validator: Object to wrap in a validator.
       
    88 
       
    89   Returns:
       
    90     Validator instance that wraps the given value.
       
    91 
       
    92   Raises:
       
    93     AttributeDefinitionError if validator is not one of the above described
       
    94     types.
       
    95   """
       
    96   if isinstance(validator, (str, unicode)):
       
    97     return Regex(validator, type(validator))
       
    98   if isinstance(validator, type):
       
    99     return Type(validator)
       
   100   if isinstance(validator, (list, tuple, set)):
       
   101     return Options(*tuple(validator))
       
   102   if isinstance(validator, Validator):
       
   103     return validator
       
   104   else:
       
   105     raise AttributeDefinitionError('%s is not a valid validator' %
       
   106                                    str(validator))
       
   107 
       
   108 
       
   109 class Validated(object):
       
   110   """Base class for other classes that require validation.
       
   111 
       
   112   A class which intends to use validated fields should sub-class itself from
       
   113   this class.  Each class should define an 'ATTRIBUTES' class variable which
       
   114   should be a map from attribute name to its validator.  For example:
       
   115 
       
   116     class Story(Validated):
       
   117       ATTRIBUTES = {'title': Type(str),
       
   118                     'authors': Repeated(Type(str)),
       
   119                     'isbn': Optional(Type(str)),
       
   120                     'pages': Type(int),
       
   121                     }
       
   122 
       
   123   Attributes that are not listed under ATTRIBUTES work like normal and are
       
   124   not validated upon assignment.
       
   125   """
       
   126 
       
   127   ATTRIBUTES = None
       
   128 
       
   129   def __init__(self, **attributes):
       
   130     """Constructor for Validated classes.
       
   131 
       
   132     This constructor can optionally assign values to the class via its
       
   133     keyword arguments.
       
   134 
       
   135     Raises:
       
   136       AttributeDefinitionError when class instance is missing ATTRIBUTE
       
   137       definition or when ATTRIBUTE is of the wrong type.
       
   138     """
       
   139     if not isinstance(self.ATTRIBUTES, dict):
       
   140       raise AttributeDefinitionError(
       
   141           'The class %s does not define an ATTRIBUTE variable.'
       
   142           % self.__class__)
       
   143 
       
   144     for key in self.ATTRIBUTES.keys():
       
   145       object.__setattr__(self, key, self.GetAttribute(key).default)
       
   146 
       
   147     self.Set(**attributes)
       
   148 
       
   149   @classmethod
       
   150   def GetAttribute(self, key):
       
   151     """Safely get the underlying attribute definition as a Validator.
       
   152 
       
   153     Args:
       
   154       key: Name of attribute to get.
       
   155 
       
   156     Returns:
       
   157       Validator associated with key or attribute value wrapped in a
       
   158       validator.
       
   159     """
       
   160     return AsValidator(self.ATTRIBUTES[key])
       
   161 
       
   162   def Set(self, **attributes):
       
   163     """Set multiple values on Validated instance.
       
   164 
       
   165     This method can only be used to assign validated methods.
       
   166 
       
   167     Args:
       
   168       attributes: Attributes to set on object.
       
   169 
       
   170     Raises:
       
   171       ValidationError when no validated attribute exists on class.
       
   172     """
       
   173     for key, value in attributes.iteritems():
       
   174       if key not in self.ATTRIBUTES:
       
   175         raise ValidationError('Class \'%s\' does not have attribute \'%s\''
       
   176                                % (self.__class__, key))
       
   177       setattr(self, key, value)
       
   178 
       
   179   def CheckInitialized(self):
       
   180     """Checks that all required fields are initialized.
       
   181 
       
   182     Since an instance of Validated starts off in an uninitialized state, it
       
   183     is sometimes necessary to check that it has been fully initialized.
       
   184     The main problem this solves is how to validate that an instance has
       
   185     all of its required fields set.  By default, Validator classes do not
       
   186     allow None, but all attributes are initialized to None when instantiated.
       
   187 
       
   188     Raises:
       
   189       Exception relevant to the kind of validation.  The type of the exception
       
   190       is determined by the validator.  Typically this will be ValueError or
       
   191       TypeError.
       
   192     """
       
   193     for key in self.ATTRIBUTES.iterkeys():
       
   194       try:
       
   195         self.GetAttribute(key)(getattr(self, key))
       
   196       except MissingAttribute, e:
       
   197         e.message = "Missing required value '%s'." % key
       
   198         raise e
       
   199 
       
   200 
       
   201   def __setattr__(self, key, value):
       
   202     """Set attribute.
       
   203 
       
   204     Setting a value on an object of this type will only work for attributes
       
   205     defined in ATTRIBUTES.  To make other assignments possible it is necessary
       
   206     to override this method in subclasses.
       
   207 
       
   208     It is important that assignment is restricted in this way because
       
   209     this validation is used as validation for parsing.  Absent this restriction
       
   210     it would be possible for method names to be overwritten.
       
   211 
       
   212     Args:
       
   213       key: Name of attribute to set.
       
   214       value: Attributes new value.
       
   215 
       
   216     Raises:
       
   217       ValidationError when trying to assign to a value that does not exist.
       
   218     """
       
   219 
       
   220     if key in self.ATTRIBUTES:
       
   221       value = self.GetAttribute(key)(value)
       
   222       object.__setattr__(self, key, value)
       
   223     else:
       
   224       raise ValidationError('Class \'%s\' does not have attribute \'%s\''
       
   225                             % (self.__class__, key))
       
   226 
       
   227   def __eq__(self, other):
       
   228     """Comparison operator."""
       
   229     if isinstance(other, type(self)):
       
   230       for attribute in self.ATTRIBUTES:
       
   231         if getattr(self, attribute) != getattr(other, attribute):
       
   232           return False
       
   233       return True
       
   234     else:
       
   235       return False
       
   236 
       
   237   def __str__(self):
       
   238     """Formatted view of validated object and nested values."""
       
   239     return repr(self)
       
   240 
       
   241   def __repr__(self):
       
   242     """Formatted view of validated object and nested values."""
       
   243     values = [(attr, getattr(self, attr)) for attr in self.ATTRIBUTES]
       
   244     dent = '    '
       
   245     value_list = []
       
   246     for attr, value in values:
       
   247       value_list.append('\n%s%s=%s' % (dent, attr, value))
       
   248 
       
   249     return "<%s %s\n%s>" % (self.__class__.__name__, ' '.join(value_list), dent)
       
   250 
       
   251   def __eq__(self, other):
       
   252     """Equality operator.
       
   253 
       
   254     Comparison is done by comparing all attribute values to those in the other
       
   255     instance.  Objects which are not of the same type are not equal.
       
   256 
       
   257     Args:
       
   258       other: Other object to compare against.
       
   259 
       
   260     Returns:
       
   261       True if validated objects are equal, else False.
       
   262     """
       
   263     if type(self) != type(other):
       
   264       return False
       
   265     for key in self.ATTRIBUTES.iterkeys():
       
   266       if getattr(self, key) != getattr(other, key):
       
   267         return False
       
   268     return True
       
   269 
       
   270   def __ne__(self, other):
       
   271     """Inequality operator."""
       
   272     return not self.__eq__(other)
       
   273 
       
   274   def __hash__(self):
       
   275     """Hash function for using Validated objects in sets and maps.
       
   276 
       
   277     Hash is done by hashing all keys and values and xor'ing them together.
       
   278 
       
   279     Returns:
       
   280       Hash of validated object.
       
   281     """
       
   282     result = 0
       
   283     for key in self.ATTRIBUTES.iterkeys():
       
   284       value = getattr(self, key)
       
   285       if isinstance(value, list):
       
   286         value = tuple(value)
       
   287       result = result ^ hash(key) ^ hash(value)
       
   288     return result
       
   289 
       
   290   @staticmethod
       
   291   def _ToValue(validator, value):
       
   292     """Convert any value to simplified collections and basic types.
       
   293 
       
   294     Args:
       
   295       validator: An instance of Validator that corresponds with 'value'.
       
   296         May also be 'str' or 'int' if those were used instead of a full
       
   297         Validator.
       
   298       value: Value to convert to simplified collections.
       
   299 
       
   300     Returns:
       
   301       The value as a dictionary if it is a Validated object.
       
   302       A list of items converted to simplified collections if value is a list
       
   303         or a tuple.
       
   304       Otherwise, just the value.
       
   305     """
       
   306     if isinstance(value, Validated):
       
   307       return value.ToDict()
       
   308     elif isinstance(value, (list, tuple)):
       
   309       return [Validated._ToValue(validator, item) for item in value]
       
   310     else:
       
   311       if isinstance(validator, Validator):
       
   312         return validator.ToValue(value)
       
   313       return value
       
   314 
       
   315   def ToDict(self):
       
   316     """Convert Validated object to a dictionary.
       
   317 
       
   318     Recursively traverses all of its elements and converts everything to
       
   319     simplified collections.
       
   320 
       
   321     Returns:
       
   322       A dict of all attributes defined in this classes ATTRIBUTES mapped
       
   323       to its value.  This structure is recursive in that Validated objects
       
   324       that are referenced by this object and in lists are also converted to
       
   325       dicts.
       
   326     """
       
   327     result = {}
       
   328     for name, validator in self.ATTRIBUTES.iteritems():
       
   329       value = getattr(self, name)
       
   330       if not(isinstance(validator, Validator) and value == validator.default):
       
   331         result[name] = Validated._ToValue(validator, value)
       
   332     return result
       
   333 
       
   334   def ToYAML(self):
       
   335     """Print validated object as simplified YAML.
       
   336 
       
   337     Returns:
       
   338       Object as a simplified YAML string compatible with parsing using the
       
   339       SafeLoader.
       
   340     """
       
   341     return yaml.dump(self.ToDict(),
       
   342                      default_flow_style=False,
       
   343                      Dumper=yaml.SafeDumper)
       
   344 
       
   345 
       
   346 
       
   347 class Validator(object):
       
   348   """Validator base class.
       
   349 
       
   350   Though any callable can be used as a validator, this class encapsulates the
       
   351   case when a specific validator needs to hold a particular state or
       
   352   configuration.
       
   353 
       
   354   To implement Validator sub-class, override the validate method.
       
   355 
       
   356   This class is permitted to change the ultimate value that is set to the
       
   357   attribute if there is a reasonable way to perform the conversion.
       
   358   """
       
   359 
       
   360   expected_type = object
       
   361 
       
   362   def __init__(self, default=None):
       
   363     """Constructor.
       
   364 
       
   365     Args:
       
   366       default: Default assignment is made during initialization and will
       
   367         not pass through validation.
       
   368     """
       
   369     self.default = default
       
   370 
       
   371   def __call__(self, value):
       
   372     """Main interface to validator is call mechanism."""
       
   373     return self.Validate(value)
       
   374 
       
   375   def Validate(self, value):
       
   376     """Override this method to customize sub-class behavior.
       
   377 
       
   378     Args:
       
   379       value: Value to validate.
       
   380 
       
   381     Returns:
       
   382       Value if value is valid, or a valid representation of value.
       
   383     """
       
   384     return value
       
   385 
       
   386   def ToValue(self, value):
       
   387     """Convert 'value' to a simplified collection or basic type.
       
   388 
       
   389     Subclasses of Validator should override this method when the dumped
       
   390     representation of 'value' is not simply <type>(value) (e.g. a regex).
       
   391 
       
   392     Args:
       
   393       value: An object of the same type that was returned from Validate().
       
   394 
       
   395     Returns:
       
   396       An instance of a builtin type (e.g. int, str, dict, etc).  By default
       
   397       it returns 'value' unmodified.
       
   398     """
       
   399     return value
       
   400 
       
   401 
       
   402 class Type(Validator):
       
   403   """Verifies property is of expected type.
       
   404 
       
   405   Can optionally convert value if it is not of the expected type.
       
   406 
       
   407   It is possible to specify a required field of a specific type in shorthand
       
   408   by merely providing the type.  This method is slightly less efficient than
       
   409   providing an explicit type but is not significant unless parsing a large
       
   410   amount of information:
       
   411 
       
   412     class Person(Validated):
       
   413       ATTRIBUTES = {'name': unicode,
       
   414                     'age': int,
       
   415                     }
       
   416 
       
   417   However, in most instances it is best to use the type constants:
       
   418 
       
   419     class Person(Validated):
       
   420       ATTRIBUTES = {'name': TypeUnicode,
       
   421                     'age': TypeInt,
       
   422                     }
       
   423   """
       
   424 
       
   425   def __init__(self, expected_type, convert=True, default=None):
       
   426     """Initialize Type validator.
       
   427 
       
   428     Args:
       
   429       expected_type: Type that attribute should validate against.
       
   430       convert: Cause conversion if value is not the right type.
       
   431         Conversion is done by calling the constructor of the type
       
   432         with the value as its first parameter.
       
   433     """
       
   434     super(Type, self).__init__(default)
       
   435     self.expected_type = expected_type
       
   436     self.convert = convert
       
   437 
       
   438   def Validate(self, value):
       
   439     """Validate that value is correct type.
       
   440 
       
   441     Args:
       
   442       value: Value to validate.
       
   443 
       
   444     Returns:
       
   445       None if value is None, value if value is of correct type, converted
       
   446       value if the validator is configured to convert.
       
   447 
       
   448     Raises:
       
   449       ValidationError if value is not of the right type and validator
       
   450       is not configured to convert.
       
   451     """
       
   452     if not isinstance(value, self.expected_type):
       
   453       if value is not None and self.convert:
       
   454         try:
       
   455           return self.expected_type(value)
       
   456         except ValueError, e:
       
   457           raise ValidationError('Type conversion failed for value \'%s\'.'
       
   458                                 % value,
       
   459                                 e)
       
   460         except TypeError, e:
       
   461           raise ValidationError('Expected value of type %s, but got \'%s\'.'
       
   462                                 % (self.expected_type, value))
       
   463       else:
       
   464         raise MissingAttribute('Missing value is required.')
       
   465     else:
       
   466       return value
       
   467 
       
   468 
       
   469 TYPE_BOOL = Type(bool)
       
   470 TYPE_INT = Type(int)
       
   471 TYPE_LONG = Type(long)
       
   472 TYPE_STR = Type(str)
       
   473 TYPE_UNICODE = Type(unicode)
       
   474 TYPE_FLOAT = Type(float)
       
   475 
       
   476 
       
   477 class Options(Validator):
       
   478   """Limit field based on pre-determined values.
       
   479 
       
   480   Options are used to make sure an enumerated set of values are the only
       
   481   one permitted for assignment.  It is possible to define aliases which
       
   482   map multiple string values to a single original.  An example of usage:
       
   483 
       
   484     class ZooAnimal(validated.Class):
       
   485       ATTRIBUTES = {
       
   486         'name': str,
       
   487         'kind': Options('platypus',                   # No aliases
       
   488                         ('rhinoceros', ['rhino']),    # One alias
       
   489                         ('canine', ('dog', 'puppy')), # Two aliases
       
   490                         )
       
   491   """
       
   492 
       
   493   def __init__(self, *options, **kw):
       
   494     """Initialize options.
       
   495 
       
   496     Args:
       
   497       options: List of allowed values.
       
   498     """
       
   499     if 'default' in kw:
       
   500       default = kw['default']
       
   501     else:
       
   502       default = None
       
   503 
       
   504     alias_map = {}
       
   505     def AddAlias(alias, original):
       
   506       """Set new alias on alias_map.
       
   507 
       
   508       Raises:
       
   509         AttributeDefinitionError when option already exists or if alias is
       
   510         not of type str..
       
   511       """
       
   512       if not isinstance(alias, str):
       
   513         raise AttributeDefinitionError(
       
   514             'All option values must be of type str.')
       
   515       elif alias in alias_map:
       
   516         raise AttributeDefinitionError(
       
   517             "Option '%s' already defined for options property." % alias)
       
   518       alias_map[alias] = original
       
   519 
       
   520     for option in options:
       
   521       if isinstance(option, str):
       
   522         AddAlias(option, option)
       
   523 
       
   524       elif isinstance(option, (list, tuple)):
       
   525         if len(option) != 2:
       
   526           raise AttributeDefinitionError("Alias is defined as a list of tuple "
       
   527                                          "with two items.  The first is the "
       
   528                                          "original option, while the second "
       
   529                                          "is a list or tuple of str aliases.\n"
       
   530                                          "\n  Example:\n"
       
   531                                          "      ('original', ('alias1', "
       
   532                                          "'alias2'")
       
   533         original, aliases = option
       
   534         AddAlias(original, original)
       
   535         if not isinstance(aliases, (list, tuple)):
       
   536           raise AttributeDefinitionError('Alias lists must be a list or tuple')
       
   537 
       
   538         for alias in aliases:
       
   539           AddAlias(alias, original)
       
   540 
       
   541       else:
       
   542         raise AttributeDefinitionError("All options must be of type str "
       
   543                                        "or of the form (str, [str...]).")
       
   544     super(Options, self).__init__(default)
       
   545     self.options = alias_map
       
   546 
       
   547   def Validate(self, value):
       
   548     """Validate options.
       
   549 
       
   550     Returns:
       
   551       Original value for provided alias.
       
   552 
       
   553     Raises:
       
   554       ValidationError when value is not one of predefined values.
       
   555     """
       
   556     if value is None:
       
   557       raise ValidationError('Value for options field must not be None.')
       
   558     value = str(value)
       
   559     if value not in self.options:
       
   560       raise ValidationError('Value \'%s\' not in %s.'
       
   561                             % (value, self.options))
       
   562     return self.options[value]
       
   563 
       
   564 
       
   565 class Optional(Validator):
       
   566   """Definition of optional attributes.
       
   567 
       
   568   Optional values are attributes which can be set to None or left
       
   569   unset.  All values in a basic Validated class are set to None
       
   570   at initialization.  Failure to assign to non-optional values
       
   571   will result in a validation error when calling CheckInitialized.
       
   572   """
       
   573 
       
   574   def __init__(self, validator, default=None):
       
   575     """Initializer.
       
   576 
       
   577     This constructor will make a few guesses about the value passed in
       
   578     as the validator:
       
   579 
       
   580       - If the validator argument is a type, it automatically creates a Type
       
   581         validator around it.
       
   582 
       
   583       - If the validator argument is a list or tuple, it automatically
       
   584         creates an Options validator around it.
       
   585 
       
   586     Args:
       
   587       validator: Optional validation condition.
       
   588 
       
   589     Raises:
       
   590       AttributeDefinitionError if validator is not callable.
       
   591     """
       
   592     self.validator = AsValidator(validator)
       
   593     self.expected_type = self.validator.expected_type
       
   594     self.default = default
       
   595 
       
   596   def Validate(self, value):
       
   597     """Optionally require a value.
       
   598 
       
   599     Normal validators do not accept None.  This will accept none on
       
   600     behalf of the contained validator.
       
   601 
       
   602     Args:
       
   603       value: Value to be validated as optional.
       
   604 
       
   605     Returns:
       
   606       None if value is None, else results of contained validation.
       
   607     """
       
   608     if value is None:
       
   609       return None
       
   610     return self.validator(value)
       
   611 
       
   612 
       
   613 class Regex(Validator):
       
   614   """Regular expression validator.
       
   615 
       
   616   Regular expression validator always converts value to string.  Note that
       
   617   matches must be exact.  Partial matches will not validate.  For example:
       
   618 
       
   619     class ClassDescr(Validated):
       
   620       ATTRIBUTES = { 'name': Regex(r'[a-zA-Z_][a-zA-Z_0-9]*'),
       
   621                      'parent': Type(type),
       
   622                      }
       
   623 
       
   624   Alternatively, any attribute that is defined as a string is automatically
       
   625   interpreted to be of type Regex.  It is possible to specify unicode regex
       
   626   strings as well.  This approach is slightly less efficient, but usually
       
   627   is not significant unless parsing large amounts of data:
       
   628 
       
   629     class ClassDescr(Validated):
       
   630       ATTRIBUTES = { 'name': r'[a-zA-Z_][a-zA-Z_0-9]*',
       
   631                      'parent': Type(type),
       
   632                      }
       
   633 
       
   634     # This will raise a ValidationError exception.
       
   635     my_class(name='AName with space', parent=AnotherClass)
       
   636   """
       
   637 
       
   638   def __init__(self, regex, string_type=unicode, default=None):
       
   639     """Initialized regex validator.
       
   640 
       
   641     Args:
       
   642       regex: Regular expression string to use for comparison.
       
   643 
       
   644     Raises:
       
   645       AttributeDefinitionError if string_type is not a kind of string.
       
   646     """
       
   647     super(Regex, self).__init__(default)
       
   648     if (not issubclass(string_type, basestring) or
       
   649         string_type is basestring):
       
   650       raise AttributeDefinitionError(
       
   651           'Regex fields must be a string type not %s.' % str(string_type))
       
   652     if isinstance(regex, basestring):
       
   653       self.re = re.compile('^%s$' % regex)
       
   654     else:
       
   655       raise AttributeDefinitionError(
       
   656           'Regular expression must be string.  Found %s.' % str(regex))
       
   657 
       
   658     self.expected_type = string_type
       
   659 
       
   660   def Validate(self, value):
       
   661     """Does validation of a string against a regular expression.
       
   662 
       
   663     Args:
       
   664       value: String to match against regular expression.
       
   665 
       
   666     Raises:
       
   667       ValidationError when value does not match regular expression or
       
   668       when value does not match provided string type.
       
   669     """
       
   670     if issubclass(self.expected_type, str):
       
   671       cast_value = TYPE_STR(value)
       
   672     else:
       
   673       cast_value = TYPE_UNICODE(value)
       
   674 
       
   675     if self.re.match(cast_value) is None:
       
   676       raise ValidationError('Value \'%s\' does not match expression \'%s\''
       
   677                             % (value, self.re.pattern))
       
   678     return cast_value
       
   679 
       
   680 
       
   681 class RegexStr(Validator):
       
   682   """Validates that a string can compile as a regex without errors.
       
   683 
       
   684   Use this validator when the value of a field should be a regex.  That
       
   685   means that the value must be a string that can be compiled by re.compile().
       
   686   The attribute will then be a compiled re object.
       
   687   """
       
   688 
       
   689   def __init__(self, string_type=unicode, default=None):
       
   690     """Initialized regex validator.
       
   691 
       
   692     Raises:
       
   693       AttributeDefinitionError if string_type is not a kind of string.
       
   694     """
       
   695     if default is not None:
       
   696       default = re.compile(default)
       
   697     super(RegexStr, self).__init__(default)
       
   698     if (not issubclass(string_type, basestring) or
       
   699         string_type is basestring):
       
   700       raise AttributeDefinitionError(
       
   701           'RegexStr fields must be a string type not %s.' % str(string_type))
       
   702 
       
   703     self.expected_type = string_type
       
   704 
       
   705   def Validate(self, value):
       
   706     """Validates that the string compiles as a regular expression.
       
   707 
       
   708     Because the regular expression might have been expressed as a multiline
       
   709     string, this function also strips newlines out of value.
       
   710 
       
   711     Args:
       
   712       value: String to compile as a regular expression.
       
   713 
       
   714     Raises:
       
   715       ValueError when value does not compile as a regular expression.  TypeError
       
   716       when value does not match provided string type.
       
   717     """
       
   718     if issubclass(self.expected_type, str):
       
   719       cast_value = TYPE_STR(value)
       
   720     else:
       
   721       cast_value = TYPE_UNICODE(value)
       
   722 
       
   723     cast_value = cast_value.replace('\n', '')
       
   724     cast_value = cast_value.replace('\r', '')
       
   725     try:
       
   726       compiled = re.compile(cast_value)
       
   727     except re.error, e:
       
   728       raise ValidationError('Value \'%s\' does not compile: %s' % (value, e), e)
       
   729     return compiled
       
   730 
       
   731   def ToValue(self, value):
       
   732     """Returns the RE pattern for this validator."""
       
   733     return value.pattern
       
   734 
       
   735 
       
   736 class Range(Validator):
       
   737   """Validates that numbers fall within the correct range.
       
   738 
       
   739   In theory this class can be emulated using Options, however error
       
   740   messages generated from that class will not be very intelligible.
       
   741   This class essentially does the same thing, but knows the intended
       
   742   integer range.
       
   743 
       
   744   Also, this range class supports floats and other types that implement
       
   745   ordinality.
       
   746 
       
   747   The range is inclusive, meaning 3 is considered in the range
       
   748   in Range(1,3).
       
   749   """
       
   750 
       
   751   def __init__(self, minimum, maximum, range_type=int, default=None):
       
   752     """Initializer for range.
       
   753 
       
   754     Args:
       
   755       minimum: Minimum for attribute.
       
   756       maximum: Maximum for attribute.
       
   757       range_type: Type of field.  Defaults to int.
       
   758     """
       
   759     super(Range, self).__init__(default)
       
   760     if not isinstance(minimum, range_type):
       
   761       raise AttributeDefinitionError(
       
   762           'Minimum value must be of type %s, instead it is %s (%s).' %
       
   763           (str(range_type), str(type(minimum)), str(minimum)))
       
   764     if not isinstance(maximum, range_type):
       
   765       raise AttributeDefinitionError(
       
   766           'Maximum value must be of type %s, instead it is %s (%s).' %
       
   767           (str(range_type), str(type(maximum)), str(maximum)))
       
   768 
       
   769     self.minimum = minimum
       
   770     self.maximum = maximum
       
   771     self.expected_type = range_type
       
   772     self._type_validator = Type(range_type)
       
   773 
       
   774   def Validate(self, value):
       
   775     """Validate that value is within range.
       
   776 
       
   777     Validates against range-type then checks the range.
       
   778 
       
   779     Args:
       
   780       value: Value to validate.
       
   781 
       
   782     Raises:
       
   783       ValidationError when value is out of range.  ValidationError when value
       
   784       is notd of the same range type.
       
   785     """
       
   786     cast_value = self._type_validator.Validate(value)
       
   787     if cast_value < self.minimum or cast_value > self.maximum:
       
   788       raise ValidationError('Value \'%s\' is out of range %s - %s'
       
   789                             % (str(value),
       
   790                                str(self.minimum),
       
   791                                str(self.maximum)))
       
   792     return cast_value
       
   793 
       
   794 
       
   795 class Repeated(Validator):
       
   796   """Repeated field validator.
       
   797 
       
   798   Indicates that attribute is expected to be a repeated value, ie,
       
   799   a sequence.  This adds additional validation over just Type(list)
       
   800   in that it retains information about what can be stored in the list by
       
   801   use of its constructor field.
       
   802   """
       
   803 
       
   804   def __init__(self, constructor, default=None):
       
   805     """Initializer for repeated field.
       
   806 
       
   807     Args:
       
   808       constructor: Type used for verifying elements of sequence attribute.
       
   809     """
       
   810     super(Repeated, self).__init__(default)
       
   811     self.constructor = constructor
       
   812     self.expected_type = list
       
   813 
       
   814   def Validate(self, value):
       
   815     """Do validation of sequence.
       
   816 
       
   817     Value must be a list and all elements must be of type 'constructor'.
       
   818 
       
   819     Args:
       
   820       value: Value to validate.
       
   821 
       
   822     Raises:
       
   823       ValidationError if value is None, not a list or one of its elements is the
       
   824       wrong type.
       
   825     """
       
   826     if not isinstance(value, list):
       
   827       raise ValidationError('Repeated fields must be sequence, '
       
   828                             'but found \'%s\'.' % value)
       
   829 
       
   830     for item in value:
       
   831       if not isinstance(item, self.constructor):
       
   832         raise ValidationError('Repeated items must be %s, but found \'%s\'.'
       
   833                               % (str(self.constructor), str(item)))
       
   834 
       
   835     return value