def test_member_added_event(self): from cocktail.schema import Schema, String foo = Schema("foo") spam = Schema("spam") spam.inherit(foo) events = EventLog() events.listen(foo.member_added, spam.member_added) bar = String("bar") foo.add_member(bar) scrum = String("scrum") foo.add_member(scrum) event = events.pop(0) self.assertEqual(event.slot, foo.member_added) self.assertEqual(event.member, bar) event = events.pop(0) self.assertEqual(event.slot, foo.member_added) self.assertEqual(event.member, scrum) self.assertFalse(events)
def test_exclusive_expression(self): from cocktail.schema import Schema, String, Boolean from cocktail.schema.exceptions import (ValueRequiredError, NoneRequiredError) test_schema = Schema() test_schema.add_member(Boolean("enabled")) test_schema.add_member( String("field", exclusive=test_schema["enabled"])) # Valid states assert test_schema.validate({"enabled": False, "field": None}) assert test_schema.validate({"enabled": True, "field": "foo"}) # None required error errors = list( test_schema.get_errors({ "enabled": False, "field": "foo" })) assert len(errors) == 1 error = errors[0] assert isinstance(error, NoneRequiredError) # Required error errors = list(test_schema.get_errors({"enabled": True, "field": None})) assert len(errors) == 1 error = errors[0] assert isinstance(error, ValueRequiredError)
def test_disabled_implicit_schema_copy(self): from cocktail.schema import Schema, Adapter user_schema = get_user_schema() form_schema = Schema() adapter = Adapter() adapter.implicit_copy = False adapter.export_schema(user_schema, form_schema) self.assertFalse(form_schema.members())
def test_exclude_undefined_member(self): from cocktail.schema import Adapter, Schema adapter = Adapter() adapter.exclude("fictitious_member") user_schema = get_user_schema() form_schema = Schema() adapter.export_schema(user_schema, form_schema) self.assertFalse("fictitious_member" in form_schema.members())
def test_implicit_schema_copy(self): from cocktail.schema import Schema, Adapter user_schema = get_user_schema() adapter = Adapter() self.assertTrue(adapter.implicit_copy) # Exporting schemas form_schema = Schema() adapter.export_schema(user_schema, form_schema) self.assertEqual( len(list(user_schema.members().keys())), len(list(form_schema.members().keys())) ) self.assertEqual( set(user_schema.members().keys()), set(form_schema.members().keys()) ) # Importing schemas user_schema_2 = Schema() adapter.import_schema(form_schema, user_schema_2) self.assertEqual( list(form_schema.members().keys()), list(user_schema_2.members().keys()) )
def test_preserves_ordering_from_constructor(self): from cocktail.schema import Schema, Member member_list = [Member("m%d" % i) for i in range(5)] schema = Schema(members=member_list) assert schema.members_order == [m.name for m in member_list]
def __init__(self, type: Union[int, Default] = default, schema: Union[Member, Default] = default, content: Union[media_content, Default] = default, description: Union[str, Default] = default, headers: Union[List[Member], Default] = default): self.content = self.content.copy() if content is not default: self.content.update(content) # Normalize member lists / mappings to a Schema object if isinstance(schema, (Sequence, Mapping)): schema = Schema(members=schema) if type is not default: self.content.update( {type: { "schema": None if schema is default else schema }}) if schema is not default and type is default: raise ValueError( "Can't specify 'schema' without supplying a default response " "type") if description is not default: self.description = description if headers is default: self.headers = list(self.headers) else: self.headers = list(headers)
def test_simple_schema_copy(self): from cocktail.schema import Adapter, Schema, Copy adapter = Adapter() adapter.implicit_copy = False adapter.copy("id") adapter.copy("name") user_schema = get_user_schema() form_schema = Schema() adapter.export_schema(user_schema, form_schema) self.assertEqual(len(form_schema.members()), 2) self.assertEqual( set(form_schema.members().keys()), set(["id", "name"]) )
def test_fails_if_member_specifies_conflicting_relative_positions(self): from cocktail.schema import Schema, Member schema = Schema(members=[ Member("m1"), Member("m2", after_member="m1", before_member="m1") ]) assert_raises(ValueError, schema.ordered_members)
def test_base_isolation(self): from cocktail.schema import Schema base = Schema() derived = Schema() self._add_error(derived, "derived_error") derived.inherit(base) assert not list(base.get_errors({}))
def test_members_can_specify_relative_positions(self): from cocktail.schema import Schema, Member m1 = Member("m1") m2 = Member("m2") m3 = Member("m3") m4 = Member("m4", after_member="m5") m5 = Member("m5", before_member="m1") m6 = Member("m6", before_member="m3") schema = Schema() schema.members_order = ["m3", "m2"] schema.add_member(m1) schema.add_member(m2) schema.add_member(m3) schema.add_member(m4) schema.add_member(m5) schema.add_member(m6) ordered_members = schema.ordered_members() assert ordered_members == [m6, m3, m2, m5, m4, m1]
def test_match_one_to_one(self): from cocktail.schema import Schema, Reference from cocktail.schema.exceptions import SchemaIntegrityError a = Schema("a") b = Schema("b") a.add_member(Reference("rel_b", type=b, bidirectional=True)) b.add_member(Reference("rel_a", type=a, bidirectional=True)) self.assertTrue(a["rel_b"].related_end is b["rel_a"]) self.assertTrue(a["rel_b"].related_type is b) self.assertTrue(b["rel_a"].related_end is a["rel_b"]) self.assertTrue(b["rel_a"].related_type is a)
def test_match_many_to_many(self): from cocktail.schema import Schema, Reference, Collection from cocktail.schema.exceptions import SchemaIntegrityError a = Schema("a") b = Schema("b") a.add_member( Collection("rel_b", items=Reference(type=b), bidirectional=True)) b.add_member( Collection("rel_a", items=Reference(type=a), bidirectional=True)) self.assertTrue(a["rel_b"].related_end is b["rel_a"]) self.assertTrue(a["rel_b"].related_type is b) self.assertTrue(b["rel_a"].related_end is a["rel_b"]) self.assertTrue(b["rel_a"].related_type is a)
def test_no_match(self): from cocktail.schema import Schema, Reference from cocktail.schema.exceptions import SchemaIntegrityError a = Schema("a") b = Schema("b") a.add_member(Reference("rel_b", type=b, bidirectional=True)) b.add_member(Reference("rel_a", type=a)) def resolve_relation(): print(a["rel_b"].related_end) self.assertRaises(SchemaIntegrityError, resolve_relation) self.assertTrue(b["rel_a"].related_end is None)
def test_follows_explicit_ordering(self): from cocktail.schema import Schema, Member m1 = Member("m1") m2 = Member("m2") m3 = Member("m3") m4 = Member("m4") schema = Schema() schema.members_order = ["m1", "m4", "m3", "m2"] schema.add_member(m1) schema.add_member(m2) schema.add_member(m3) schema.add_member(m4) assert schema.ordered_members() == [m1, m4, m3, m2]
def test_callable(self): from cocktail.schema import Schema, String, Boolean from cocktail.schema.exceptions import ValueRequiredError test_schema = Schema() test_schema.add_member(Boolean("enabled")) test_schema.add_member( String("field", required=lambda ctx: ctx.get_value("enabled"))) assert test_schema.validate({"enabled": False, "field": None}) assert test_schema.validate({"enabled": True, "field": "foo"}) errors = list(test_schema.get_errors({"enabled": True, "field": None})) assert len(errors) == 1 error = errors[0] assert isinstance(error, ValueRequiredError)
def test_grouped_members(self): from cocktail.schema import Schema, Member a1 = Member("a1", member_group="a") a2 = Member("a2", member_group="a") b1 = Member("b1", member_group="b") b2 = Member("b2", member_group="b") z = Member("z") schema = Schema(members=[a1, b2, a2, z, b1]) schema.members_order = ["a2", "a1", "b2", "b1"] schema.groups_order = "a", "b" groups = schema.grouped_members() assert len(groups) == 3 assert all(isinstance(group, tuple) for group in groups) assert groups[0][0] == None assert groups[1][0] == "a" assert groups[2][0] == "b" assert list(groups[0][1]) == [z] assert list(groups[1][1]) == [a2, a1] assert list(groups[2][1]) == [b2, b1]
def test_scalar(self): from cocktail.schema import Schema, Integer, exceptions class Validable(object): def __init__(self, foo): self.foo = foo def __repr__(self): return "Validable(%r)" % self.foo self._test_validation( Schema(members={"foo": Integer()}), [Validable(None), Validable(1), Validable(15)], [Validable(""), Validable("hello, world!"), Validable(3.15)], exceptions.TypeCheckError)
def test_ignores_missing_anchors(self): from cocktail.schema import Schema, Member m1 = Member("m1", after_member="m3") m2 = Member("m2") schema = Schema() schema.members_order = ["m2"] schema.add_member(m1) schema.add_member(m2) assert schema.ordered_members() == [m2, m1]
def test_single_inheritance(self): from cocktail.schema import Schema base = Schema("base") self._add_error(base, "base_error") derived = Schema("derived") derived.inherit(base) errors = list(derived.get_errors({})) assert len(errors) == 1 assert errors[0].error_id == "base_error"
def test_implicitly_includes_unspecified_members(self): from cocktail.schema import Schema, Member m1 = Member("m1") m2 = Member("m2") m3 = Member("m3") m4 = Member("m4") schema = Schema() schema.members_order = ["m3", "m2"] schema.add_member(m1) schema.add_member(m2) schema.add_member(m3) schema.add_member(m4) ordered_members = schema.ordered_members() assert len(ordered_members) == 4 assert ordered_members[:2] == [m3, m2] assert set(ordered_members[2:]) == set([m1, m4])
def get_schema(self): from cocktail.schema import Schema, Reference, Collection schema = Schema() schema.add_member(Reference("parent", type=schema, bidirectional=True)) schema.add_member( Collection("children", items=Reference(type=schema), bidirectional=True)) return schema
def test_multiple_inheritance(self): from cocktail.schema import Schema base1 = Schema("base1") self._add_error(base1, "base1_error") base2 = Schema("base2") self._add_error(base2, "base2_error") derived = Schema("derived") derived.inherit(base1) derived.inherit(base2) errors = list(derived.get_errors({})) assert len(errors) == 2 assert errors[0].error_id == "base1_error" assert errors[1].error_id == "base2_error"
def test_fails_on_cyclic_relative_positions(self): from cocktail.schema import Schema, Member # Self references schema = Schema(members=[Member("m1", after_member="m1")]) assert_raises(ValueError, schema.ordered_members) schema = Schema(members=[Member("m1", before_member="m1")]) assert_raises(ValueError, schema.ordered_members) # 2 step cycle schema = Schema(members=[ Member("m1", before_member="m2"), Member("m2", before_member="m1") ]) assert_raises(ValueError, schema.ordered_members) schema = Schema(members=[ Member("m1", after_member="m2"), Member("m2", before_member="m1") ]) assert_raises(ValueError, schema.ordered_members) schema = Schema(members=[ Member("m1", before_member="m2"), Member("m2", after_member="m1") ]) assert_raises(ValueError, schema.ordered_members) schema = Schema(members=[ Member("m1", after_member="m2"), Member("m2", after_member="m1") ]) assert_raises(ValueError, schema.ordered_members) # 3 step cycle schema = Schema(members=[ Member("m1", before_member="m3"), Member("m2", before_member="m1"), Member("m3", before_member="m2") ]) assert_raises(ValueError, schema.ordered_members)
def get_user_schema(): import re from cocktail.schema import Schema, Integer, String, Boolean return Schema(members = { "id": Integer( required = True, unique = True, min = 1 ), "name": String( required = True, min = 4, max = 20, format = re.compile("^[a-zA-Z][a-zA-Z_0-9]*$") ), "enabled": Boolean( required = True, default = True ) })
def test_translated(self): from cocktail.schema import Schema, String, exceptions schema = Schema() schema.add_member(String("foo", translated=True, required=True)) validable = {"foo": {"ca": "Hola", "es": "Hola", "en": "Hello"}} assert not list(schema.get_errors(validable)) validable["foo"]["fr"] = None validable["foo"]["es"] = None errors = list(schema.get_errors(validable)) assert len(errors) == 2 assert set([error.language for error in errors]) == set(["fr", "es"])
def test_add_duplicated_validation_to_base_schema(self): from cocktail.schema import Schema def a(): pass s1 = Schema() s2 = Schema() s2.inherit(s1) s2.add_validation(a) v1 = list(s2.validations()) s1.add_validation(a) v2 = list(s2.validations()) assert v1 == v2
class SchemaCoercionTestCase(TestCase): def setUp(self): from cocktail.schema import Schema, Integer, String self.schema = Schema(members=[ Integer("num", required=True, min=5, default=50), String("text", min=5, default="cornucopia") ]) def test_can_ignore_errors(self): from itertools import product from cocktail.schema import Coercion for obj in [{ "num": 3, "text": None }, { "num": 20, "text": "hum" }, { "num": 3, "text": "hum" }, { "num": 15, "text": "spam!" }]: original = obj.copy() result = self.schema.coerce(obj, Coercion.NONE) assert result is obj assert result == original def test_can_replace_invalid_values_with_none(self): from cocktail.schema import Coercion # num invalid, text valid obj = {"num": 3, "text": None} result = self.schema.coerce(obj, Coercion.SET_NONE) assert obj is result assert result == {"num": None, "text": None} # num valid, text invalid obj = {"num": 20, "text": "hum"} result = self.schema.coerce(obj, Coercion.SET_NONE) assert obj is result assert result == {"num": 20, "text": None} # both invalid obj = {"num": 3, "text": "hum"} result = self.schema.coerce(obj, Coercion.SET_NONE) assert obj is result assert result == {"num": None, "text": None} # both valid obj = {"num": 15, "text": "spam!"} result = self.schema.coerce(obj, Coercion.SET_NONE) assert obj is result assert result == {"num": 15, "text": "spam!"} def test_can_replace_invalid_values_with_default(self): from cocktail.schema import Coercion default_num = self.schema["num"].default default_text = self.schema["text"].default # num invalid, text valid obj = {"num": 3, "text": None} result = self.schema.coerce(obj, Coercion.SET_DEFAULT) assert obj is result assert result == {"num": default_num, "text": None} # num valid, text invalid obj = {"num": 20, "text": "hum"} result = self.schema.coerce(obj, Coercion.SET_DEFAULT) assert obj is result assert result == {"num": 20, "text": default_text} # both invalid obj = {"num": 3, "text": "hum"} result = self.schema.coerce(obj, Coercion.SET_DEFAULT) assert obj is result assert result == {"num": default_num, "text": default_text} # both valid obj = {"num": 15, "text": "spam!"} result = self.schema.coerce(obj, Coercion.SET_DEFAULT) assert obj is result assert result == {"num": 15, "text": "spam!"} def test_can_raise_errors_immediately(self): from cocktail.schema import Coercion from cocktail.schema.exceptions import (InputError, MinValueError, MinLengthError) FI = Coercion.FAIL_IMMEDIATELY # num invalid, text valid obj = {"num": 3, "text": None} try: self.schema.coerce(obj, FI) except InputError as e: assert e.member is self.schema assert len(e.errors) == 1 error = e.errors[0] assert isinstance(error, MinValueError) assert error.member is self.schema["num"] else: raise AssertionError( f"Coercing {obj} should raise an input error on 'num'") # num valid, text invalid obj = {"num": 20, "text": "hum"} try: self.schema.coerce(obj, FI) except InputError as e: assert e.member is self.schema assert len(e.errors) == 1 error = e.errors[0] assert isinstance(error, MinLengthError) assert error.member is self.schema["text"] else: raise AssertionError( f"Coercing {obj} should raise a validation error on 'text'") # both invalid obj = {"num": 3, "text": "hum"} try: self.schema.coerce(obj, FI) except InputError as e: assert e.member is self.schema assert len(e.errors) == 1 assert e.errors[0].member in list(self.schema.members().values()) else: raise AssertionError( f"Coercing {obj} should raise a validation error") # both valid obj = {"num": 15, "text": "spam!"} original = obj.copy() result = self.schema.coerce(obj, FI) assert obj is result assert result == original def test_can_aggregate_errors(self): from cocktail.schema import Coercion from cocktail.schema.exceptions import InputError def bad_keys(e): keys = set() for error in e.errors: keys.update(member.name for member in error.invalid_members) return keys # num invalid, text valid obj = {"num": 3, "text": None} try: self.schema.coerce(obj, Coercion.FAIL) except InputError as e: assert bad_keys(e) == {"num"} else: raise AssertionError( f"Coercing {obj} should raise a validation error on 'num'") # num valid, text invalid obj = {"num": 20, "text": "hum"} try: self.schema.coerce(obj, Coercion.FAIL) except InputError as e: assert bad_keys(e) == {"text"} else: raise AssertionError( f"Coercing {obj} should raise a validation error on 'text'") # both invalid obj = {"num": 3, "text": "hum"} try: self.schema.coerce(obj, Coercion.FAIL) except InputError as e: assert bad_keys(e) == {"num", "text"} else: raise AssertionError( f"Coercing {obj} should raise a validation error") # both valid obj = {"num": 15, "text": "spam!"} original = obj.copy() result = self.schema.coerce(obj, Coercion.FAIL) assert obj is result assert result == original
def test_deep_inheritance(self): from cocktail.schema import Schema s1 = Schema() self._add_error(s1, "s1_error") s2 = Schema() s2.inherit(s1) self._add_error(s2, "s2_error") s3 = Schema() s3.inherit(s2) self._add_error(s3, "s3_error") s4 = Schema() s4.inherit(s3) errors = list(s4.get_errors({})) assert len(errors) == 3 assert errors[0].error_id == "s1_error" assert errors[1].error_id == "s2_error" assert errors[2].error_id == "s3_error"
def setUp(self): from cocktail.schema import Schema, Integer, String self.schema = Schema(members=[ Integer("num", required=True, min=5, default=50), String("text", min=5, default="cornucopia") ])