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