def test_dict_with_Value():
    d4 = {'a': 1, 'b': '3', 'c': 56.2}
    assert Value(Dict[str, Value(int)]).validate(d4) == {
        'a': 1,
        'b': 3,
        'c': 56
    }
def test_NamedValue(value):
    '''Test the parsing of dictionary elements with NamedValue'''
    d = {value: 5}
    assert NamedValue(value, int).validate(d) == d  # simple constructor
    assert NamedValue(value, Value(int, val_max=6)).validate(d) == d  # constructor from Value
    d_dval = {value: {value: 5}}
    # constructor from DictValue
    assert NamedValue(value, DictValue({value: int})).validate(d_dval) == d_dval
    d2 = {value: value}
    assert NamedValue(value, type(value)).validate(d2) == d2
    assert NamedValue(value, Value(type(value))).validate(d2) == d2

    # the key must match exactly
    with pytest.raises(SettingsValueError) as excinfo:
        NamedValue(value, int).validate({'wrong_key': 5})
    assert excinfo.match(r"Setting ")
    assert excinfo.match(r" not in dictionary")
    assert excinfo.type == SettingsValueError

    # the value must have the right type
    with pytest.raises(SettingsValueError) as excinfo:
        NamedValue(value, int).validate({value: 'wrong_value'})
    assert excinfo.match('does not have the right type')
    assert excinfo.type == SettingsValueError

    # the value to validate must be a dictionary
    with pytest.raises(SettingsValueError) as excinfo:
        NamedValue(value, int).validate(value)
    assert excinfo.match('is not a dictionary')
    assert excinfo.type == SettingsValueError
def test_str_to_num():
    '''Test conversion from str to numbers.'''

    assert Value(int).validate('25') == 25
    assert Value(float).validate('25.0') == 25.0
    assert Value(complex).validate('2+5j') == 2 + 5j
    assert Value(Fraction).validate('79/98') == Fraction(79, 98)
def test_list_len_val():
    '''Test both the list's length and the size of the values'''
    assert Value(List[int], val_max=5, val_min=1, len_max=4,
                 len_min=1).validate([1, 2]) == [1, 2]
    assert Value(List[int], val_max=5, val_min=1, len_max=4,
                 len_min=1).validate([4]) == [4]
    assert Value(List[int], val_max=5, val_min=1, len_max=4,
                 len_min=1).validate([1, 2]) == [1, 2]
def test_num_to_str():
    '''Test conversion from numbers to strings.'''

    assert Value(str).validate(25) == '25'
    assert Value(str).validate(25.0) == '25.0'
    assert Value(str).validate(2 + 5j) == '(2+5j)'
    assert Value(str).validate(Fraction(
        78 / 98)) == '3584497662601007/4503599627370496'
def test_nested_DictValue():
    '''Test DictValues with other DictValues as types.'''
    d = {
        'subsection1': {
            'subsubsection1': 'asd',
            'subsubsection2': 5
        },
        'subsection2': [1, 2, 3]
    }
    assert DictValue({
        'subsection1':
        DictValue({
            'subsubsection1': str,
            'subsubsection2': int
        }),
        'subsection2':
        Value(List[int])
    }).validate(d) == d
    # avoid repetition of DictValue in nested types
    assert DictValue({
        'subsection1': {
            'subsubsection1': str,
            'subsubsection2': int
        },
        'subsection2': Value(List[int])
    }).validate(d) == d

    d2 = {
        'subsection1': {
            'subsubsection1': 'asd',
            'subsubsection2': 5
        },
        'subsection2': 1
    }
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue({
            'subsection1': {
                'subsubsection1': str,
                'subsubsection2': int
            },
            'subsection2': Value(List[int])
        }).validate(d2)
    assert excinfo.match('Setting "subsection2"')
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError

    # with optional sub-DictValues
    d3 = {'subsection2': [1, 2, 3]}
    assert DictValue({
        'subsection1':
        DictValue({
            'subsubsection1': str,
            'subsubsection2': int
        },
                  kind=Kind.optional),
        'subsection2':
        Value(List[int])
    }).validate(d3) == d3
def test_tuples():
    '''Tuples can have any number of sub-types, which must match exactly.'''
    assert Value(Tuple[int]).validate([
        1,
    ]) == (1, )
    assert Value(Tuple[int, int]).validate([1, 2]) == (1, 2)
    assert Value(Tuple[int, str]).validate((1, 'asd')) == (1, 'asd')

    with pytest.raises(SettingsValueError) as excinfo:
        Value(Tuple[int]).validate([1, 2])
    assert excinfo.match('Details: "Wrong number of values: 2 instead of 1."')
    assert excinfo.type == SettingsValueError
def test_simple_unions():
    '''Test that Unions of simple types go through each member.'''
    assert Value(Union[int, str]).validate(12.0) == 12
    assert Value(Union[int, str]).validate('12.0') == '12.0'

    assert Value(Union[float, complex]).validate(5 + 0j) == 5 + 0j
    assert Value(Union[float, Fraction]).validate('5/2') == Fraction(5, 2)

    with pytest.raises(SettingsValueError) as excinfo:
        Value(Union[int, float]).validate(5 + 1j)
    assert excinfo.match("does not have any of the right types")
    assert excinfo.type == SettingsValueError
def test_nested_dicts():
    '''Test the parsing of nested dictionaries.'''
    d = {'a': {('b', 2): 4}}
    assert Value(Dict[str, Dict[Tuple[str, int], int]]).validate(d) == {
        'a': {
            ('b', 2): 4
        }
    }
    assert Value(Dict[str, Dict[Tuple[str, int], str]]).validate(d) == {
        'a': {
            ('b', 2): '4'
        }
    }
def test_str_len():
    '''Test the max and min length of a simple str.'''
    assert Value(str, len_max=3).validate('abc')
    with pytest.raises(SettingsValueError) as excinfo:
        Value(str, len_max=3).validate('abcd')
    assert excinfo.match('cannot be larger than 3.')
    assert excinfo.type == SettingsValueError

    assert Value(str, len_max=3).validate(123)
    with pytest.raises(SettingsValueError) as excinfo:
        Value(str, len_max=3).validate(1234)
    assert excinfo.match('cannot be larger than 3.')
    assert excinfo.type == SettingsValueError
def test_custom_functions():
    '''Test user-defined functions that restrict the value.'''
    assert Value(int, fun=lambda x: x != 5).validate(4) == 4
    assert Value(int, fun=lambda x: x != 5).validate(6) == 6

    with pytest.raises(SettingsValueError) as excinfo:
        Value(int, fun=lambda x: x != 5).validate(5)
    assert excinfo.match(r"is not valid according to the user function")
    assert excinfo.type == SettingsValueError

    assert Value(List[int],
                 fun=lambda lst: all(x == 6
                                     for x in lst)).validate([6, 6,
                                                              6]) == [6, 6, 6]
def test_wrong_casts():
    '''validate values that cannot be converted to the requested type.'''
    with pytest.raises(SettingsValueError) as excinfo:
        Value(int).validate('50.0')
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError

    with pytest.raises(SettingsValueError) as excinfo:
        Value(int).validate(5 + 0j)
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError

    with pytest.raises(SettingsValueError) as excinfo:
        Value(bytes).validate('asd')
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError
def test_optional():
    '''Test that optional values are validated if present, but no error is raised if not.'''
    d1 = {'age': 28, 'city': 'utrecht'}
    d2 = {'city': 'utrecht'}
    dval = DictValue({'age': Value(int, kind=Value.optional), 'city': str})
    assert dval.validate(d1) == d1
    assert dval.validate(d2) == d2
def test_wrong_generics():
    '''Test that using a generic without arguments fails'''
    with pytest.raises(SettingsTypeError) as excinfo:
        Value(List).validate([1, 2, 3])
    assert excinfo.match(
        "Invalid requested type \(List\), generic types must contain arguments."
    )
    assert excinfo.type == SettingsTypeError
def test_nested_lists_len():
    '''Test the length of nested lists'''
    assert Value(List[List[int]]).validate([[1, 2], [4, 5]]) == [[1, 2],
                                                                 [4, 5]]

    assert Value(List[List[int]],
                 len_max=[2, 2]).validate([[1, 2], [4, 5]]) == [[1, 2], [4, 5]]
    assert Value(List[List[int]],
                 len_min=[2, 3]).validate([[1, 2, 3], [4, 5,
                                                       6]]) == [[1, 2, 3],
                                                                [4, 5, 6]]
    assert Value(List[List[int]],
                 len_max=[None, 2]).validate([[1, 2], [4, 5], [4,
                                                               5]]) == [[1, 2],
                                                                        [4, 5],
                                                                        [4, 5]]

    with pytest.raises(SettingsValueError) as excinfo:
        Value(List[List[int]], len_max=[2, 2]).validate([[1, 2], [4, 5],
                                                         [7, 8]])
    assert excinfo.match('cannot be larger than')
    assert excinfo.type == SettingsValueError

    with pytest.raises(SettingsValueError) as excinfo:
        Value(List[List[int]], len_min=[2, 4]).validate([[1, 2, 3],
                                                         [4, 5, 6, 7]])
    assert excinfo.match('cannot be smaller than')
    assert excinfo.type == SettingsValueError

    with pytest.raises(SettingsValueError) as excinfo:
        Value(List[List[int]], len_max=[None, 2]).validate([[1, 2], [4, 5],
                                                            [4, 5, 5]])
    assert excinfo.match('cannot be larger than')
    assert excinfo.type == SettingsValueError
def test_exclusive():
    '''Test exclusive values.'''
    d1 = {'age': 28}
    dval = DictValue({'age': Value(int, kind=Value.exclusive)})
    assert dval.validate(d1) == d1
    dval2 = DictValue({
        'age': Value(int, kind=Value.exclusive),
        'city': Value(str, kind=Value.exclusive)
    })
    # only one of them is present, ok
    assert dval2.validate(d1) == d1

    d2 = {'age': 28, 'city': 'utrecht'}
    with pytest.raises(SettingsValueError) as excinfo:
        dval2.validate(d2)
    assert excinfo.match("Only one of the values")
    assert excinfo.match("can be present at the same time")
    assert excinfo.type == SettingsValueError
def test_own_types():
    '''Test that user-defined types, such as combination of other types, work.'''
    class f_float:
        '''Simulates a type that converts numbers or str into floats through Fraction.'''
        def __new__(cls, x: str) -> float:
            '''Return the float'''
            return float(Fraction(x))  # type: ignore

    f_float.__name__ = 'float(Fraction())'

    # good
    assert Value(f_float).validate('4/5') == 0.8

    # wrong
    with pytest.raises(SettingsValueError) as excinfo:
        Value(f_float).validate('asd')
    assert excinfo.match(
        r"does not have the right type \(float\(Fraction\(\)\)\).")
    assert excinfo.type == SettingsValueError
def test_num_to_num():
    '''Test converions between numbers of different types.'''

    # int, Fraction to float
    assert Value(float).validate(25) == 25.0
    assert Value(float).validate(Fraction(89, 5)) == 17.8
    # float, Fraction to int
    assert Value(int).validate(12.2) == 12
    assert Value(int).validate(Fraction(10, 5)) == 2
    # int, float to Fraction
    assert Value(Fraction).validate(7) == Fraction(7, 1)
    assert Value(Fraction).validate(56.2) == Fraction(3954723422784717,
                                                      70368744177664)
    # int, float, Fraction to complex
    assert Value(complex).validate(25) == 25 + 0j
    assert Value(complex).validate(12.2) == 12.2 + 0j
    assert Value(complex).validate(Fraction(10, 5)) == 2 + 0j
def test_DictValue_in_Dict():
    '''Test the combination of DictValue and Dict.'''
    d = {
        'key1': {
            'age': 28,
            'city': 'utrecht'
        },
        'key2': {
            'age': 52,
            'city': 'london'
        },
        'key3': {
            'age': 24,
            'city': 'rome'
        }
    }
    assert Value(Dict[str, DictValue({
        'age': int,
        'city': str
    })]).validate(d) == d

    # wrong key type
    with pytest.raises(SettingsValueError) as excinfo:
        assert Value(Dict[int, DictValue({
            'age': int,
            'city': str
        })]).validate(d) == d
    assert excinfo.match("Setting")
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError

    # wrong key type
    with pytest.raises(SettingsValueError) as excinfo:
        assert Value(Dict[int, DictValue({
            'age': int,
            'city': str
        })]).validate(45) == d
    assert excinfo.match("Setting")
    assert excinfo.match("does not have the right type")
    assert excinfo.match("This type can only validate dictionaries")
    assert excinfo.type == SettingsValueError
def test_argument_expansion():
    '''Test types with more than one argument in the constructor.'''
    import datetime

    with pytest.raises(SettingsValueError) as excinfo:
        Value(datetime.date).validate([2015, 5, 3])
    assert excinfo.match('does not have the right type')
    assert excinfo.type == SettingsValueError

    assert Value(datetime.date, expand_args=True).validate(
        (2015, 5, 3)) == datetime.date(2015, 5, 3)
    assert Value(datetime.date, expand_args=True).validate({
        'year': 2015,
        'month': 5,
        'day': 3
    }) == datetime.date(2015, 5, 3)

    with pytest.raises(SettingsValueError) as excinfo:
        Value(datetime.date, expand_args=True).validate(2015)
    assert excinfo.match('Expected a list or a dictionary')
    assert excinfo.type == SettingsValueError
def test_nested_lists():
    '''Test lists of lists of simple types'''
    assert Value(List[List[int]]).validate([[1, 2, 3], [4, 5,
                                                        6]]) == [[1, 2, 3],
                                                                 [4, 5, 6]]
    assert Value(List[List[str]]).validate([['asd', 'dsa'],
                                            ['t', 'y',
                                             'i']]) == [['asd', 'dsa'],
                                                        ['t', 'y', 'i']]
    assert Value(List[Set[Tuple[int,
                                float]]]).validate([[[1, 2], [1, 2]],
                                                    [[4, 5],
                                                     [6, 7]]]) == [{(1, 2.0)},
                                                                   {(4, 5.0),
                                                                    (6, 7.0)}]

    # this is a list of lists of str, and works
    assert Value(List[List[str]]).validate([['a'], ['s'], ['d'], ['d'], ['s'],
                                            ['a']]) == [['a'], ['s'], ['d'],
                                                        ['d'], ['s'], ['a']]
    assert Value(List[List[List[str]]]).validate([[[1], [2]],
                                                  [[4],
                                                   [5]]]) == [[['1'], ['2']],
                                                              [['4'], ['5']]]

    with pytest.raises(SettingsValueError) as excinfo:
        # this is a list of str, not lists of list of str (even though str is iterable)!!
        Value(List[List[str]]).validate(['asddsa'])
    assert excinfo.match('does not have the right type')
    assert excinfo.type == SettingsValueError
def test_simple_list_len():
    '''Test the max and min length of a simple list.'''
    assert Value(List[int], len_max=4, len_min=2).validate([1, 2,
                                                            3]) == [1, 2, 3]
    assert Value(List[int], len_max=4, len_min=1).validate([1, 2]) == [1, 2]
    assert Value(List[int], len_max=4,
                 len_min=2).validate([1, 2, 3, 4]) == [1, 2, 3, 4]
    assert Value(List[int], len_max=3, len_min=3).validate([1, 2,
                                                            3]) == [1, 2, 3]
    assert Value(List[int], len_min=3).validate([1, 2, 3, 4]) == [1, 2, 3, 4]
    assert Value(List[int], len_max=3).validate([1, 2]) == [1, 2]

    with pytest.raises(SettingsValueError) as excinfo:
        Value(List[int], len_max=4, len_min=2).validate([1])
    assert excinfo.match('cannot be smaller than')
    assert excinfo.type == SettingsValueError

    with pytest.raises(SettingsValueError) as excinfo:
        Value(List[int], len_max=4, len_min=2).validate([1, 2, 3, 4, 5])
    assert excinfo.match('cannot be larger than')
    assert excinfo.type == SettingsValueError
def test_simple_lists():
    '''Test lists of simple types.'''
    assert Value(List[int]).validate([1, 2]) == [1, 2]
    assert Value(List[float]).validate([1, 2.5, -9.1]) == [1, 2.5, -9.1]
    assert Value(List[str]).validate([1, 'a', 'asd',
                                      '\u2569']) == ['1', 'a', 'asd', '╩']
    assert Value(List[Union[int, str]]).validate([5, '6.0',
                                                  'a']) == [5, '6.0', 'a']

    with pytest.raises(SettingsValueError) as excinfo:
        Value(List[int]).validate('56')
    assert excinfo.match('does not have the right type')
    assert excinfo.type == SettingsValueError

    # empty list
    assert Value(List[int]).validate([]) == []
def test_nested_unions():
    '''Test Unions of Sequences and simple types'''
    assert Value(Union[int, List[int]]).validate(12) == 12
    assert Value(Union[int, List[int]]).validate([12]) == [12]
    assert Value(Union[int, List[int]]).validate([12, 13, 14]) == [12, 13, 14]

    assert Value(Union[List[int],
                       List[List[int]]]).validate([[1, 2, 3],
                                                   [4, 5, 6]]) == [[1, 2, 3],
                                                                   [4, 5, 6]]

    with pytest.raises(SettingsValueError) as excinfo:
        Value(Union[int, List[int]]).validate(5 + 1j)
    assert excinfo.match("does not have any of the right types")
    assert excinfo.type == SettingsValueError

    with pytest.raises(SettingsValueError) as excinfo:
        Value(Union[int, List[int]], len_max=2).validate([12, 13, 14])
    assert excinfo.match("does not have any of the right types")
    assert excinfo.type == SettingsValueError
def test_max_min_val():
    '''Test that max and min values work'''

    assert Value(int, val_max=30, val_min=5).validate(25) == 25
    assert Value(int, val_max=30, val_min=5).validate(30) == 30
    assert Value(int, val_max=30, val_min=5).validate(5) == 5

    with pytest.raises(SettingsValueError) as excinfo:
        Value(int, val_max=30, val_min=5).validate(50)
    assert excinfo.match("cannot be larger than")
    assert excinfo.type == SettingsValueError

    with pytest.raises(SettingsValueError) as excinfo:
        Value(int, val_max=30, val_min=5).validate(-5)
    assert excinfo.match("cannot be smaller than")
    assert excinfo.type == SettingsValueError

    # val_max/min can only be used with types that have __gt__/__lt__ methods
    with pytest.raises(SettingsValueError) as excinfo:
        Value(str, val_max=30, val_min=5).validate(5)
    assert excinfo.match("Value 5 of type str cannot be compared to")
    assert excinfo.type == SettingsValueError
def test_simple_sequences():
    '''Test sets'''
    assert Value(Set[float]).validate([1, 2.5, -9.1, 1]) == {1, 2.5, -9.1}
import sys
from typing import List, Tuple, Union, Dict
from settings_parser.value import Value, DictValue

class f_float(type):
    '''Type that converts numbers or str into floats through Fraction.'''
    def __new__(mcs, x: str) -> float:
        '''Return the float'''
        return float(Fraction(x))  # type: ignore
f_float.__name__ = 'float(Fraction())'

# smallest float number
min_float = sys.float_info.min

Vector = Tuple[f_float]
settings = [DictValue('lattice', [Value('name', str),
                                  Value('spacegroup', Union[int, str]),
                                  Value('S_conc', float, val_min=0, val_max=100),
                                  Value('A_conc', float, val_min=0, val_max=100),
                                  Value('a', float, val_min=0),
                                  Value('b', float, val_min=0),
                                  Value('c', float, val_min=0),
                                  Value('alpha', float, val_min=0, val_max=360),
                                  Value('beta', float, val_min=0, val_max=360),
                                  Value('gamma', float, val_min=0, val_max=360),
                                  Value('sites_pos', Union[Vector, List[Vector]],
                                        val_min=0, val_max=1,
                                        len_min=[1, 3], len_max=[None, 3]),
                                  Value('sites_occ', Union[f_float, List[f_float]],
                                        val_min=0, val_max=1, len_min=1),
                                  Value('d_max', float, val_min=0, kind=Value.optional),
def test_simple_dicts():
    '''Test the parsing of simple dictionaries.'''
    d = {'a': '1'}
    assert Value(Dict[str, str]).validate(d) == d
    assert Value(Dict[str, int]).validate(d) == {'a': 1}
    assert Value(Dict[str, float]).validate(d) == {'a': 1.0}
    assert Value(Dict[str, complex]).validate(d) == {'a': (1 + 0j)}

    assert Value(Dict[str, str]).validate({}) == {}

    d4 = {'a': 1, 'b': '3', 'c': 56.2}
    assert Value(Dict[str, int]).validate(d4) == {'a': 1, 'b': 3, 'c': 56}
    assert Value(Dict[str, str]).validate(d4) == {
        'a': '1',
        'b': '3',
        'c': '56.2'
    }

    d2 = {1: 5}
    assert Value(Dict[str, str]).validate(d2) == {'1': '5'}
    assert Value(Dict[str, int]).validate(d2) == {'1': 5}
    assert Value(Dict[int, str]).validate(d2) == {1: '5'}
    assert Value(Dict[int, int]).validate(d2) == {1: 5}

    assert Value(Dict[str, Union[int, str]]).validate({'a': 1}) == {'a': 1}
    assert Value(Dict[str, Union[int, str]]).validate({'a': '1'}) == {'a': 1}
    assert Value(Dict[str, Union[int, str]]).validate({'a': 'b'}) == {'a': 'b'}

    assert Value(Dict[str, List[int]]).validate({'a': [1, 2, 3]}) == {
        'a': [1, 2, 3]
    }
    assert Value(Dict[str, List[int]],
                 len_max=[1, 3]).validate({'a': [1, 2, 3]}) == {
                     'a': [1, 2, 3]
                 }

    assert Value(Dict[str, Set[int]],
                 len_max=[1, 3]).validate({'a': [1, 2, 2]}) == {
                     'a': {1, 2}
                 }

    d3 = {(1, 2): [3, 4]}
    assert Value(Dict[Tuple[int, int], List[int]]).validate(d3) == d3
    # this works too
    assert Value(Dict[tuple, List[int]]).validate(d3) == d3

    with pytest.raises(SettingsValueError) as excinfo:
        Value(Dict[int, int]).validate(d)
    assert excinfo.match("Setting")
    assert excinfo.match("\(value: 'a', type: str\)")
    assert excinfo.match('does not have the right type')
    assert excinfo.type == SettingsValueError

    with pytest.raises(SettingsValueError) as excinfo:
        Value(Dict[str, Union[int, complex]]).validate({'a': 'b'})
    assert excinfo.match("Setting")
    assert excinfo.match("\(value: 'b', type: str\)")
    assert excinfo.match('does not have the right type')
    assert excinfo.type == SettingsValueError

    with pytest.raises(SettingsValueError) as excinfo:
        Value(Dict[str, int]).validate(None)
    assert excinfo.match("Setting")
    assert excinfo.match("\(value: None, type: NoneType\)")
    assert excinfo.match('does not have the right type')
    assert excinfo.type == SettingsValueError

    with pytest.raises(SettingsValueError) as excinfo:
        Value(Dict[str, int]).validate(5)
    assert excinfo.match("Setting")
    assert excinfo.match('does not have the right type')
    assert excinfo.match("This type can only validate dictionaries")
    assert excinfo.type == SettingsValueError
def test_always_right_casts(value):
    '''Parsing a value of the same type as expected should aways work
        and return the same value.'''
    assert Value(type(value)).validate(value) == value
    assert type(Value(type(value)).validate(value)) == type(value)
def test_DictValue():
    '''Test DictValues'''
    d = {'age': 28, 'city': 'utrecht'}
    # simple type list
    dval1 = DictValue({'age': int, 'city': str})
    assert dval1.validate(d) == d
    # Value list
    dval2 = DictValue({'age': Value(int, val_min=0), 'city': Value(str)})
    assert dval2.validate(d) == d
    # mixed
    dval3 = DictValue({'age': Value(int, val_min=0), 'city': str})
    assert dval3.validate(d) == d
    # both methods create the same DictValues, their reprs are equal
    assert repr(dval1) == repr(dval2)
    assert repr(dval1) == repr(dval3)

    d = {'age': 28}
    # list of names and types is not a valid constructor
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue(['age', str]).validate(d)
    assert excinfo.match("The first argument must be a dictionary")
    assert excinfo.type == SettingsValueError

    d = 'not a dictionary'
    # DictValues only validate dictionaries
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue({'age': int}).validate(d)
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError

    # extra key:value pairs are ok, but they give off a warning and are ignored
    d = {'age': 28, 'city': 'utrecht'}
    d2 = {'age': 28, 'city': 'utrecht', 'extra': True}
    with pytest.warns(SettingsExtraValueWarning) as warnings:
        assert DictValue({'age': int, 'city': str}).validate(d2) == d
    assert len(warnings) == 1  # one warning
    warning = warnings.pop(SettingsExtraValueWarning)
    assert warning.category == SettingsExtraValueWarning
    assert 'Some values or sections should not be present in the file' in str(
        warning.message)

    d = {'age': 28}
    # the dict doesn't contain all keys
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue({'age': int, 'city': str}).validate(d)
    assert excinfo.match("Setting ")
    assert excinfo.match("not in dictionary")
    assert excinfo.type == SettingsValueError

    d = {'ag': 28, 'city': 'utrecht'}
    # wrong key name
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue({'age': int, 'city': str}).validate(d)
    assert excinfo.match("Setting ")
    assert excinfo.match("not in dictionary")
    assert excinfo.type == SettingsValueError

    d = {'age': 'asd', 'city': 'utrecht'}
    # wrong value type
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue({'age': int, 'city': str}).validate(d)
    assert excinfo.match("Setting ")
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError

    # DictValue in Value
    d = {'age': 28, 'city': 'utrecht'}
    assert Value(DictValue({'age': int, 'city': str})).validate(d) == d