def test_merge_list_with_oneof(self): "Non-rule node in list's inner spec" # TODO: refactor and reformulate. # This test was written for older implementation of Monk. int_spec = IsA(int, default=123) str_spec = IsA(str, default='foo') # no defaults #rule = Rule(datatype=list, inner_spec=OneOf([123, 456])) item_spec = Any([int_spec, str_spec]) rule = ListOf(item_spec) assert rule.get_default_for([]) == [] assert rule.get_default_for([789]) == [789] # with defaults # (same results because defaults have no effect on lists) #rule = Rule(datatype=list, # inner_spec=OneOf([123, 456], first_is_default=True)) item_spec = Any([int_spec, str_spec], first_is_default=True) rule = ListOf(item_spec) assert rule.get_default_for([]) == [] assert rule.get_default_for([789]) == [789]
def test_translate_list(): assert translate(list) == IsA(list) assert translate([]) == IsA(list) assert translate([int]) == ListOf(IsA(int)) assert translate([1]) == ListOf(IsA(int, default=1)) with raises_regexp(StructureSpecificationError, 'Expected a list containing exactly 1 item; ' 'got 3: \[1, 2, 3\]'): translate([1, 2, 3])
def test_combinator_any(): v = Any([ IsA(str), IsA(int) ]) assert repr(v) == '(must be str or must be int)' v('foo') v(123) with raises_regexp(ValidationError, '^must be str or must be int$'): v(4.5)
def test_list(self): assert translate(list) == IsA(list) assert translate([]) == IsA(list) with pytest.raises(errors.StructureSpecificationError) as excinfo: translate([1, 2]) assert ( "StructureSpecificationError: Expected a list " "containing exactly 1 item; got 2: [1, 2]") in excinfo.exconly()
def test_validate_optional_one_of_in_a_list(self): # unset "optional" should work exactly as in a Rule schema = translate([IsA(str) | IsA(int)]) with raises(ValidationError): schema([]) schema = translate([optional(IsA(str) | IsA(int))]) schema([]) schema([123]) schema([123, 'sss']) with raises(ValidationError): schema([123, 'sss', 999.999])
def test_merge_dictof(self): ## present non-empty inner spec (optional) spec = DictOf([ (Equals('a'), IsA(int, default=1)), ]) | Equals(None) # optional missing dictionary with required key assert spec.get_default_for(None) == None # optional empty dictionary with required key assert spec.get_default_for({}) == {'a': 1} ## present non-empty inner spec (optional) spec = DictOf([ (Equals('a'), IsA(int, default=1) | Equals(None)), ]) # optional missing dictionary with optional key # XXX CHANGED #assert spec.get_default_for(None) == None assert spec.get_default_for(None) == {'a': None} # optional empty dictionary with optional key # XXX CHANGED # (if the value can be either int or None, why choose one? # here we set None not because of Equals(None) but because # we *mean* None — no value could be chosen. #assert spec.get_default_for({}) == {'a': 1} assert spec.get_default_for({}) == {'a': None} ## present non-empty inner spec (required) spec = DictOf([ (Equals('a'), IsA(int, default=1)), ]) # required missing dictionary → inner spec assert spec.get_default_for({}) == {'a': 1} # required empty dictionary → inner spec assert spec.get_default_for({}) == {'a': 1} # XXX CHANGED ## required non-empty dictionary → inner spec #fallback = lambda s, v, **kw: v #assert merge_defaults(rule, {'a': 2}, {}, fallback) == {'a': 2} #assert merge_defaults(rule, {'b': 3}, {}, fallback) == {'a': 1, 'b': 3} # bogus value; will not pass validation but should be preserved assert spec.get_default_for(123) == 123
def test_translate_dict(): assert translate(dict) == IsA(dict) assert translate({}) == IsA(dict) # literal as a key assert translate({'foo': 123}) == DictOf([ (Equals('foo'), IsA(int, default=123)), ]) assert translate({123: str}) == DictOf([ (Equals(123), IsA(str)), ]) # validator as a key assert translate({Equals('foo') | Equals('bar'): str}) == DictOf([ (Equals('foo') | Equals('bar'), IsA(str)), ]) # type as a key assert translate({str: int}) == DictOf([ (IsA(str), IsA(int)), ]) # function as a key func = lambda: 'foo' assert translate({func: int}) == DictOf([ (Equals('foo'), IsA(int)), ])
def test_magic_and_or(): v = IsA(str) | IsA(int) assert isinstance(v, Any) assert repr(v) == '(must be str or must be int)' v = IsA(str) & IsA(int) # silly but who cares assert isinstance(v, All) assert repr(v) == '(must be str and must be int)' v = IsA(str) & IsA(int) | IsA(float) assert repr(v) == '((must be str and must be int) or must be float)' v = IsA(str) | IsA(int) & IsA(float) assert repr(v) == '(must be str or (must be int and must be float))'
def test_merge_oneof(self): str_rule = IsA(str, default='hello') int_rule = IsA(int, default=123) schema = Any([str_rule, int_rule]) assert schema.get_default_for(None) == None schema = Any([str_rule, int_rule], default=456) assert schema.get_default_for(None) == 456 schema = Any([str_rule, int_rule], first_is_default=True) assert schema.get_default_for(None) == 'hello' schema = Any([int_rule, str_rule], first_is_default=True) assert schema.get_default_for(None) == 123
def test_list_of_all(): v = ListOfAll(IsA(str)) assert repr(v) == 'ListOfAll(must be str)' with raises_regexp(ValidationError, '^must be list$'): v('foo') with raises_regexp(ValidationError, '^lacks item: must be str$'): v([]) # positive v(['foo']) v(['foo', 'bar']) with raises_regexp(ValidationError, '^item #2: must be str$'): v(['foo', 'bar', 123]) # negated v = ~v with raises_regexp(ValidationError, '^~ListOfAll\(must be str\)$'): v(['foo']) with raises_regexp(ValidationError, '^~ListOfAll\(must be str\)$'): v(['foo', 'bar']) v(['foo', 'bar', 123])
def test_list_of_any(): v = ListOfAny(IsA(str)) assert repr(v) == 'ListOfAny(must be str)' with raises_regexp(ValidationError, '^must be list$'): v('foo') with raises_regexp(ValidationError, '^lacks item: must be str$'): v([]) # positive v(['foo']) v(['foo', 123]) with raises_regexp(ValidationError, '^item #0: must be str or item #1: must be str$'): v([123, 5.5]) # negated v = ~v with raises_regexp(ValidationError, '^~ListOfAny\(must be str\)$'): v(['foo']) with raises_regexp(ValidationError, '^~ListOfAny\(must be str\)$'): v(['foo', 123]) v([123, 5.5])
def test_merge_isa__dict(self): # TODO use a single test for all types used with IsA # optional missing dictionary assert (IsA(dict) | Equals(None)).get_default_for(None) == None # XXX CHANGED ## required missing dictionary → empty dictionary #assert IsA(dict).get_default_for(None) == {} assert IsA(dict).get_default_for(None) == None # required empty dictionary assert IsA(dict).get_default_for({}) == {} # required non-empty dictionary assert IsA(dict).get_default_for({'x': 1}) == {'x': 1}
def test_opt_key__py2_unicode(self): raw = { opt_key(unicode('foo')): int, } assert translate(raw) == DictOf([ (Equals(unicode('foo')) | ~Exists(), IsA(int)), ])
def test_opt_key__str(self): # this also means Unicode for Py3 but it's OK raw = { opt_key('foo'): int, } assert translate(raw) == DictOf([ (Equals('foo') | ~Exists(), IsA(int)), ])
def test_isa(): v = IsA(str) assert repr(v) == 'must be str' v('foo') with raises_regexp(ValidationError, '^must be str'): v(123)
def test_combinator_edge_cases(): with raises_regexp(TypeError, 'got Exists class instead of its instance'): IsA(str) | Exists # translate() is applied to the right argument assert IsA(str) | 'Albatross!' == IsA(str) | IsA(str, default='Albatross!') assert IsA(str) | None == IsA(str) | Anything() assert Length(min=3) & [] == Length(min=3) & IsA(list)
def test_regression_22(): ~Anything() ~IsA(str) ~Equals(5) ~Contains('x') ~InRange(5) ~Length(5) ~ListOf(str) ~DictOf([]) ~Exists()
def test_missing(self): # MISSING KEY dict_with_opt_key = translate({IsA(text_type) | ~Exists(): text_type}) dict_with_opt_key({}) dict_with_req_key_opt_value = translate( {'a': IsA(text_type) | Equals(None)}) with raises(MissingKeys): dict_with_req_key_opt_value({}) dict_with_req_key_req_value = translate({'a': IsA(text_type)}) with raises(MissingKeys): dict_with_req_key_req_value({}) dict_with_req_keys_req_values = translate({'a': text_type, 'b': int}) with raises(MissingKeys): dict_with_req_keys_req_values({'b': 1})
def test_typed_optional_dict(self): "A value of given type (dict) or no value" spec = IsA(dict) | Equals(None) # value is present spec({}) # value is missing spec(None)
def test_typed_optional_list(self): "A value of given type (list) or no value" spec = IsA(list) | Equals(None) # value is present spec([]) # value is missing spec(None)
def test_typed_required_dict(self): "A value of given type (dict)" spec = IsA(dict) # value is present spec({}) # value is missing with raises_regexp(ValidationError, 'must be dict'): spec(None)
def test_dictof(): # key may be missing dict_of_str_to_int_optional_keys = DictOf([ (IsA(str) | ~Exists(), IsA(int)), ]) dict_of_str_to_int_optional_keys({}) dict_of_str_to_int_optional_keys({'foo': 123}) with raises_regexp(InvalidKeys, '123'): dict_of_str_to_int_optional_keys({123: 456}) # key must be present, exact literals not specified dict_of_str_to_int = DictOf([ (IsA(str), IsA(int)), ]) with raises_regexp(MissingKeys, 'must have keys: must be str'): dict_of_str_to_int({}) dict_of_str_to_int({'foo': 123}) dict_of_str_to_int({'foo': 123, 'bar': 456}) with raises_regexp(InvalidKeys, '123'): dict_of_str_to_int({'foo': 123, 'bar': 456, 123: 'quux'}) with raises_regexp(ValidationError, "'quux' value must be int"): dict_of_str_to_int({'foo': 123, 'bar': 456, 'quux': 4.2})
def test_typed_required(self): "A value of given type" spec = IsA(int) # value is present and matches datatype spec(1) # value is present but does not match datatype with raises_regexp(ValidationError, 'must be int'): spec('bogus') # value is missing with raises_regexp(ValidationError, 'must be int'): spec(None)
def test_typed_required_list(self): "A value of given type (list)" spec = IsA(list) # value is present spec([]) with raises_regexp(ValidationError, 'must be list'): spec('bogus') # value is missing with raises_regexp(ValidationError, 'must be list'): spec(None)
def test_typed_optional(self): "A value of given type or no value" spec = IsA(int) | Equals(None) # value is present and matches datatype spec(1) # value is present and equals None spec(None) # value is present but does not match datatype with raises_regexp(AllFailed, "must be int or must equal None"): spec('bogus') # value is missing spec(None)
def test_merge_dictof_dictof_isa(self): raw_spec = { 'content': { 'text': t('hello'), }, } spec = translate(raw_spec) # make sure translation went as expected assert spec == DictOf([ (Equals('content'), DictOf([ (Equals('text'), IsA(t, default=t('hello'))), ])), ]) # make sure merging works as expected for nested dict assert raw_spec == spec.get_default_for({'content': {}}) # make sure merging works as expected for nested *and* root dicts assert raw_spec == spec.get_default_for({})
def test_rule(self): rule = IsA(str, default='abc') | IsA(int) assert rule == translate(rule)
def test_string(self): assert translate(str) == IsA(str) assert translate('foo') == IsA(str, default='foo')
def test_int(self): assert translate(int) == IsA(int) assert translate(5) == IsA(int, default=5)
def test_float(self): assert translate(float) == IsA(float) assert translate(.5) == IsA(float, default=.5)