def test_instance_in_list_in_dict(self): spec = translate({}) assert spec.get_default_for({"a": [1]}) == {"a": [1]} spec = translate({"a": [0]}) assert spec.get_default_for({"a": [0]}) == {"a": [0]} assert spec.get_default_for({"a": [0, 1]}) == {"a": [0, 1]}
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_custom_validators_in_dict_keys(self): # FIXME this is one of those cases where "human-readable" errors # are much less readable than "mechanical" ones (as before). # Can we do anything? day_note_schema = translate({ InRange(2000, 2020): { InRange(1, 12): { InRange(1, 31): str, }, }, }) good_note = {2013: {12: {9: 'it is a good day today'}}} bad_note1 = {1999: {12: {9: 'wrong year: below min'}}} bad_note2 = {2013: {13: {9: 'wrong month: above max'}}} bad_note3 = {2013: {12: {40: 'wrong day of month: above max'}}} day_note_schema(good_note) #with raises_regexp(InvalidKeys, '^1999$'): with raises_regexp(ValidationError, "^must not have keys like 1999$"): day_note_schema(bad_note1) #with raises_regexp(InvalidKeys, '^2013: 13$'): with raises_regexp(ValidationError, "^2013 value must not have keys like 13$"): day_note_schema(bad_note2) #with raises_regexp(InvalidKeys, '^2013: 12: 40$'): with raises_regexp(ValidationError, "^in 2013 \(12 value must not have keys like 40\)$"): day_note_schema(bad_note3)
def test_int_in_list_in_dict_in_list_in_dict(self): spec = translate({'foo': [{'bar': [int]}]}) with raises_regexp(ValidationError, "'foo' value must be list"): spec({'foo': None}) with raises_regexp(ValidationError, "'foo' value item #0: 'bar' value must be list"): spec({'foo': [{'bar': None}]}) with raises_regexp(ValidationError, "'foo' value lacks item: must be dict"): spec({'foo': []}) with raises_regexp(ValidationError, "'foo' value item #0: 'bar' value lacks item: must be int"): spec({'foo': [{'bar': []}]}) spec({'foo': [{'bar': [1]}]}) spec({'foo': [{'bar': [1, 2]}]}) with raises_regexp(ValidationError, "'foo' value item #0: 'bar' value item #1: must be int"): spec({'foo': [{'bar': [1, 'bogus']}]})
def test_required_inside_optional_dict_in_dict(self): spec = translate({"foo": optional({"a": 1, "b": optional(2)})}) data = {} expected = {"foo": None} assert merge_defaults(spec, data) == expected data = {"foo": None} expected = {"foo": None} assert merge_defaults(spec, data) == expected data = {"foo": {}} # XXX CHANGED: # expected = {'foo': {'a': 1, 'b': 2}} expected = {"foo": {"a": 1, "b": None}} assert merge_defaults(spec, data) == expected data = {"foo": {"a": 3}} # XXX CHANGED: # expected = {'foo': {'a': 3, 'b': 2}} expected = {"foo": {"a": 3, "b": None}} assert merge_defaults(spec, data) == expected data = {"foo": {"b": 3}} expected = {"foo": {"a": 1, "b": 3}} assert merge_defaults(spec, data) == expected
def test_unexpected_dict_in_dict(self): """ Non-dictionary in spec, dict in data. Data is preserved though won't validate. """ spec = translate({"a": t}) data = {"a": {"b": 123}} assert spec.get_default_for(data) == data
def test_custom_validators_in_dict_keys(self): # FIXME this is one of those cases where "human-readable" errors # are much less readable than "mechanical" ones (as before). # Can we do anything? day_note_schema = translate({ InRange(2000, 2020): { InRange(1, 12): { InRange(1, 31): str, }, }, }) good_note = {2013: {12: {9: 'it is a good day today'}}} bad_note1 = {1999: {12: {9: 'wrong year: below min'}}} bad_note2 = {2013: {13: {9: 'wrong month: above max'}}} bad_note3 = {2013: {12: {40: 'wrong day of month: above max'}}} day_note_schema(good_note) #with raises_regexp(InvalidKeys, '^1999$'): with raises_regexp(ValidationError, "^must not have keys like 1999$"): day_note_schema(bad_note1) #with raises_regexp(InvalidKeys, '^2013: 13$'): with raises_regexp(ValidationError, "^2013 value must not have keys like 13$"): day_note_schema(bad_note2) #with raises_regexp(InvalidKeys, '^2013: 12: 40$'): with raises_regexp( ValidationError, "^in 2013 \(12 value must not have keys like 40\)$"): day_note_schema(bad_note3)
def test_instance_in_list_of_dicts_in_dict(self): spec = translate({"a": [{"b": 1}]}) assert spec.get_default_for({}) == {"a": []} assert spec.get_default_for({"a": []}) == {"a": []} assert spec.get_default_for({"a": [{}]}) == {"a": [{"b": 1}]} assert spec.get_default_for({"a": [{"b": 0}]}) == {"a": [{"b": 0}]}
def test_int_in_list_in_dict_in_list_in_dict(self): spec = translate({'foo': [{'bar': [int]}]}) with raises_regexp(ValidationError, "'foo' value must be list"): spec({'foo': None}) with raises_regexp(ValidationError, "'foo' value item #0: 'bar' value must be list"): spec({'foo': [{'bar': None}]}) with raises_regexp(ValidationError, "'foo' value lacks item: must be dict"): spec({'foo': []}) with raises_regexp( ValidationError, "'foo' value item #0: 'bar' value lacks item: must be int"): spec({'foo': [{'bar': []}]}) spec({'foo': [{'bar': [1]}]}) spec({'foo': [{'bar': [1, 2]}]}) with raises_regexp( ValidationError, "'foo' value item #0: 'bar' value item #1: must be int"): spec({'foo': [{'bar': [1, 'bogus']}]})
def test_callable_nested_in_dict(self): """ Nested callable defaults. """ spec = translate({"content": {"text": lambda: t("hello")}}) data = {} expected = {"content": {"text": t("hello")}} assert merge_defaults(spec, data) == expected
def test_unexpected_list_in_dict(self): """ Non-list in spec, list in data. Data is preserved though won't validate. """ spec = translate({"a": t}) data = {"a": [123]} assert spec.get_default_for(data) == data
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_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_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_list_type(self): v = translate(list) with raises_regexp(ValidationError, 'must be list'): v(None) v([]) v(['hi']) v([1234])
def test_list_obj_empty(self): v = translate([]) with raises_regexp(ValidationError, 'must be list'): v(None) v([]) v([None]) v(['hi']) v([1234])
def test_list_with_opt_elem(self): v = translate([optional(str)]) with raises_regexp(ValidationError, 'must be list'): v(None) v([]) with raises_regexp(ValidationError, 'must be str or must not exist'): v([None]) v(['hi']) with raises_regexp(ValidationError, 'must be str'): v([1234])
def test_list_with_req_elem(self): v = translate([str]) with raises_regexp(ValidationError, 'must be list'): v(None) with raises_regexp(ValidationError, 'lacks item: must be str'): v([]) with raises_regexp(ValidationError, '#0: must be str'): v([None]) v(['hi']) with raises_regexp(ValidationError, '#0: must be str'): v([1234])
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_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_int_in_dict_in_dict(self): "A required int nested in a required dict nested in another required dict" spec = translate({'foo': {'bar': int}}) # inner key is missing with raises_regexp(ValidationError, "^'foo' value must have keys: 'bar'$"): spec({'foo': {}}) # inner key is present, inner value is missing with raises_regexp(ValidationError, "^in 'foo' \('bar' value must be int\)$"): spec({'foo': {'bar': None}}) # inner value is present spec({'foo': {'bar': 123}})
def test_int_in_dict(self): "A required int nested in a required dict" spec = translate({'foo': int}) # key is missing with raises_regexp(MissingKeys, "must have keys: 'foo'"): spec({}) # key is present, value is missing with raises_regexp(ValidationError, "'foo' value must be int"): spec({'foo': None}) # key is present, value is present spec({'foo': 1})
def test_dict_in_dict(self): "A required dict nested in another required dict" spec = translate({'foo': dict}) # key is missing with raises_regexp(MissingKeys, "must have keys: 'foo'"): spec({}) # key is present, value is missing with raises_regexp(ValidationError, "'foo' value must be dict"): spec({'foo': None}) # value is present validate(spec, {'foo': {}})
def test_type_in_dict_in_dict(self): spec = translate({"a": {"b": int}}) # key is absent; should be inserted assert spec.get_default_for({}) == {"a": {"b": None}} # same with nested key assert spec.get_default_for({"a": {}}) == {"a": {"b": None}} # key is present but value is None; should be overridden with defaults # # XXX do we really need to override *present* values in data # even if they are None? # assert spec.get_default_for({"a": None}) == {"a": {"b": None}} assert spec.get_default_for({"a": {"b": None}}) == {"a": {"b": None}} # key is present, value is not None; leave as is # (even if it won't pass validation) assert spec.get_default_for({"a": {"b": 1234}}) == {"a": {"b": 1234}} assert spec.get_default_for({"a": t("bogus string")}) == {"a": t("bogus string")}
def test_int_in_optional_dict(self): "A required int nested in an optional dict" spec = translate({'foo': int}) | Equals(None) # outer optional value is missing spec(None) # outer optional value is present, inner key is missing with raises_regexp(AllFailed, "must have keys: 'foo' or must equal None"): spec({}) # inner key is present, inner value is missing with raises_regexp(AllFailed, "'foo' value must be int or must equal None"): spec({'foo': None}) # inner value is present spec({'foo': 123})
def test_validator_in_dict(self): spec = translate({"foo": IsA(str, default="bar")}) data = {} expected = {"foo": "bar"} assert merge_defaults(spec, data) == expected
def test_type_in_list_in_dict(self): spec = translate({"a": [int]}) assert spec.get_default_for({"a": []}) == {"a": []} assert spec.get_default_for({"a": [123]}) == {"a": [123]} assert spec.get_default_for({"a": [123, 456]}) == {"a": [123, 456]}
def test_custom_structures(self): "custom keys should not be lost even if they are not in spec" spec = translate({}) data = {"a": [{"b": {"c": 123}}]} assert spec.get_default_for(data) == data
def test_rule_in_list(self): spec = translate({"a": [IsA(int)]}) assert spec.get_default_for({"a": []}) == {"a": []} assert spec.get_default_for({"a": None}) == {"a": []} assert spec.get_default_for({}) == {"a": []}
def test_instance_in_dict(self): spec = translate({"a": {"b": 1}}) assert spec.get_default_for({}) == {"a": {"b": 1}}