8.1 KiB
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 <class 'Structure'>
>>> 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'