Error reporting should be accurate: >>> from voluptuous import * >>> schema = Schema(['one', {'two': 'three', 'four': ['five'], ... 'six': {'seven': 'eight'}}]) >>> schema(['one']) ['one'] >>> schema([{'two': 'three'}]) [{'two': 'three'}] It should show the exact index and container type, in this case a list value: >>> try: ... schema(['one', 'two']) ... raise AssertionError('MultipleInvalid not raised') ... except MultipleInvalid as e: ... exc = e >>> str(exc) == 'expected a dictionary @ data[1]' True It should also be accurate for nested values: >>> try: ... schema([{'two': 'nine'}]) ... raise AssertionError('MultipleInvalid not raised') ... except MultipleInvalid as e: ... exc = e >>> str(exc) "not a valid value for dictionary value @ data[0]['two']" >>> try: ... schema([{'four': ['nine']}]) ... raise AssertionError('MultipleInvalid not raised') ... except MultipleInvalid as e: ... exc = e >>> str(exc) "not a valid value @ data[0]['four'][0]" >>> try: ... schema([{'six': {'seven': 'nine'}}]) ... raise AssertionError('MultipleInvalid not raised') ... except MultipleInvalid as e: ... exc = e >>> str(exc) "not a valid value for dictionary value @ data[0]['six']['seven']" Errors should be reported depth-first: >>> validate = Schema({'one': {'two': 'three', 'four': 'five'}}) >>> try: ... validate({'one': {'four': 'six'}}) ... except Invalid as e: ... print(e) ... print(e.path) not a valid value for dictionary value @ data['one']['four'] ['one', 'four'] Voluptuous supports validation when extra fields are present in the data: >>> schema = Schema({'one': 1, Extra: object}) >>> schema({'two': 'two', 'one': 1}) == {'two': 'two', 'one': 1} True >>> schema = Schema({'one': 1}) >>> try: ... schema({'two': 2}) ... raise AssertionError('MultipleInvalid not raised') ... except MultipleInvalid as e: ... exc = e >>> str(exc) "extra keys not allowed @ data['two']" dict, list, and tuple should be available as type validators: >>> Schema(dict)({'a': 1, 'b': 2}) == {'a': 1, 'b': 2} True >>> Schema(list)([1,2,3]) [1, 2, 3] >>> Schema(tuple)((1,2,3)) (1, 2, 3) Validation should return instances of the right types when the types are subclasses of dict or list: >>> class Dict(dict): ... pass >>> >>> d = Schema(dict)(Dict(a=1, b=2)) >>> d == {'a': 1, 'b': 2} True >>> type(d) is Dict True >>> class List(list): ... pass >>> >>> l = Schema(list)(List([1,2,3])) >>> l [1, 2, 3] >>> type(l) is List True Multiple errors are reported: >>> schema = Schema({'one': 1, 'two': 2}) >>> try: ... schema({'one': 2, 'two': 3, 'three': 4}) ... except MultipleInvalid as e: ... errors = sorted(e.errors, key=lambda k: str(k)) ... print([str(i) for i in errors]) # doctest: +NORMALIZE_WHITESPACE ["extra keys not allowed @ data['three']", "not a valid value for dictionary value @ data['one']", "not a valid value for dictionary value @ data['two']"] >>> schema = Schema([[1], [2], [3]]) >>> try: ... schema([1, 2, 3]) ... except MultipleInvalid as e: ... print([str(i) for i in e.errors]) # doctest: +NORMALIZE_WHITESPACE ['expected a list @ data[0]', 'expected a list @ data[1]', 'expected a list @ data[2]'] Required fields in dictionary which are invalid should not have required : >>> from voluptuous import * >>> schema = Schema({'one': {'two': 3}}, required=True) >>> try: ... schema({'one': {'two': 2}}) ... except MultipleInvalid as e: ... errors = e.errors >>> 'required' in ' '.join([x.msg for x in errors]) False Multiple errors for nested fields in dicts and objects: > \>\>\> from collections import namedtuple \>\>\> validate = Schema({ > ... 'anobject': Object({ ... 'strfield': str, ... 'intfield': int ... > }) ... }) \>\>\> try: ... SomeObj = namedtuple('SomeObj', ('strfield', > 'intfield')) ... validate({'anobject': SomeObj(strfield=123, > intfield='one')}) ... except MultipleInvalid as e: ... > print(sorted(str(i) for i in e.errors)) \# doctest: > +NORMALIZE\_WHITESPACE ["expected int for object value @ > data['anobject']['intfield']", "expected str for object value @ > data['anobject']['strfield']"] Custom classes validate as schemas: >>> class Thing(object): ... pass >>> schema = Schema(Thing) >>> t = schema(Thing()) >>> type(t) is Thing True Classes with custom metaclasses should validate as schemas: >>> class MyMeta(type): ... pass >>> class Thing(object): ... __metaclass__ = MyMeta >>> schema = Schema(Thing) >>> t = schema(Thing()) >>> type(t) is Thing True Schemas built with All() should give the same error as the original validator (Issue \#26): >>> schema = Schema({ ... Required('items'): All([{ ... Required('foo'): str ... }]) ... }) >>> try: ... schema({'items': [{}]}) ... raise AssertionError('MultipleInvalid not raised') ... except MultipleInvalid as e: ... exc = e >>> str(exc) "required key not provided @ data['items'][0]['foo']" Validator should return same instance of the same type for object: >>> class Structure(object): ... def __init__(self, q=None): ... self.q = q ... def __repr__(self): ... return '{0.__name__}(q={1.q!r})'.format(type(self), self) ... >>> schema = Schema(Object({'q': 'one'}, cls=Structure)) >>> type(schema(Structure(q='one'))) is Structure True Object validator should treat cls argument as optional. In this case it shouldn't check object type: >>> from collections import namedtuple >>> NamedTuple = namedtuple('NamedTuple', ('q',)) >>> schema = Schema(Object({'q': 'one'})) >>> named = NamedTuple(q='one') >>> schema(named) == named True >>> schema(named) NamedTuple(q='one') If cls argument passed to object validator we should check object type: >>> schema = Schema(Object({'q': 'one'}, cls=Structure)) >>> schema(NamedTuple(q='one')) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... MultipleInvalid: expected a >>> schema = Schema(Object({'q': 'one'}, cls=NamedTuple)) >>> schema(NamedTuple(q='one')) NamedTuple(q='one') Ensure that objects with \_\_slots\_\_ supported properly: >>> class SlotsStructure(Structure): ... __slots__ = ['q'] ... >>> schema = Schema(Object({'q': 'one'})) >>> schema(SlotsStructure(q='one')) SlotsStructure(q='one') >>> class DictStructure(object): ... __slots__ = ['q', '__dict__'] ... def __init__(self, q=None, page=None): ... self.q = q ... self.page = page ... def __repr__(self): ... return '{0.__name__}(q={1.q!r}, page={1.page!r})'.format(type(self), self) ... >>> structure = DictStructure(q='one') >>> structure.page = 1 >>> try: ... schema(structure) ... raise AssertionError('MultipleInvalid not raised') ... except MultipleInvalid as e: ... exc = e >>> str(exc) "extra keys not allowed @ data['page']" >>> schema = Schema(Object({'q': 'one', Extra: object})) >>> schema(structure) DictStructure(q='one', page=1) Ensure that objects can be used with other validators: >>> schema = Schema({'meta': Object({'q': 'one'})}) >>> schema({'meta': Structure(q='one')}) {'meta': Structure(q='one')} Ensure that subclasses of Invalid of are raised as is. >>> class SpecialInvalid(Invalid): ... pass ... >>> def custom_validator(value): ... raise SpecialInvalid('boom') ... >>> schema = Schema({'thing': custom_validator}) >>> try: ... schema({'thing': 'not an int'}) ... except MultipleInvalid as e: ... exc = e >>> exc.errors[0].__class__.__name__ 'SpecialInvalid'