|
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 |