diff options
Diffstat (limited to 'voluptuous.py')
| -rw-r--r-- | voluptuous.py | 98 |
1 files changed, 66 insertions, 32 deletions
diff --git a/voluptuous.py b/voluptuous.py index 20da1a1..2b5f488 100644 --- a/voluptuous.py +++ b/voluptuous.py @@ -133,6 +133,25 @@ class Invalid(Error): return Exception.__str__(self) + path +class InvalidList(Invalid): + def __init__(self, errors=None): + self.errors = errors[:] if errors else [] + + @property + def msg(self): + return self.errors[0].msg + + @property + def path(self): + return self.errors[0].path + + def add(self, error): + self.errors.append(error) + + def __str__(self): + return str(self.errors[0]) + + class Schema(object): """A validation schema. @@ -160,16 +179,21 @@ class Schema(object): return self.validate([], self.schema, data) def validate(self, path, schema, data): - if isinstance(schema, dict): - return self.validate_dict(path, schema, data) - elif isinstance(schema, list): - return self.validate_list(path, schema, data) - type_ = type(schema) - if type_ is type: - type_ = schema - if type_ in (int, long, str, unicode, float, complex, object, - list, dict, types.NoneType) or callable(schema): - return self.validate_scalar(path, schema, data) + try: + if isinstance(schema, dict): + return self.validate_dict(path, schema, data) + elif isinstance(schema, list): + return self.validate_list(path, schema, data) + type_ = type(schema) + if type_ is type: + type_ = schema + if type_ in (int, long, str, unicode, float, complex, object, + list, dict, types.NoneType) or callable(schema): + return self.validate_scalar(path, schema, data) + except InvalidList: + raise + except Invalid, e: + raise InvalidList([e]) raise SchemaError('unsupported schema data type %r' % type(schema).__name__) @@ -185,7 +209,7 @@ class Schema(object): >>> validate([]) Traceback (most recent call last): ... - Invalid: expected a dictionary + InvalidList: expected a dictionary An invalid dictionary value: @@ -193,14 +217,14 @@ class Schema(object): >>> validate({'one': 'three'}) Traceback (most recent call last): ... - Invalid: not a valid value for dictionary value @ data['one'] + InvalidList: not a valid value for dictionary value @ data['one'] An invalid key: >>> validate({'two': 'three'}) Traceback (most recent call last): ... - Invalid: not a valid value for dictionary key @ data['two'] + InvalidList: not a valid value for dictionary key @ data['two'] Validation function, in this case the "int" type: @@ -218,7 +242,7 @@ class Schema(object): >>> validate({'10': 'twenty'}) Traceback (most recent call last): ... - Invalid: not a valid value for dictionary key @ data['10'] + InvalidList: not a valid value for dictionary key @ data['10'] Wrap them in the coerce() function to achieve this: @@ -240,6 +264,7 @@ class Schema(object): isinstance(key, required)) invalid = None error = None + errors = [] for key, value in data.iteritems(): key_path = path + [key] for skey, svalue in schema.iteritems(): @@ -261,8 +286,11 @@ class Schema(object): out[new_key] = self.validate(key_path, svalue, value) except Invalid, e: if len(e.path) > len(key_path): - raise - raise Invalid(e.msg + ' for dictionary value', e.path) + errors.append(e) + else: + errors.append(Invalid(e.msg + ' for dictionary value', + e.path)) + break # Key and value okay, mark any required() fields as found. required_keys.discard(skey) @@ -273,18 +301,21 @@ class Schema(object): else: if invalid: if len(error.path) > len(path) + 1: - raise error + errors.append(error) else: - raise Invalid(invalid, key_path) + errors.append(Invalid(invalid, key_path)) else: - raise Invalid('extra keys not allowed', key_path) + errors.append(Invalid('extra keys not allowed', + key_path)) if required_keys: if len(required_keys) > 1: message = 'required keys %s not provided' \ % ', '.join(map(repr, map(str, required_keys))) else: message = 'required key %r not provided' % required_keys.pop() - raise Invalid(message, path) + errors.append(Invalid(message, path)) + if errors: + raise InvalidList(errors) return out def validate_list(self, path, schema, data): @@ -298,7 +329,7 @@ class Schema(object): >>> validator([3.5]) Traceback (most recent call last): ... - Invalid: invalid list value @ data[0] + InvalidList: invalid list value @ data[0] >>> validator([1]) [1] """ @@ -311,9 +342,11 @@ class Schema(object): out = type(data)() invalid = None + errors = [] index_path = UNDEFINED for i, value in enumerate(data): index_path = path + [i] + invalid = None for s in schema: try: out.append(self.validate(index_path, s, value)) @@ -323,10 +356,11 @@ class Schema(object): raise invalid = e else: - if len(invalid.path) > len(index_path): - raise invalid - else: - raise Invalid('invalid list value', index_path) + if len(invalid.path) <= len(index_path): + invalid = Invalid('invalid list value', index_path) + errors.append(invalid) + if errors: + raise InvalidList(errors) return out @staticmethod @@ -413,7 +447,7 @@ def msg(schema, msg): >>> validate(['three']) Traceback (most recent call last): ... - Invalid: should be one of "one", "two" or an integer + InvalidList: should be one of "one", "two" or an integer Messages are only applied to invalid direct descendants of the schema: @@ -421,7 +455,7 @@ def msg(schema, msg): >>> validate([['three']]) Traceback (most recent call last): ... - Invalid: invalid list value @ data[0][0] + InvalidList: invalid list value @ data[0][0] """ def f(v): try: @@ -460,13 +494,13 @@ def true(msg=None): >>> validate([]) Traceback (most recent call last): ... - Invalid: value was not true + InvalidList: value was not true >>> validate([1]) [1] >>> validate(False) Traceback (most recent call last): ... - Invalid: value was not true + InvalidList: value was not true ...and so on. """ @@ -505,7 +539,7 @@ def boolean(msg=None): >>> validate('moo') Traceback (most recent call last): ... - Invalid: expected boolean + InvalidList: expected boolean """ def f(v): try: @@ -537,7 +571,7 @@ def any(*validators, **kwargs): >>> validate('moo') Traceback (most recent call last): ... - Invalid: no valid value found + InvalidList: no valid value found """ msg = kwargs.pop('msg', None) @@ -586,7 +620,7 @@ def match(pattern, msg=None): >>> validate('123EF4') Traceback (most recent call last): ... - Invalid: does not match regular expression + InvalidList: does not match regular expression Pattern may also be a compiled regular expression: |
