From c1f1895d63713057a89d7a00388e53c8a8e0afd2 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sat, 18 May 2024 15:35:43 -0400 Subject: [PATCH 01/16] tests: skip coverage for repr et al --- .coveragerc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.coveragerc b/.coveragerc index 621aa8f..5ab5689 100644 --- a/.coveragerc +++ b/.coveragerc @@ -12,3 +12,9 @@ source = [report] show_missing = true precision = 2 +exclude_also = + def __repr__ + + if 0: + + raise NotImplementedError From 6c08e30c988be9d0d86bd605940645acbd3c7699 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sat, 18 May 2024 15:36:20 -0400 Subject: [PATCH 02/16] tests: skip coverage for insane 'asdict' etc --- src/colander/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/colander/__init__.py b/src/colander/__init__.py index 06efc1f..a7ba3f7 100644 --- a/src/colander/__init__.py +++ b/src/colander/__init__.py @@ -199,7 +199,7 @@ def _keyname(self): return str(self.pos) return str(self.node.name) - def asdict(self, translate=None, separator='; '): + def asdict(self, translate=None, separator='; '): # pragma NO COVER """Return a dict holding a basic error report for this exception. The values in the dict will **not** be language-translated by @@ -231,7 +231,7 @@ def asdict(self, translate=None, separator='; '): errors['.'.join(keyparts)] = msgs return errors - def __str__(self): + def __str__(self): # pragma NO COVER return pprint.pformat(self.asdict()) From 85b1d997973991cca440a9e96c7b8b0184186df2 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sat, 18 May 2024 15:37:18 -0400 Subject: [PATCH 03/16] tests: add explicit coverage for '_required'/'_null'/'_drop' --- tests/test_singletons.py | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/test_singletons.py diff --git a/tests/test_singletons.py b/tests/test_singletons.py new file mode 100644 index 0000000..e2f2d13 --- /dev/null +++ b/tests/test_singletons.py @@ -0,0 +1,45 @@ +import pytest + + +def test__required___reduce__(): + from colander import _required + + not_the_singleton = _required() + + assert not_the_singleton.__reduce__() == "required" + + +def test_required_pickling(): + import pickle + from colander import required as the_singleton + from colander import _required + + pickled = pickle.dumps(the_singleton) + + unpickled = pickle.loads(pickled) + + assert unpickled is the_singleton + + +def test__null___bool__(): + from colander import _null + + not_the_singleton = _null() + + assert not not_the_singleton + + +def test__null___reduce__(): + from colander import _null + + not_the_singleton = _null() + + assert not_the_singleton.__reduce__() == "null" + + +def test__drop___reduce__(): + from colander import _drop + + not_the_singleton = _drop() + + assert not_the_singleton.__reduce__() == "drop" From 9f444086b386d8aab3394e3784e9e2d039d81617 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sat, 18 May 2024 15:37:53 -0400 Subject: [PATCH 04/16] tests: add explicit coveage for 'interpolate'/'is_nonstr_iter' --- tests/test_utils.py | 69 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/test_utils.py diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..4cafa0a --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,69 @@ +import pytest + + +def test_interpolate_w_empty_msgs(): + from colander import interpolate + + msgs = () + + result = list(interpolate(msgs)) + + assert result == [] + + +def test_interpolate_w_msg_strings(): + from colander import interpolate + + msgs = ("abc", "def") + + result = list(interpolate(msgs)) + + assert result == ["abc", "def"] + + +def test_interpolate_w_msg_objects_w_interpolate(): + from colander import interpolate + + class UppercaseDammit: + + def __init__(self, value): + self.value = value + + def interpolate(self): + return self.value.upper() + + msgs = [UppercaseDammit(x) for x in ("abc", "def")] + + result = list(interpolate(msgs)) + + assert result == ["ABC", "DEF"] + + +def test_interpolate_w_msg_objects_wo_interpolate(): + from colander import interpolate + + abc, def_ = object(), object() + msgs = [abc, def_] + + result = list(interpolate(msgs)) + + assert result == [abc, def_] + + +@pytest.mark.parametrize( + "value, expected", + [ + (None, False), + (object(), False), + ("", False), + ((), True), + ([], True), + ({}, True), + ], +) +def test_is_nonstr_iter(value, expected): + from colander import is_nonstr_iter + + result = is_nonstr_iter(value) + + assert result == expected From 1289cab5786bc359123d9b7fa5afbc2c7a15ef89 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sat, 18 May 2024 15:38:55 -0400 Subject: [PATCH 05/16] tests: add explicit coverage for 'Invalid'/'UnsupportedFields' --- tests/test_exceptions.py | 256 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 tests/test_exceptions.py diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py new file mode 100644 index 0000000..570d85d --- /dev/null +++ b/tests/test_exceptions.py @@ -0,0 +1,256 @@ +from unittest import mock + +import pytest + + +@pytest.fixture(scope="function", params=[ + "String", "Integer", "Float", "Decimal", "Boolean", +]) +def non_positional_node(request): + import colander + + node_klass = getattr(colander, request.param) + yield node_klass + + +@pytest.fixture(scope="function", params=["Tuple", "Sequence"]) +def positional_node(request): + import colander + + node_klass = getattr(colander, request.param) + yield node_klass + + +@pytest.mark.parametrize( + "msg, expected", [ + ((), []), + ([], []), + (None, []), + ("MESSAGE", ["MESSAGE"]), +]) +def test_invalid_messages(msg, expected): + import colander + + node = object() + invalid = colander.Invalid(node, msg) + + assert list(invalid.messages()) == expected + + +_MARKER = object() + + +@pytest.mark.parametrize("pos", [_MARKER, None, 23]) +def test_invalid_add_w_non_positional_node(non_positional_node, pos): + import colander + + node = colander.SchemaNode( + non_positional_node(), + colander.SchemaNode( + name="child", + typ=colander.String(), + ) + ) + invalid = colander.Invalid(node) + child_exc = colander.Invalid(object(), "testing") + + if pos is _MARKER: + invalid.add(child_exc) + else: + invalid.add(child_exc, pos=pos) + + assert not child_exc.positional + + if pos not in (_MARKER, None): + assert child_exc.pos == pos + + assert invalid.children[-1] is child_exc + + +@pytest.mark.parametrize("pos", [_MARKER, None, 23]) +def test_invalid_add_w_positional_node(positional_node, pos): + import colander + + node = colander.SchemaNode( + positional_node(), + colander.SchemaNode( + name="child", + typ=colander.String(), + ) + ) + invalid = colander.Invalid(node) + child_exc = colander.Invalid(object(), "testing") + + if pos is _MARKER: + invalid.add(child_exc) + else: + invalid.add(child_exc, pos=pos) + + assert child_exc.positional + + if pos not in (_MARKER, None): + assert child_exc.pos == pos + + assert invalid.children[-1] is child_exc + + +@pytest.mark.parametrize( + "name, pos_or_raises", [ + ("zero", 0), + ("one", 1), + ("two", 2), + ("nonesuch", KeyError), +]) +def test_invalid___setitem__(name, pos_or_raises): + import colander + + MSG = "testing" + + class Node(colander.Schema): + zero = colander.SchemaNode( + colander.String(), + ) + one = colander.SchemaNode( + colander.String(), + ) + two = colander.SchemaNode( + colander.String(), + ) + + node = Node() + invalid = colander.Invalid(node) + invalid.add = mock.Mock(spec_set=()) + + if not isinstance(pos_or_raises, int): + + with pytest.raises(pos_or_raises): + invalid[name] = MSG + + invalid.add.assert_not_called() + + else: + invalid[name] = MSG + + assert len(invalid.add.call_args_list) == 1 + args, kwargs = invalid.add.call_args_list[0] + assert len(args) == 2 + assert isinstance(args[0], colander.Invalid) + assert args[0].node is node.children[pos_or_raises] + assert args[1] == pos_or_raises + assert kwargs == {} + + +def test_invalid_paths_empty(): + import colander + + node = object() + invalid = colander.Invalid(node, "empty") + + paths = list(invalid.paths()) + + assert paths == [(invalid,)] + + +def test_invalid_paths_one_child(): + import colander + + node = object() + invalid = colander.Invalid(node, "empty") + child_exc = colander.Invalid(object(), "testing") + invalid.children.append(child_exc) + + paths = list(invalid.paths()) + + assert paths == [ + (invalid, child_exc), + ] + + +def test_invalid_paths_multiple_children(): + import colander + + node = object() + invalid = colander.Invalid(node, "parent") + + child_one = colander.Invalid(object(), "child one") + invalid.children.append(child_one) + + child_two = colander.Invalid(object(), "child two") + invalid.children.append(child_two) + + paths = list(invalid.paths()) + + assert paths == [ + (invalid, child_one), + (invalid, child_two), + ] + + +def test_invalid_paths_multiple_children_grandchild(): + import colander + + node = object() + invalid = colander.Invalid(node, "grandparent") + + child_one = colander.Invalid(object(), "child one") + invalid.children.append(child_one) + + grandchild_one = colander.Invalid(object(), "grandchild one") + child_one.children.append(grandchild_one) + + child_two = colander.Invalid(object(), "child two") + invalid.children.append(child_two) + + paths = list(invalid.paths()) + + assert paths == [ + (invalid, child_one, grandchild_one), + (invalid, child_two), + ] + + +def test_invalid__keyname_w_non_positional(): + import colander + + node = mock.Mock(spec_set=["name"]) + node.name = "Name" + invalid = colander.Invalid(node, "testing") + + assert invalid._keyname() == "Name" + + +def test_invalid__keyname_w_positional(): + import colander + + node = object() + invalid = colander.Invalid(node, "testing") + invalid.positional = True + invalid.pos = 42 + + assert invalid._keyname() == "42" + + +#def test_invalid_asdict_blah_blah_not_gonna_do_it() +#def test_invalid___str___not_gonna_do_it() + + +def test_unsupportedfields___init___default(): + import colander + + node = object() + usf = colander.UnsupportedFields(node, ["abc", "def"]) + + assert usf.node is node + assert usf.msg is None + assert usf.fields == ["abc", "def"] + + +def test_unsupportedfields___init___explicit(): + import colander + + node = object() + usf = colander.UnsupportedFields(node, ["abc", "def"], "MESSAGE") + + assert usf.node is node + assert usf.msg == "MESSAGE" + assert usf.fields == ["abc", "def"] From 58704dfb89ac485087a618fecb137ff2146bab99 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Sat, 18 May 2024 16:15:48 -0400 Subject: [PATCH 06/16] tests: add explicit coverage for validators --- tests/test_validators.py | 745 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 745 insertions(+) create mode 100644 tests/test_validators.py diff --git a/tests/test_validators.py b/tests/test_validators.py new file mode 100644 index 0000000..3aabe42 --- /dev/null +++ b/tests/test_validators.py @@ -0,0 +1,745 @@ +import re +from unittest import mock +import warnings + +import pytest +import translationstring + + +def _assert_trstring(found, expected): + assert isinstance(found, translationstring.TranslationString) + assert str(found) == expected + +def test_all___call___w_empty_subs(): + import colander + + node = object() + value = "testing" + all_ = colander.All() + assert list(all_.validators) == [] + + all_(node, value) # no raise + + +def test_all___call___w_one_sub_wo_raise(): + import colander + + node = object() + value = "testing" + validator = mock.Mock(spec_set=(), return_value=None) + all_ = colander.All(validator) + assert list(all_.validators) == [validator] + + all_(node, value) # no raise + + validator.assert_called_once_with(node, value) + +def test_all___call___w_one_sub_w_raise(): + import colander + + node = object() + value = "testing" + validator = mock.Mock( + spec_set=(), side_effect=colander.Invalid(node, "testing"), + ) + all_ = colander.All(validator) + assert list(all_.validators) == [validator] + + with pytest.raises(colander.Invalid) as exc: + all_(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["testing"] + + validator.assert_called_once_with(node, value) + + +def test_all___call___w_multi_subs_w_one_raises(): + import colander + + node = object() + value = "testing" + validator_1 = mock.Mock(spec_set=(), return_value=None) + validator_2 = mock.Mock( + spec_set=(), side_effect=colander.Invalid(node, "testing"), + ) + all_ = colander.All(validator_1, validator_2) + assert list(all_.validators) == [validator_1, validator_2] + + with pytest.raises(colander.Invalid) as exc: + all_(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["testing"] + + validator_1.assert_called_once_with(node, value) + validator_2.assert_called_once_with(node, value) + + +def test_all___call___w_multi_subs_w_many_raises(): + import colander + + node = object() + value = "testing" + validator_1 = mock.Mock( + spec_set=(), side_effect=colander.Invalid( + node, ["testing 1a", "testing 1b"] + ), + ) + validator_2 = mock.Mock( + spec_set=(), side_effect=colander.Invalid(node, "testing 2"), + ) + all_ = colander.All(validator_1, validator_2) + assert list(all_.validators) == [validator_1, validator_2] + + with pytest.raises(colander.Invalid) as exc: + all_(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["testing 1a", "testing 1b", "testing 2"] + + validator_1.assert_called_once_with(node, value) + validator_2.assert_called_once_with(node, value) + + +def test_any___call___w_empty_subs(): + import colander + + node = object() + value = "testing" + any_ = colander.Any() + assert list(any_.validators) == [] + + any_(node, value) # no raise + + +def test_any___call___w_one_sub_wo_raise(): + import colander + + node = object() + value = "testing" + validator = mock.Mock(spec_set=(), return_value=None) + any_ = colander.Any(validator) + assert list(any_.validators) == [validator] + + any_(node, value) # no raise + + validator.assert_called_once_with(node, value) + + +def test_any___call___w_one_sub_w_raise(): + import colander + + node = object() + value = "testing" + validator = mock.Mock( + spec_set=(), side_effect=colander.Invalid(node, "testing"), + ) + any_ = colander.Any(validator) + assert list(any_.validators) == [validator] + + with pytest.raises(colander.Invalid) as exc: + any_(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["testing"] + + +def test_any___call___w_multi_subs_w_one_raises(): + import colander + + node = object() + value = "testing" + validator_1 = mock.Mock(spec_set=(), return_value=None) + validator_2 = mock.Mock( + spec_set=(), side_effect=colander.Invalid(node, "testing"), + ) + any_ = colander.Any(validator_1, validator_2) + assert list(any_.validators) == [validator_1, validator_2] + + any_(node, value) + + validator_1.assert_called_once_with(node, value) + validator_2.assert_called_once_with(node, value) + +def test_any___call___w_multi_subs_w_all_raise(): + import colander + + node = object() + value = "testing" + validator_1 = mock.Mock( + spec_set=(), side_effect=colander.Invalid( + node, ["testing 1a", "testing 1b"] + ), + ) + validator_2 = mock.Mock( + spec_set=(), side_effect=colander.Invalid(node, "testing 2"), + ) + any_ = colander.Any(validator_1, validator_2) + assert list(any_.validators) == [validator_1, validator_2] + + with pytest.raises(colander.Invalid) as exc: + any_(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["testing 1a", "testing 1b", "testing 2"] + + validator_1.assert_called_once_with(node, value) + validator_2.assert_called_once_with(node, value) + + +def test_function___init___w_msg_none_and_message_none(): + import colander + + node = object() + value = "testing" + + def func(node, value): + pass + + fv = colander.Function(func) + + _assert_trstring(fv.msg, "Invalid value") + + +def test_function___init___w_msg_set_and_message_none(): + import colander + + node = object() + value = "testing" + + def func(node, value): + pass + + fv = colander.Function(func, "Testing") + + assert fv.msg == "Testing" + + +def test_function___init___w_msg_set_and_message_set(): + import colander + + node = object() + value = "testing" + + def func(node, value): + pass + + with pytest.raises(ValueError): + colander.Function(func, "msg", "message") + + +def test_function___init___w_msg_none_and_message_set(): + import colander + + node = object() + value = "testing" + + def func(node, value): + pass + + with warnings.catch_warnings(record=True) as warned: + fv = colander.Function(func, message="message") + + assert fv.msg == "message" # really? + assert len(warned) == 1 + assert warned[0].category is DeprecationWarning + + +def test_function___call___returing_falsish_non_string(): + import colander + + node = object() + value = "testing" + + func = mock.Mock(spec_set=[], return_value=None) + + fv = colander.Function(func, "Testing") + + with pytest.raises(colander.Invalid) as exc: + fv(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["Testing"] + func.assert_called_once_with(value) + + +def test_function___call___returing_truthy_non_string(): + import colander + + node = object() + value = "testing" + + func = mock.Mock(spec_set=[], return_value=True) + + fv = colander.Function(func, "Testing") + + fv(node, value) # no raise + + func.assert_called_once_with(value) + + +def test_function___call___returing_empty_string(): + import colander + + node = object() + value = "testing" + + func = mock.Mock(spec_set=[], return_value=None) + + fv = colander.Function(func, "Testing") + + with pytest.raises(colander.Invalid) as exc: + fv(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["Testing"] + func.assert_called_once_with(value) + + +def test_function___call___returing_nonempty_string(): + import colander + + node = object() + value = "testing" + + func = mock.Mock(spec_set=[], return_value="failed") + + fv = colander.Function(func, "Testing") + + with pytest.raises(colander.Invalid) as exc: + fv(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["failed"] + func.assert_called_once_with(value) + + +def test_regex___init___w_str_pattern_defaults(): + import colander + + pattern = "^Testing$" + + rgx = colander.Regex(pattern) + + assert isinstance(rgx.match_object, re.Pattern) + assert rgx.match_object.pattern == pattern + assert rgx.match_object.flags == re.UNICODE # 're' adds it + _assert_trstring(rgx.msg, "String does not match expected pattern") + + +def test_regex___init___w_str_pattern_w_custom_flags(): + import colander + + pattern = "^Testing$" + + rgx = colander.Regex(pattern, flags=re.IGNORECASE) + + assert isinstance(rgx.match_object, re.Pattern) + assert rgx.match_object.pattern == pattern + assert rgx.match_object.flags == re.IGNORECASE | re.UNICODE # 're' adds + _assert_trstring(rgx.msg, "String does not match expected pattern") + + +def test_regex___init___w_regex_object_w_message(): + import colander + + pattern = "^Testing$" + compiled = re.compile(pattern) + + rgx = colander.Regex(compiled, "Testing") + + assert rgx.match_object is compiled + assert rgx.msg == "Testing" + + +def test_regex___call___hit(): + import colander + + node = object() + value = "Testing" + pattern = "^Testing$" + compiled = re.compile(pattern) + rgx = colander.Regex(compiled, "Should not raise") + + rgx(node, "Testing") # no raise + + +def test_regex___call___miss(): + import colander + + node = object() + value = "Testing" + pattern = "^Not Testing$" + compiled = re.compile(pattern) + rgx = colander.Regex(compiled, "Should raise") + + with pytest.raises(colander.Invalid) as exc: + rgx(node, "Testing") + + assert exc.value.node is node + assert exc.value.messages() == ["Should raise"] + + +def test_email___init___wo_msg(): + import colander + + email = colander.Email() + + _assert_trstring(email.msg, "Invalid email address") + + +def test_email___init___w_msg(): + import colander + + email = colander.Email("Testing") + + assert email.msg == "Testing" + + +def test_dataurl___init__w_defaults(): + import colander + + durl = colander.DataURL() + + _assert_trstring(durl.url_err, "Not a data URL") + _assert_trstring(durl.mimetype_err, "Invalid MIME type") + _assert_trstring(durl.base64_err, "Invalid Base64 encoded data") + +def test_dataurl___init__w_explicit(): + import colander + + durl = colander.DataURL( + url_err="Bad URL", + mimetype_err="Bad MIMEtype", + base64_err="Bad base64", + ) + + assert durl.url_err == "Bad URL" + assert durl.mimetype_err == "Bad MIMEtype" + assert durl.base64_err == "Bad base64" + + +@pytest.mark.parametrize("bad_url", [ + "", + "foo", + "data:foo" + "data:foo;base64" + "data:foo;base32," + "data:text/plain;charset=ASCII,foo", +]) +def test_dataurl___call__miss_bad_url(bad_url): + import colander + + node = object() + durl = colander.DataURL(url_err="Bad URL") + + with pytest.raises(colander.Invalid) as exc: + durl(node, bad_url) + + assert exc.value.node is node + assert exc.value.msg == "Bad URL" + + +@pytest.mark.parametrize("bad_mt", [ + "data:no/mime,foo", + "data:no-mime;base64,Zm9vCg==", +]) +def test_dataurl___call__miss_bad_mimetype(bad_mt): + import colander + + node = object() + durl = colander.DataURL(mimetype_err="Bad MIMEtype") + + with pytest.raises(colander.Invalid) as exc: + durl(node, bad_mt) + + assert exc.value.node is node + assert exc.value.msg == "Bad MIMEtype" + + +@pytest.mark.parametrize("bad_b64", [ + "data:;base64,Zm9vCg", + "data:text/plain;base64,Zm*vCg==", +]) +def test_dataurl___call__miss_bad_base64(bad_b64): + import colander + + node = object() + durl = colander.DataURL(base64_err="Bad b64") + + with pytest.raises(colander.Invalid) as exc: + durl(node, bad_b64) + + assert exc.value.node is node + assert exc.value.msg == "Bad b64" + + + +def test_dataurl___call__w_invalid_mimetype_and_b64data(): + import colander + + bad_mt_and_b64 = "data:no/mime;base64,Zm*vCg==" + node = object() + durl = colander.DataURL(mimetype_err="Bad MIMEtype", base64_err="Bad b64") + + with pytest.raises(colander.Invalid) as exc: + durl(node, bad_mt_and_b64) + + assert exc.value.node is node + assert "Bad MIMEtype" in exc.value.messages() + assert "Bad b64" in exc.value.messages() + + +def test_range___init___w_defaults(): + import colander + + range_ = colander.Range() + + assert range_.min is None + assert range_.max is None + assert range_.min_err is colander.Range._MIN_ERR + assert range_.max_err is colander.Range._MAX_ERR + + +def test_range___init___w_explicit(): + import colander + + range_ = colander.Range(-2, 13, "Min err", "Max err") + + assert range_.min == -2 + assert range_.max == 13 + assert range_.min_err == "Min err" + assert range_.max_err == "Max err" + + +@pytest.mark.parametrize("min_, max_, raises", [ + (None, None, False), + (-5, None, False), + (45, None, True), + (None, 45, False), + (None, 41, True), + (-5, 45, False), + (44, 55, True), + (34, 35, True), +]) +def test_range___call___w_min_none_w_max_none(min_, max_, raises): + import colander + + node = object() + value = 42 + range_ = colander.Range(min=min_, max=max_) + + if raises: + with pytest.raises(colander.Invalid): + range_(node, value) + + else: + range_(node, value) # no raise + + +def test_length___init___w_defaults(): + import colander + + length = colander.Length() + + assert length.min is None + assert length.max is None + assert length.min_err is colander.Length._MIN_ERR + assert length.max_err is colander.Length._MAX_ERR + + +def test_length___init___w_explicit(): + import colander + + length = colander.Length(0, 13, "Min err", "Max err") + + assert length.min == 0 + assert length.max == 13 + assert length.min_err == "Min err" + assert length.max_err == "Max err" + + +@pytest.mark.parametrize("min_, max_, raises", [ + (None, None, False), + (0, None, False), + (45, None, True), + (None, 15, False), + (None, 4, True), + (0, 15, False), + (10, 15, True), + (0, 4, True), +]) +def test_length___call__(min_, max_, raises): + import colander + + node = object() + value = "ABCDEF" + length = colander.Length(min=min_, max=max_) + + if raises: + with pytest.raises(colander.Invalid): + length(node, value) + + else: + length(node, value) # no raise + + +def test_oneof___init___w_defaults(): + import colander + + oneof = colander.OneOf(["a", "b", "c"]) + + assert list(oneof.choices) == ["a", "b", "c"] + assert oneof.msg_err is colander.OneOf._MSG_ERR + + +def test_oneof___init___w_explicit(): + import colander + + oneof = colander.OneOf(["a", "b", "c"], "Pick one") + + assert list(oneof.choices) == ["a", "b", "c"] + assert oneof.msg_err == "Pick one" + + +def test_oneof___call___w_miss(): + import colander + + node = object() + oneof = colander.OneOf(["a", "b", "c"]) + + with pytest.raises(colander.Invalid): + oneof(node, "nonesuch") + + +def test_oneof___call___w_hit(): + import colander + + node = object() + oneof = colander.OneOf(["a", "b", "c"]) + + oneof(node, "a") # no raise + + +def test_noneof___init___w_defaults(): + import colander + + noneof = colander.NoneOf(["a", "b", "c"]) + + assert list(noneof.forbidden) == ["a", "b", "c"] + assert noneof.msg_err is colander.NoneOf._MSG_ERR + + +def test_noneof___init___w_explicit(): + import colander + + noneof = colander.NoneOf(["a", "b", "c"], "Pick another") + + assert list(noneof.forbidden) == ["a", "b", "c"] + assert noneof.msg_err == "Pick another" + + +def test_noneof___call___w_miss(): + import colander + + node = object() + noneof = colander.NoneOf(["a", "b", "c"]) + + noneof(node, "nonesuch") # no raise + + +def test_noneof___call___w_hit(): + import colander + + node = object() + noneof = colander.NoneOf(["a", "b", "c"]) + + with pytest.raises(colander.Invalid): + noneof(node, "a") + + +def test_containsonly___init__(): + import colander + + containsonly = colander.ContainsOnly(["a", "b", "c"]) + + assert list(containsonly.choices) == ["a", "b", "c"] + +def test_containsonly___call___w_hit(): + import colander + + containsonly = colander.ContainsOnly(["a", "b", "c"]) + node = object() + + containsonly(node, set(["a", "b"])) # no raise + +def test_containsonly___call___w_miss(): + import colander + + containsonly = colander.ContainsOnly(["a", "b", "c"]) + node = object() + + with pytest.raises(colander.Invalid): + containsonly(node, set(["a", "z"])) # no raise + + +def test_luhnok_w_checksum_raises(): + import colander + + node = object() + value = "ABC" + + with mock.patch("colander._luhnok") as checksum: + checksum.side_effect = ValueError("testing") + with pytest.raises(colander.Invalid): + colander.luhnok(node, value) + + checksum.assert_called_once_with("ABC") + + +def test_luhnok_w_checksum_mod_10_not_0(): + import colander + + node = object() + value = "ABC" + + with mock.patch("colander._luhnok") as checksum: + checksum.return_value = 111 + with pytest.raises(colander.Invalid): + colander.luhnok(node, value) + + checksum.assert_called_once_with("ABC") + + +def test_luhnok_hit(): + import colander + + node = object() + value = "ABC" + + with mock.patch("colander._luhnok") as checksum: + checksum.return_value = 1000 + + colander.luhnok(node, value) # no faise + + +@pytest.mark.parametrize("value, checksum, raises", [ + ("ABC", None, ValueError), + ("", 0, False), + ("10", 2, False), + ("100", 1, False), + ("4111111111111111", 30, False), +]) +def test__luhnok(value, checksum, raises): + import colander + + if raises: + with pytest.raises(raises): + colander._luhnok(value) + else: + assert colander._luhnok(value) == checksum + +# def test__luhnok_w_..... + +# def test__make_url_regex_src # CAN'T, it is deleted after making URL_REGEX! From e8a13be23ccd6c3ea671314c35662a4167570bd8 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 2 Aug 2024 15:29:07 -0400 Subject: [PATCH 07/16] chore: appease isort --- pyproject.toml | 3 ++- src/colander/__init__.py | 3 ++- tests/test_colander.py | 35 ++++++++++++++++++++++++----------- tests/test_singletons.py | 3 ++- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d14e33f..dee6df2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,4 +31,5 @@ force_sort_within_sections = true no_lines_before = "THIRDPARTY" sections = "FUTURE,THIRDPARTY,FIRSTPARTY,LOCALFOLDER" default_section = "THIRDPARTY" -known_first_party = "colander" +known_first_party = "colander,pytest,translationstring" +force_single_line = true diff --git a/src/colander/__init__.py b/src/colander/__init__.py index a7ba3f7..c5ba854 100644 --- a/src/colander/__init__.py +++ b/src/colander/__init__.py @@ -8,10 +8,11 @@ import mimetypes import pprint import re -import translationstring import types import warnings +import translationstring + _ = translationstring.TranslationStringFactory('colander') diff --git a/tests/test_colander.py b/tests/test_colander.py index 8520868..6836870 100644 --- a/tests/test_colander.py +++ b/tests/test_colander.py @@ -102,7 +102,8 @@ def test_asdict(self): def test_asdict_with_all_validator(self): # see https://github.com/Pylons/colander/pull/27 - from colander import All, Positional + from colander import All + from colander import Positional node1 = DummySchemaNode(None, 'node1') node2 = DummySchemaNode(Positional(), 'node2') @@ -1610,7 +1611,8 @@ def _makeOne(self, **kw): return Sequence(**kw) def test_alias(self): - from colander import Seq, Sequence + from colander import Seq + from colander import Sequence self.assertEqual(Seq, Sequence) @@ -1778,7 +1780,8 @@ def test_getvalue(self): self.assertEqual(typ.get_value(node1, appstruct, '1.0'), 3) def test_cstruct_children_cstruct_is_null(self): - from colander import SequenceItems, null + from colander import SequenceItems + from colander import null typ = self._makeOne() result = typ.cstruct_children(None, null) @@ -1799,7 +1802,8 @@ def _makeOne(self, encoding=None, allow_empty=False): return String(encoding, allow_empty) def test_alias(self): - from colander import Str, String + from colander import Str + from colander import String self.assertEqual(Str, String) @@ -1932,7 +1936,8 @@ def _makeOne(self, strict=False): return Integer(strict=strict) def test_alias(self): - from colander import Int, Integer + from colander import Int + from colander import Integer self.assertEqual(Int, Integer) @@ -2228,7 +2233,8 @@ def _makeOne(self): return Boolean() def test_alias(self): - from colander import Bool, Boolean + from colander import Bool + from colander import Boolean self.assertEqual(Bool, Boolean) @@ -3350,7 +3356,9 @@ def test_deserialize_with_validator(self): self.assertEqual(e.msg, 'Wrong') def test_deserialize_with_unbound_validator(self): - from colander import Invalid, UnboundDeferredError, deferred + from colander import Invalid + from colander import UnboundDeferredError + from colander import deferred typ = DummyType() @@ -3365,7 +3373,8 @@ def _validate(node, value): self.assertRaises(Invalid, node.bind(foo='foo').deserialize, None) def test_deserialize_value_is_null_no_missing(self): - from colander import Invalid, null + from colander import Invalid + from colander import null typ = DummyType() node = self._makeOne(typ) @@ -3412,7 +3421,9 @@ def test_deserialize_null_can_be_used_as_missing(self): self.assertEqual(node.deserialize(null), null) def test_deserialize_appstruct_deferred(self): - from colander import Invalid, deferred, null + from colander import Invalid + from colander import deferred + from colander import null typ = DummyType() node = self._makeOne(typ) @@ -3449,7 +3460,8 @@ def test_serialize_noargs_uses_default(self): self.assertEqual(node.serialize(), 'abc') def test_serialize_default_deferred(self): - from colander import deferred, null + from colander import deferred + from colander import null typ = DummyType() node = self._makeOne(typ) @@ -4039,7 +4051,8 @@ class MySchema(colander.Schema): class TestSchema(unittest.TestCase): def test_alias(self): - from colander import MappingSchema, Schema + from colander import MappingSchema + from colander import Schema self.assertEqual(Schema, MappingSchema) diff --git a/tests/test_singletons.py b/tests/test_singletons.py index e2f2d13..4dd53fe 100644 --- a/tests/test_singletons.py +++ b/tests/test_singletons.py @@ -11,8 +11,9 @@ def test__required___reduce__(): def test_required_pickling(): import pickle - from colander import required as the_singleton + from colander import _required + from colander import required as the_singleton pickled = pickle.dumps(the_singleton) From 515117477d83acf79c93d38b53b161783e030871 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 2 Aug 2024 15:37:07 -0400 Subject: [PATCH 08/16] chore: appease black --- tests/test_exceptions.py | 49 +++++++----- tests/test_validators.py | 168 +++++++++++++++++++++++---------------- 2 files changed, 128 insertions(+), 89 deletions(-) diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 570d85d..155688d 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -3,9 +3,16 @@ import pytest -@pytest.fixture(scope="function", params=[ - "String", "Integer", "Float", "Decimal", "Boolean", -]) +@pytest.fixture( + scope="function", + params=[ + "String", + "Integer", + "Float", + "Decimal", + "Boolean", + ], +) def non_positional_node(request): import colander @@ -22,12 +29,14 @@ def positional_node(request): @pytest.mark.parametrize( - "msg, expected", [ - ((), []), - ([], []), - (None, []), - ("MESSAGE", ["MESSAGE"]), -]) + "msg, expected", + [ + ((), []), + ([], []), + (None, []), + ("MESSAGE", ["MESSAGE"]), + ], +) def test_invalid_messages(msg, expected): import colander @@ -49,7 +58,7 @@ def test_invalid_add_w_non_positional_node(non_positional_node, pos): colander.SchemaNode( name="child", typ=colander.String(), - ) + ), ) invalid = colander.Invalid(node) child_exc = colander.Invalid(object(), "testing") @@ -76,7 +85,7 @@ def test_invalid_add_w_positional_node(positional_node, pos): colander.SchemaNode( name="child", typ=colander.String(), - ) + ), ) invalid = colander.Invalid(node) child_exc = colander.Invalid(object(), "testing") @@ -95,12 +104,14 @@ def test_invalid_add_w_positional_node(positional_node, pos): @pytest.mark.parametrize( - "name, pos_or_raises", [ - ("zero", 0), - ("one", 1), - ("two", 2), - ("nonesuch", KeyError), -]) + "name, pos_or_raises", + [ + ("zero", 0), + ("one", 1), + ("two", 2), + ("nonesuch", KeyError), + ], +) def test_invalid___setitem__(name, pos_or_raises): import colander @@ -230,8 +241,8 @@ def test_invalid__keyname_w_positional(): assert invalid._keyname() == "42" -#def test_invalid_asdict_blah_blah_not_gonna_do_it() -#def test_invalid___str___not_gonna_do_it() +# def test_invalid_asdict_blah_blah_not_gonna_do_it() +# def test_invalid___str___not_gonna_do_it() def test_unsupportedfields___init___default(): diff --git a/tests/test_validators.py b/tests/test_validators.py index 3aabe42..0f36ad8 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -10,37 +10,40 @@ def _assert_trstring(found, expected): assert isinstance(found, translationstring.TranslationString) assert str(found) == expected + def test_all___call___w_empty_subs(): import colander - + node = object() value = "testing" all_ = colander.All() assert list(all_.validators) == [] - all_(node, value) # no raise + all_(node, value) # no raise def test_all___call___w_one_sub_wo_raise(): import colander - + node = object() value = "testing" validator = mock.Mock(spec_set=(), return_value=None) all_ = colander.All(validator) assert list(all_.validators) == [validator] - all_(node, value) # no raise + all_(node, value) # no raise validator.assert_called_once_with(node, value) + def test_all___call___w_one_sub_w_raise(): import colander - + node = object() value = "testing" validator = mock.Mock( - spec_set=(), side_effect=colander.Invalid(node, "testing"), + spec_set=(), + side_effect=colander.Invalid(node, "testing"), ) all_ = colander.All(validator) assert list(all_.validators) == [validator] @@ -56,12 +59,13 @@ def test_all___call___w_one_sub_w_raise(): def test_all___call___w_multi_subs_w_one_raises(): import colander - + node = object() value = "testing" validator_1 = mock.Mock(spec_set=(), return_value=None) validator_2 = mock.Mock( - spec_set=(), side_effect=colander.Invalid(node, "testing"), + spec_set=(), + side_effect=colander.Invalid(node, "testing"), ) all_ = colander.All(validator_1, validator_2) assert list(all_.validators) == [validator_1, validator_2] @@ -78,16 +82,16 @@ def test_all___call___w_multi_subs_w_one_raises(): def test_all___call___w_multi_subs_w_many_raises(): import colander - + node = object() value = "testing" validator_1 = mock.Mock( - spec_set=(), side_effect=colander.Invalid( - node, ["testing 1a", "testing 1b"] - ), + spec_set=(), + side_effect=colander.Invalid(node, ["testing 1a", "testing 1b"]), ) validator_2 = mock.Mock( - spec_set=(), side_effect=colander.Invalid(node, "testing 2"), + spec_set=(), + side_effect=colander.Invalid(node, "testing 2"), ) all_ = colander.All(validator_1, validator_2) assert list(all_.validators) == [validator_1, validator_2] @@ -104,36 +108,37 @@ def test_all___call___w_multi_subs_w_many_raises(): def test_any___call___w_empty_subs(): import colander - + node = object() value = "testing" any_ = colander.Any() assert list(any_.validators) == [] - any_(node, value) # no raise + any_(node, value) # no raise def test_any___call___w_one_sub_wo_raise(): import colander - + node = object() value = "testing" validator = mock.Mock(spec_set=(), return_value=None) any_ = colander.Any(validator) assert list(any_.validators) == [validator] - any_(node, value) # no raise + any_(node, value) # no raise validator.assert_called_once_with(node, value) def test_any___call___w_one_sub_w_raise(): import colander - + node = object() value = "testing" validator = mock.Mock( - spec_set=(), side_effect=colander.Invalid(node, "testing"), + spec_set=(), + side_effect=colander.Invalid(node, "testing"), ) any_ = colander.Any(validator) assert list(any_.validators) == [validator] @@ -147,12 +152,13 @@ def test_any___call___w_one_sub_w_raise(): def test_any___call___w_multi_subs_w_one_raises(): import colander - + node = object() value = "testing" validator_1 = mock.Mock(spec_set=(), return_value=None) validator_2 = mock.Mock( - spec_set=(), side_effect=colander.Invalid(node, "testing"), + spec_set=(), + side_effect=colander.Invalid(node, "testing"), ) any_ = colander.Any(validator_1, validator_2) assert list(any_.validators) == [validator_1, validator_2] @@ -162,18 +168,19 @@ def test_any___call___w_multi_subs_w_one_raises(): validator_1.assert_called_once_with(node, value) validator_2.assert_called_once_with(node, value) + def test_any___call___w_multi_subs_w_all_raise(): import colander - + node = object() value = "testing" validator_1 = mock.Mock( - spec_set=(), side_effect=colander.Invalid( - node, ["testing 1a", "testing 1b"] - ), + spec_set=(), + side_effect=colander.Invalid(node, ["testing 1a", "testing 1b"]), ) validator_2 = mock.Mock( - spec_set=(), side_effect=colander.Invalid(node, "testing 2"), + spec_set=(), + side_effect=colander.Invalid(node, "testing 2"), ) any_ = colander.Any(validator_1, validator_2) assert list(any_.validators) == [validator_1, validator_2] @@ -406,6 +413,7 @@ def test_dataurl___init__w_defaults(): _assert_trstring(durl.mimetype_err, "Invalid MIME type") _assert_trstring(durl.base64_err, "Invalid Base64 encoded data") + def test_dataurl___init__w_explicit(): import colander @@ -420,14 +428,17 @@ def test_dataurl___init__w_explicit(): assert durl.base64_err == "Bad base64" -@pytest.mark.parametrize("bad_url", [ - "", - "foo", - "data:foo" - "data:foo;base64" - "data:foo;base32," - "data:text/plain;charset=ASCII,foo", -]) +@pytest.mark.parametrize( + "bad_url", + [ + "", + "foo", + "data:foo" + "data:foo;base64" + "data:foo;base32," + "data:text/plain;charset=ASCII,foo", + ], +) def test_dataurl___call__miss_bad_url(bad_url): import colander @@ -441,10 +452,13 @@ def test_dataurl___call__miss_bad_url(bad_url): assert exc.value.msg == "Bad URL" -@pytest.mark.parametrize("bad_mt", [ - "data:no/mime,foo", - "data:no-mime;base64,Zm9vCg==", -]) +@pytest.mark.parametrize( + "bad_mt", + [ + "data:no/mime,foo", + "data:no-mime;base64,Zm9vCg==", + ], +) def test_dataurl___call__miss_bad_mimetype(bad_mt): import colander @@ -458,10 +472,13 @@ def test_dataurl___call__miss_bad_mimetype(bad_mt): assert exc.value.msg == "Bad MIMEtype" -@pytest.mark.parametrize("bad_b64", [ - "data:;base64,Zm9vCg", - "data:text/plain;base64,Zm*vCg==", -]) +@pytest.mark.parametrize( + "bad_b64", + [ + "data:;base64,Zm9vCg", + "data:text/plain;base64,Zm*vCg==", + ], +) def test_dataurl___call__miss_bad_base64(bad_b64): import colander @@ -475,7 +492,6 @@ def test_dataurl___call__miss_bad_base64(bad_b64): assert exc.value.msg == "Bad b64" - def test_dataurl___call__w_invalid_mimetype_and_b64data(): import colander @@ -513,16 +529,19 @@ def test_range___init___w_explicit(): assert range_.max_err == "Max err" -@pytest.mark.parametrize("min_, max_, raises", [ - (None, None, False), - (-5, None, False), - (45, None, True), - (None, 45, False), - (None, 41, True), - (-5, 45, False), - (44, 55, True), - (34, 35, True), -]) +@pytest.mark.parametrize( + "min_, max_, raises", + [ + (None, None, False), + (-5, None, False), + (45, None, True), + (None, 45, False), + (None, 41, True), + (-5, 45, False), + (44, 55, True), + (34, 35, True), + ], +) def test_range___call___w_min_none_w_max_none(min_, max_, raises): import colander @@ -560,16 +579,19 @@ def test_length___init___w_explicit(): assert length.max_err == "Max err" -@pytest.mark.parametrize("min_, max_, raises", [ - (None, None, False), - (0, None, False), - (45, None, True), - (None, 15, False), - (None, 4, True), - (0, 15, False), - (10, 15, True), - (0, 4, True), -]) +@pytest.mark.parametrize( + "min_, max_, raises", + [ + (None, None, False), + (0, None, False), + (45, None, True), + (None, 15, False), + (None, 4, True), + (0, 15, False), + (10, 15, True), + (0, 4, True), + ], +) def test_length___call__(min_, max_, raises): import colander @@ -666,6 +688,7 @@ def test_containsonly___init__(): assert list(containsonly.choices) == ["a", "b", "c"] + def test_containsonly___call___w_hit(): import colander @@ -674,6 +697,7 @@ def test_containsonly___call___w_hit(): containsonly(node, set(["a", "b"])) # no raise + def test_containsonly___call___w_miss(): import colander @@ -724,13 +748,16 @@ def test_luhnok_hit(): colander.luhnok(node, value) # no faise -@pytest.mark.parametrize("value, checksum, raises", [ - ("ABC", None, ValueError), - ("", 0, False), - ("10", 2, False), - ("100", 1, False), - ("4111111111111111", 30, False), -]) +@pytest.mark.parametrize( + "value, checksum, raises", + [ + ("ABC", None, ValueError), + ("", 0, False), + ("10", 2, False), + ("100", 1, False), + ("4111111111111111", 30, False), + ], +) def test__luhnok(value, checksum, raises): import colander @@ -740,6 +767,7 @@ def test__luhnok(value, checksum, raises): else: assert colander._luhnok(value) == checksum + # def test__luhnok_w_..... # def test__make_url_regex_src # CAN'T, it is deleted after making URL_REGEX! From 1a907c4ab7bcbe1c719ee8d2122a03d90089ff16 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 2 Aug 2024 15:41:58 -0400 Subject: [PATCH 09/16] chore: appease flake8 --- tests/test_singletons.py | 4 ---- tests/test_validators.py | 14 -------------- 2 files changed, 18 deletions(-) diff --git a/tests/test_singletons.py b/tests/test_singletons.py index 4dd53fe..505bdf4 100644 --- a/tests/test_singletons.py +++ b/tests/test_singletons.py @@ -1,6 +1,3 @@ -import pytest - - def test__required___reduce__(): from colander import _required @@ -12,7 +9,6 @@ def test__required___reduce__(): def test_required_pickling(): import pickle - from colander import _required from colander import required as the_singleton pickled = pickle.dumps(the_singleton) diff --git a/tests/test_validators.py b/tests/test_validators.py index 0f36ad8..47eb4c7 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -198,9 +198,6 @@ def test_any___call___w_multi_subs_w_all_raise(): def test_function___init___w_msg_none_and_message_none(): import colander - node = object() - value = "testing" - def func(node, value): pass @@ -212,9 +209,6 @@ def func(node, value): def test_function___init___w_msg_set_and_message_none(): import colander - node = object() - value = "testing" - def func(node, value): pass @@ -226,9 +220,6 @@ def func(node, value): def test_function___init___w_msg_set_and_message_set(): import colander - node = object() - value = "testing" - def func(node, value): pass @@ -239,9 +230,6 @@ def func(node, value): def test_function___init___w_msg_none_and_message_set(): import colander - node = object() - value = "testing" - def func(node, value): pass @@ -364,7 +352,6 @@ def test_regex___call___hit(): import colander node = object() - value = "Testing" pattern = "^Testing$" compiled = re.compile(pattern) rgx = colander.Regex(compiled, "Should not raise") @@ -376,7 +363,6 @@ def test_regex___call___miss(): import colander node = object() - value = "Testing" pattern = "^Not Testing$" compiled = re.compile(pattern) rgx = colander.Regex(compiled, "Should raise") From 9ca47b4b44602fd4bae6e0f3ce7b8143f17b3ce7 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 2 Aug 2024 16:01:53 -0400 Subject: [PATCH 10/16] tests: remove 'unittest.TestCase' cases covered by pytest cases --- tests/test_colander.py | 693 --------------------------------------- tests/test_validators.py | 3 +- 2 files changed, 1 insertion(+), 695 deletions(-) diff --git a/tests/test_colander.py b/tests/test_colander.py index 6836870..be34e77 100644 --- a/tests/test_colander.py +++ b/tests/test_colander.py @@ -15,699 +15,6 @@ def invalid_exc(func, *arg, **kw): raise AssertionError('Invalid not raised') # pragma: no cover -class TestInvalid(unittest.TestCase): - def _makeOne(self, node, msg=None, val=None): - from colander import Invalid - - exc = Invalid(node, msg, val) - return exc - - def test_ctor(self): - exc = self._makeOne(None, 'msg', 'val') - self.assertEqual(exc.node, None) - self.assertEqual(exc.msg, 'msg') - self.assertEqual(exc.value, 'val') - self.assertEqual(exc.children, []) - - def test_add(self): - exc = self._makeOne(None, 'msg') - other = Dummy() - exc.add(other) - self.assertFalse(hasattr(other, 'positional')) - self.assertEqual(exc.children, [other]) - - def test_add_positional(self): - from colander import Positional - - p = Positional() - node = DummySchemaNode(p) - exc = self._makeOne(node, 'msg') - other = Dummy() - exc.add(other) - self.assertEqual(other.positional, True) - self.assertEqual(exc.children, [other]) - - def test__keyname_no_parent(self): - node = DummySchemaNode(None, name='name') - exc = self._makeOne(None, '') - exc.node = node - self.assertEqual(exc._keyname(), 'name') - - def test__keyname_positional(self): - exc = self._makeOne(None, '') - exc.positional = True - exc.pos = 2 - self.assertEqual(exc._keyname(), '2') - - def test__keyname_nonpositional_parent(self): - parent = Dummy() - parent.node = DummySchemaNode(None) - exc = self._makeOne(None, 'me') - exc.parent = parent - exc.pos = 2 - exc.node = DummySchemaNode(None, name='name') - self.assertEqual(exc._keyname(), 'name') - - def test_paths(self): - exc1 = self._makeOne(None, 'exc1') - exc2 = self._makeOne(None, 'exc2') - exc3 = self._makeOne(None, 'exc3') - exc4 = self._makeOne(None, 'exc4') - exc1.add(exc2) - exc2.add(exc3) - exc1.add(exc4) - paths = list(exc1.paths()) - self.assertEqual(paths, [(exc1, exc2, exc3), (exc1, exc4)]) - - def test_asdict(self): - from colander import Positional - - node1 = DummySchemaNode(None, 'node1') - node2 = DummySchemaNode(Positional(), 'node2') - node3 = DummySchemaNode(Positional(), 'node3') - node4 = DummySchemaNode(Positional(), 'node4') - exc1 = self._makeOne(node1, 'exc1') - exc1.pos = 1 - exc2 = self._makeOne(node2, 'exc2') - exc3 = self._makeOne(node3, 'exc3') - exc4 = self._makeOne(node4, 'exc4') - exc1.add(exc2, 2) - exc2.add(exc3, 3) - exc1.add(exc4, 4) - d = exc1.asdict() - self.assertEqual( - d, - {'node1.node2.3': 'exc1; exc2; exc3', 'node1.node4': 'exc1; exc4'}, - ) - - def test_asdict_with_all_validator(self): - # see https://github.com/Pylons/colander/pull/27 - from colander import All - from colander import Positional - - node1 = DummySchemaNode(None, 'node1') - node2 = DummySchemaNode(Positional(), 'node2') - node3 = DummySchemaNode(Positional(), 'node3') - node1.children = [node3] - validator1 = DummyValidator('validator1') - validator2 = DummyValidator('validator2') - validator = All(validator1, validator2) - exc1 = self._makeOne(node1, 'exc1') - exc1.pos = 1 - exc1['node3'] = 'message1' - exc2 = self._makeOne(node2, 'exc2') - exc3 = invalid_exc(validator, None, None) - exc1.add(exc2, 2) - exc2.add(exc3, 3) - d = exc1.asdict() - self.assertEqual( - d, - { - 'node1.node2.3': 'exc1; exc2; validator1; validator2', - 'node1.node3': 'exc1; message1', - }, - ) - - def test_asdict_with_all_validator_functional(self): - # see https://github.com/Pylons/colander/issues/2 - class MySchema(colander.MappingSchema): - number1 = colander.SchemaNode( - colander.Int(), validator=colander.Range(min=1) - ) - number2 = colander.SchemaNode( - colander.Int(), validator=colander.Range(min=1) - ) - - def validate_higher(node, val): - if val['number1'] >= val['number2']: - raise colander.Invalid( - node, 'Number 1 must be lower than number 2' - ) - - def validate_different(node, val): - if val['number1'] == val['number2']: - raise colander.Invalid(node, "They can't be the same, either") - - schema = MySchema( - validator=colander.All(validate_higher, validate_different) - ) - try: - schema.deserialize(dict(number1=2, number2=2)) - except colander.Invalid as e: - result = e.asdict() - self.assertEqual( - result, - { - '': ( - "Number 1 must be lower than number 2; " - "They can't be the same, either" - ) - }, - ) - try: - schema.deserialize(dict(number1=2, number2=2)) - except colander.Invalid as e: - result = e.asdict(separator=None) - self.assertEqual( - result, - { - '': [ - "Number 1 must be lower than number 2", - "They can't be the same, either", - ] - }, - ) - - def test_asdict_with_msg_none(self): - # see https://github.com/Pylons/colander/pull/333 - from colander import All - - node1 = DummySchemaNode(None, 'node1') - validator1 = DummyValidator('validator1') - validator2 = DummyValidatorWithMsgNone() - validator = All(All(validator1, validator2)) - exc = invalid_exc(validator, node1, None) - result = exc.asdict() - self.assertEqual(result, {'node1': 'validator1'}) - - def test___str__(self): - from colander import Positional - - node1 = DummySchemaNode(None, 'node1') - node2 = DummySchemaNode(Positional(), 'node2') - node3 = DummySchemaNode(Positional(), 'node3') - node4 = DummySchemaNode(Positional(), 'node4') - exc1 = self._makeOne(node1, 'exc1') - exc1.pos = 1 - exc2 = self._makeOne(node2, 'exc2') - exc3 = self._makeOne(node3, 'exc3') - exc4 = self._makeOne(node4, 'exc4') - exc1.add(exc2, 2) - exc2.add(exc3, 3) - exc1.add(exc4, 4) - result = str(exc1) - self.assertEqual( - result, - "{'node1.node2.3': 'exc1; exc2; exc3', " - "'node1.node4': 'exc1; exc4'}", - ) - - def test___setitem__fails(self): - node = DummySchemaNode(None) - exc = self._makeOne(node, 'msg') - self.assertRaises(KeyError, exc.__setitem__, 'notfound', 'msg') - - def test___setitem__succeeds(self): - node = DummySchemaNode(None) - child = DummySchemaNode(None) - child.name = 'found' - node.children = [child] - exc = self._makeOne(node, 'msg') - exc['found'] = 'msg2' - self.assertEqual(len(exc.children), 1) - childexc = exc.children[0] - self.assertEqual(childexc.pos, 0) - self.assertEqual(childexc.node.name, 'found') - - def test_messages_msg_iterable(self): - node = DummySchemaNode(None) - exc = self._makeOne(node, [123, 456]) - self.assertEqual(exc.messages(), [123, 456]) - - def test_messages_msg_not_iterable(self): - node = DummySchemaNode(None) - exc = self._makeOne(node, 'msg') - self.assertEqual(exc.messages(), ['msg']) - - def test_messages_msg_None(self): - node = DummySchemaNode(None) - exc = self._makeOne(node, None) - self.assertEqual(exc.messages(), []) - - -class TestAll(unittest.TestCase): - def _makeOne(self, validators): - from colander import All - - return All(*validators) - - def test_success(self): - validator1 = DummyValidator() - validator2 = DummyValidator() - validator = self._makeOne([validator1, validator2]) - self.assertEqual(validator(None, None), None) - - def test_failure(self): - validator1 = DummyValidator('msg1') - validator2 = DummyValidator('msg2') - validator = self._makeOne([validator1, validator2]) - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg, ['msg1', 'msg2']) - - def test_Invalid_children(self): - from colander import Invalid - - node1 = DummySchemaNode(None, 'node1') - node = DummySchemaNode(None, 'node') - node.children = [node1] - exc1 = Invalid(node1, 'exc1') - exc2 = Invalid(node1, 'exc2') - validator1 = DummyValidator('validator1', [exc1]) - validator2 = DummyValidator('validator2', [exc2]) - validator = self._makeOne([validator1, validator2]) - exc = invalid_exc(validator, node, None) - self.assertEqual(exc.children, [exc1, exc2]) - - -class TestAny(unittest.TestCase): - def _makeOne(self, validators): - from colander import Any - - return Any(*validators) - - def test_success(self): - validator1 = DummyValidator('msg1') - validator2 = DummyValidator() - validator = self._makeOne([validator1, validator2]) - self.assertEqual(validator(None, None), None) - - def test_failure(self): - validator1 = DummyValidator('msg1') - validator2 = DummyValidator('msg2') - validator = self._makeOne([validator1, validator2]) - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg, ['msg1', 'msg2']) - - def test_Invalid_children(self): - from colander import Invalid - - node1 = DummySchemaNode(None, 'node1') - node = DummySchemaNode(None, 'node') - node.children = [node1] - exc1 = Invalid(node1, 'exc1') - validator1 = DummyValidator('validator1', [exc1]) - validator2 = DummyValidator() - validator = self._makeOne([validator1, validator2]) - self.assertEqual(validator(None, None), None) - - -class TestFunction(unittest.TestCase): - def _makeOne(self, *arg, **kw): - from colander import Function - - return Function(*arg, **kw) - - def test_success_function_returns_True(self): - validator = self._makeOne(lambda x: True) - self.assertEqual(validator(None, None), None) - - def test_fail_function_returns_empty_string(self): - validator = self._makeOne(lambda x: '') - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg, 'Invalid value') - - def test_fail_function_returns_False(self): - validator = self._makeOne(lambda x: False) - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg, 'Invalid value') - - def test_fail_function_returns_string(self): - validator = self._makeOne(lambda x: 'fail') - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg, 'fail') - - def test_deprecated_message(self): - import warnings - - orig_warn = warnings.warn - log = [] - - def warn(message, category=None, stacklevel=1): - log.append((message, category, stacklevel)) - - try: - # Monkey patching warn so that tests run quietly - warnings.warn = warn - validator = self._makeOne(lambda x: False, message='depr') - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg.interpolate(), 'depr') - finally: - warnings.warn = orig_warn - - def test_deprecated_message_warning(self): - import warnings - - orig_warn = warnings.warn - log = [] - - def warn(message, category=None, stacklevel=1): - log.append((message, category, stacklevel)) - - try: - # Monkey patching warn since catch_warnings context manager - # is not working when running the full suite - warnings.warn = warn - validator = self._makeOne(lambda x: False, message='depr') - invalid_exc(validator, None, None) - self.assertEqual(len(log), 1) - finally: - warnings.warn = orig_warn - - def test_msg_and_message_error(self): - self.assertRaises( - ValueError, - self._makeOne, - lambda x: False, - msg='one', - message='two', - ) - - def test_error_message_adds_mapping_to_configured_message(self): - validator = self._makeOne(lambda x: False, msg='fail ${val}') - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg.interpolate(), 'fail None') - - def test_error_message_adds_mapping_to_return_message(self): - validator = self._makeOne(lambda x: 'fail ${val}') - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg.interpolate(), 'fail None') - - def test_error_message_does_not_overwrite_configured_domain(self): - import translationstring - - _ = translationstring.TranslationStringFactory('fnord') - validator = self._makeOne(lambda x: False, msg=_('fail ${val}')) - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg.domain, 'fnord') - - def test_error_message_does_not_overwrite_returned_domain(self): - import translationstring - - _ = translationstring.TranslationStringFactory('fnord') - validator = self._makeOne(lambda x: _('fail ${val}')) - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg.domain, 'fnord') - - def test_propagation(self): - validator = self._makeOne(lambda x: 'a' in x, 'msg') - self.assertRaises(TypeError, validator, None, None) - - -class TestRange(unittest.TestCase): - def _makeOne(self, **kw): - from colander import Range - - return Range(**kw) - - def test_success_no_bounds(self): - validator = self._makeOne() - self.assertEqual(validator(None, 1), None) - - def test_success_upper_bound_only(self): - validator = self._makeOne(max=1) - self.assertEqual(validator(None, -1), None) - - def test_success_minimum_bound_only(self): - validator = self._makeOne(min=0) - self.assertEqual(validator(None, 1), None) - - def test_success_min_and_max(self): - validator = self._makeOne(min=1, max=1) - self.assertEqual(validator(None, 1), None) - - def test_min_failure(self): - validator = self._makeOne(min=1) - e = invalid_exc(validator, None, 0) - self.assertEqual(e.msg.interpolate(), '0 is less than minimum value 1') - - def test_min_failure_msg_override(self): - validator = self._makeOne(min=1, min_err='wrong') - e = invalid_exc(validator, None, 0) - self.assertEqual(e.msg, 'wrong') - - def test_max_failure(self): - validator = self._makeOne(max=1) - e = invalid_exc(validator, None, 2) - self.assertEqual( - e.msg.interpolate(), '2 is greater than maximum value 1' - ) - - def test_max_failure_msg_override(self): - validator = self._makeOne(max=1, max_err='wrong') - e = invalid_exc(validator, None, 2) - self.assertEqual(e.msg, 'wrong') - - -class TestRegex(unittest.TestCase): - def _makeOne(self, pattern): - from colander import Regex - - return Regex(pattern) - - def test_valid_regex(self): - self.assertEqual(self._makeOne('a')(None, 'a'), None) - self.assertEqual(self._makeOne('[0-9]+')(None, '1111'), None) - self.assertEqual(self._makeOne('')(None, ''), None) - self.assertEqual(self._makeOne('.*')(None, ''), None) - - def test_invalid_regexs(self): - from colander import Invalid - - self.assertRaises(Invalid, self._makeOne('[0-9]+'), None, 'a') - self.assertRaises(Invalid, self._makeOne('a{2,4}'), None, 'ba') - - def test_regex_not_string(self): - import re - - from colander import Invalid - - regex = re.compile('[0-9]+') - self.assertEqual(self._makeOne(regex)(None, '01'), None) - self.assertRaises(Invalid, self._makeOne(regex), None, 't') - - -class TestEmail(unittest.TestCase): - def _makeOne(self): - from colander import Email - - return Email() - - def test_valid_emails(self): - validator = self._makeOne() - self.assertEqual(validator(None, 'me@here.com'), None) - self.assertEqual(validator(None, 'me1@here1.com'), None) - self.assertEqual(validator(None, 'name@here1.us'), None) - self.assertEqual(validator(None, 'name@here1.info'), None) - self.assertEqual(validator(None, 'foo@bar.baz.biz'), None) - self.assertEqual(validator(None, "tip'oneill@house.gov"), None) - self.assertEqual(validator(None, "lorem@i--ipsum.com"), None) - - def test_empty_email(self): - validator = self._makeOne() - e = invalid_exc(validator, None, '') - self.assertEqual(e.msg, 'Invalid email address') - - def test_invalid_emails(self): - validator = self._makeOne() - from colander import Invalid - - self.assertRaises(Invalid, validator, None, 'me@here.') - self.assertRaises(Invalid, validator, None, '@here.us') - self.assertRaises(Invalid, validator, None, 'me@here..com') - self.assertRaises(Invalid, validator, None, 'me@we-here-.com') - self.assertRaises(Invalid, validator, None, 'name1,name2@here.info') - - -class TestDataURL(unittest.TestCase): - def _makeOne(self): - from colander import DataURL - - return DataURL() - - def test_valid_data_urls(self): - validator = self._makeOne() - self.assertEqual(validator(None, 'data:,'), None) - self.assertEqual(validator(None, 'data:,foo'), None) - self.assertEqual(validator(None, 'data:;base64,'), None) - self.assertEqual(validator(None, 'data:;base64,Zm9vCg=='), None) - self.assertEqual(validator(None, 'data:text/plain,foo'), None) - self.assertEqual( - validator(None, 'data:text/plain;base64,Zm9vCg=='), None - ) - self.assertEqual(validator(None, 'data:text/plain,foo%20bar'), None) - self.assertEqual(validator(None, 'data:text/plain,%F0%9F%A4%93'), None) - - def test_invalid_data_urls(self): - validator = self._makeOne() - msg = 'Not a data URL' - e = invalid_exc(validator, None, '') - self.assertEqual(e.msg, msg) - e = invalid_exc(validator, None, 'foo') - self.assertEqual(e.msg, msg) - e = invalid_exc(validator, None, 'data:foo') - self.assertEqual(e.msg, msg) - e = invalid_exc(validator, None, 'data:;base64') - self.assertEqual(e.msg, msg) - e = invalid_exc(validator, None, 'data:;base32,') - self.assertEqual(e.msg, msg) - e = invalid_exc(validator, None, 'data:text/plain;charset=ASCII,foo') - self.assertEqual(e.msg, msg) - e = invalid_exc( - validator, None, 'data:text/plain;charset=ASCII;base64,Zm9vCg==' - ) - self.assertEqual(e.msg, msg) - - def test_invalid_mimetypes(self): - validator = self._makeOne() - msg = 'Invalid MIME type' - e = invalid_exc(validator, None, 'data:no/mime,foo') - self.assertEqual(e.msg, msg) - e = invalid_exc(validator, None, 'data:no-mime;base64,Zm9vCg==') - self.assertEqual(e.msg, msg) - - def test_invalid_base64_data(self): - validator = self._makeOne() - msg = 'Invalid Base64 encoded data' - e = invalid_exc(validator, None, 'data:;base64,Zm9vCg') - self.assertEqual(e.msg, msg) - e = invalid_exc(validator, None, 'data:text/plain;base64,Zm*vCg==') - self.assertEqual(e.msg, msg) - - def test_invalid_mimetypes_and_base64(self): - validator = self._makeOne() - msg = ['Invalid MIME type', 'Invalid Base64 encoded data'] - e = invalid_exc(validator, None, 'data:no/mime;base64,Zm9vCg') - self.assertEqual(e.msg, msg) - - -class TestLength(unittest.TestCase): - def _makeOne(self, **kw): - from colander import Length - - return Length(**kw) - - def test_success_no_bounds(self): - validator = self._makeOne() - self.assertEqual(validator(None, ''), None) - - def test_success_upper_bound_only(self): - validator = self._makeOne(max=1) - self.assertEqual(validator(None, 'a'), None) - - def test_success_minimum_bound_only(self): - validator = self._makeOne(min=0) - self.assertEqual(validator(None, ''), None) - - def test_success_min_and_max(self): - validator = self._makeOne(min=1, max=1) - self.assertEqual(validator(None, 'a'), None) - - def test_min_failure(self): - validator = self._makeOne(min=1) - e = invalid_exc(validator, None, '') - self.assertEqual(e.msg.interpolate(), 'Shorter than minimum length 1') - - def test_max_failure(self): - validator = self._makeOne(max=1) - e = invalid_exc(validator, None, 'ab') - self.assertEqual(e.msg.interpolate(), 'Longer than maximum length 1') - - def test_min_failure_msg_override(self): - validator = self._makeOne(min=1, min_err='Need at least ${min}, mate') - e = invalid_exc(validator, None, []) - self.assertEqual(e.msg.interpolate(), 'Need at least 1, mate') - - def test_max_failure_msg_override(self): - validator = self._makeOne(max=1, max_err='No more than ${max}, mate') - e = invalid_exc(validator, None, [1, 2]) - self.assertEqual(e.msg.interpolate(), 'No more than 1, mate') - - -class TestOneOf(unittest.TestCase): - def _makeOne(self, values): - from colander import OneOf - - return OneOf(values) - - def test_success(self): - validator = self._makeOne([1]) - self.assertEqual(validator(None, 1), None) - - def test_failure(self): - validator = self._makeOne([1, 2]) - e = invalid_exc(validator, None, None) - self.assertEqual(e.msg.interpolate(), '"None" is not one of 1, 2') - - -class TestNoneOf(unittest.TestCase): - def _makeOne(self, values): - from colander import NoneOf - - return NoneOf(values) - - def test_success(self): - validator = self._makeOne([1, 2]) - self.assertEqual(validator(None, 3), None) - - def test_failure(self): - validator = self._makeOne([1, 2]) - e = invalid_exc(validator, None, 2) - self.assertEqual(e.msg.interpolate(), '"2" must not be one of 1, 2') - - -class TestContainsOnly(unittest.TestCase): - def _makeOne(self, values): - from colander import ContainsOnly - - return ContainsOnly(values) - - def test_success(self): - validator = self._makeOne([1]) - self.assertEqual(validator(None, [1]), None) - - def test_failure(self): - validator = self._makeOne([1]) - e = invalid_exc(validator, None, [2]) - self.assertEqual( - e.msg.interpolate(), - 'One or more of the choices you made was not acceptable', - ) - - def test_failure_with_custom_error_template(self): - validator = self._makeOne([1]) - from colander import _ - - validator.err_template = _('${val}: ${choices}') - e = invalid_exc(validator, None, [2]) - self.assertTrue('[2]' in e.msg.interpolate()) - - -class Test_luhnok(unittest.TestCase): - def _callFUT(self, node, value): - from colander import luhnok - - return luhnok(node, value) - - def test_fail(self): - - val = '10' - self.assertRaises(colander.Invalid, self._callFUT, None, val) - - def test_fail2(self): - - val = '99999999999999999999999' - self.assertRaises(colander.Invalid, self._callFUT, None, val) - - def test_fail3(self): - - val = 'abcdefghij' - self.assertRaises(colander.Invalid, self._callFUT, None, val) - - def test_success(self): - val = '4111111111111111' - self.assertFalse(self._callFUT(None, val)) - - class Test_url_validator(unittest.TestCase): def _callFUT(self, val): from colander import url diff --git a/tests/test_validators.py b/tests/test_validators.py index 47eb4c7..63469d8 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -742,6 +742,7 @@ def test_luhnok_hit(): ("10", 2, False), ("100", 1, False), ("4111111111111111", 30, False), + ("99999999999999999999999", 207, False), ], ) def test__luhnok(value, checksum, raises): @@ -754,6 +755,4 @@ def test__luhnok(value, checksum, raises): assert colander._luhnok(value) == checksum -# def test__luhnok_w_..... - # def test__make_url_regex_src # CAN'T, it is deleted after making URL_REGEX! From 070fc6a7221e4966d5b180e549ee4ed5ce414c47 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 2 Aug 2024 16:26:27 -0400 Subject: [PATCH 11/16] tests: test 'url'/file_uri'/'uuid' validators w/ pytest --- tests/test_colander.py | 206 --------------------------------------- tests/test_validators.py | 136 ++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 206 deletions(-) diff --git a/tests/test_colander.py b/tests/test_colander.py index be34e77..4d3854f 100644 --- a/tests/test_colander.py +++ b/tests/test_colander.py @@ -15,212 +15,6 @@ def invalid_exc(func, *arg, **kw): raise AssertionError('Invalid not raised') # pragma: no cover -class Test_url_validator(unittest.TestCase): - def _callFUT(self, val): - from colander import url - - return url(None, val) - - def _assert_success(self, val): - result = self._callFUT(val) - self.assertEqual(result, None) - - def _assert_failure(self, val): - from colander import Invalid - - self.assertRaises(Invalid, self._callFUT, val) - - def test_it_success(self): - self._assert_success('http://example.com') - - def test_it_failure(self): - self._assert_failure('not-a-url') - - def test_add_sample_dos(self): - # In the old regex (colander <=1.6) this would cause a catastrophic - # backtracking that would cause the regex engine to go into an infinite - # loop. - self._assert_success( - "http://www.mysite.com/(tttttttttttttttttttttt.jpg" - ) - - def test_no_scheme(self): - self._assert_success("www.mysite.com") - - def test_file_scheme_raises(self): - self._assert_failure("file:///this/is/a/file.jpg") - - def test_auth_user(self): - self._assert_success("http://user@mysite.com") - - def test_auth_user_blank_password(self): - self._assert_success("http://user:@mysite.com") - - def test_auth_user_password(self): - self._assert_success("http://user:password@mysite.com") - - def test_auth_user_password_with_quoted_atmark(self): - self._assert_success("http://user:pass%40word@mysite.com") - - def test_host_ipv6(self): - self._assert_success("http://[2001:db8::0]/") - - def test_host_ipv4(self): - self._assert_success("http://192.0.2.1/") - - def test_host_fqdn_dot_finished(self): - self._assert_success("http://www.mysite.com.") - - def test_host_fqdn_dot_started_raises(self): - self._assert_failure("http://.mysite.com") - - def test_host_fqdn_hyphen_contains(self): - self._assert_success("http://www.my-site.com") - - def test_host_fqdn_hyphen_finished_raises(self): - self._assert_failure("http://www.mysite-.com") - - def test_host_fqdn_hyphen_started_raises(self): - self._assert_failure("http://www.-mysite.com") - - def test_host_i18n_idna(self): - self._assert_success("http://xn--vck8cuc4a.com") - - def test_host_i18n_raw(self): - self._assert_success( - str( - b"http://\xe3\x82\xb5\xe3\x83\xb3\xe3\x83\x97\xe3\x83\xab.com", - "utf-8", - ) - ) - - def test_host_localhost(self): - self._assert_success("http://localhost/") - - def test_host_no_fqdn_failure(self): - self._assert_failure("http://mysite") - - def test_port(self): - self._assert_success("http://mysite.com:8080") - - def test_no_port_raises(self): - self._assert_failure("http://mysite.com:/path") - - def test_wrong_port_raises(self): - self._assert_failure("http://mysite.com:aaa") - - def test_qs(self): - self._assert_success("http://mysite.com/path?k=v") - - def test_fragment(self): - self._assert_success("http://mysite.com/path#fragment") - - def test_qs_fragment(self): - self._assert_success("http://mysite.com/path?k=v#fragment") - - def test_slashless_qs(self): - self._assert_success("http://mysite.com?k=v") - - def test_slashless_fragment(self): - self._assert_success("http://mysite.com#fragment") - - def test_trailing_space_raises(self): - self._assert_failure("http://mysite.com ") - - -class Test_file_uri_validator(unittest.TestCase): - def _callFUT(self, val): - from colander import file_uri - - return file_uri(None, val) - - def test_it_success(self): - val = 'file:///' - result = self._callFUT(val) - self.assertEqual(result, None) - - def test_it_failure(self): - val = 'not-a-uri' - from colander import Invalid - - self.assertRaises(Invalid, self._callFUT, val) - - def test_no_path_fails(self): - val = 'file://' - from colander import Invalid - - self.assertRaises(Invalid, self._callFUT, val) - - def test_file_with_path(self): - val = "file:///this/is/a/file.jpg" - - result = self._callFUT(val) - self.assertEqual(result, None) - - def test_file_with_path_windows(self): - val = "file:///c:/is/a/file.jpg" - - result = self._callFUT(val) - self.assertEqual(result, None) - - -class TestUUID(unittest.TestCase): - def _callFUT(self, val): - from colander import uuid - - return uuid(None, val) - - def test_success_hexadecimal(self): - val = '123e4567e89b12d3a456426655440000' - result = self._callFUT(val) - self.assertEqual(result, None) - - def test_success_with_dashes(self): - val = '123e4567-e89b-12d3-a456-426655440000' - result = self._callFUT(val) - self.assertEqual(result, None) - - def test_success_upper_case(self): - val = '123E4567-E89B-12D3-A456-426655440000' - result = self._callFUT(val) - self.assertEqual(result, None) - - def test_success_with_braces(self): - val = '{123e4567-e89b-12d3-a456-426655440000}' - result = self._callFUT(val) - self.assertEqual(result, None) - - def test_success_with_urn_ns(self): - val = 'urn:uuid:{123e4567-e89b-12d3-a456-426655440000}' - result = self._callFUT(val) - self.assertEqual(result, None) - - def test_failure_random_string(self): - val = 'not-a-uuid' - from colander import Invalid - - self.assertRaises(Invalid, self._callFUT, val) - - def test_failure_not_hexadecimal(self): - val = '123zzzzz-uuuu-zzzz-uuuu-42665544zzzz' - from colander import Invalid - - self.assertRaises(Invalid, self._callFUT, val) - - def test_failure_invalid_length(self): - # Correct UUID: 8-4-4-4-12 - val = '88888888-333-4444-333-cccccccccccc' - from colander import Invalid - - self.assertRaises(Invalid, self._callFUT, val) - - def test_failure_with_invalid_urn_ns(self): - val = 'urn:abcd:{123e4567-e89b-12d3-a456-426655440000}' - from colander import Invalid - - self.assertRaises(Invalid, self._callFUT, val) - - class TestSchemaType(unittest.TestCase): def _makeOne(self, *arg, **kw): from colander import SchemaType diff --git a/tests/test_validators.py b/tests/test_validators.py index 63469d8..81467b7 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -756,3 +756,139 @@ def test__luhnok(value, checksum, raises): # def test__make_url_regex_src # CAN'T, it is deleted after making URL_REGEX! + + +@pytest.mark.parametrize( + "value", + [ + "not-a-url", + "file:///this/is/a/file.jpg", + "http://.mysite.com", + "http://www.mysite-.com", + "http://www.-mysite.com", + "http://mysite", + "http://mysite.com:/path", + "http://mysite.com:aaa", + "http://mysite.com ", + ], +) +def test_url_failures(value): + import colander + + node = object() + + with pytest.raises(colander.Invalid) as exc: + colander.url(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["Must be a URL"] + + +@pytest.mark.parametrize( + "value", + [ + "http://example.com", + "http://www.mysite.com/(tttttttttttttttttttttt.jpg", + "www.mysite.com", + "http://user@mysite.com", + "http://user:@mysite.com", + "http://user:password@mysite.com", + "http://user:pass%40word@mysite.com", + "http://[2001:db8::0]/", + "http://192.0.2.1/", + "http://www.mysite.com.", + "http://www.my-site.com", + "http://xn--vck8cuc4a.com", + str( + b"http://\xe3\x82\xb5\xe3\x83\xb3\xe3\x83\x97\xe3\x83\xab.com", + "utf-8", + ), + "http://localhost/", + "http://mysite.com:8080", + "http://mysite.com/path?k=v", + "http://mysite.com/path#fragment", + "http://mysite.com/path?k=v#fragment", + "http://mysite.com?k=v", + "http://mysite.com#fragment", + ], +) +def test_url_successes(value): + import colander + + node = object() + + assert colander.url(node, value) is None + + +@pytest.mark.parametrize( + "value", + [ + "not-a-uri", + "file://", + ], +) +def test_file_uri_failures(value): + import colander + + node = object() + + with pytest.raises(colander.Invalid) as exc: + colander.file_uri(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["Must be a file:// URI scheme"] + + +@pytest.mark.parametrize( + "value", + [ + "file:///", + "file:///this/is/a/file.jpg", + "file:///c:/is/a/file.jpg", + ], +) +def test_file_uri_successes(value): + import colander + + node = object() + + assert colander.file_uri(node, value) is None + + +@pytest.mark.parametrize( + "value", + [ + "not-a-uuid", + "123zzzzz-uuuu-zzzz-uuuu-42665544zzzz", + "88888888-333-4444-333-cccccccccccc", + "urn:abcd:{123e4567-e89b-12d3-a456-426655440000}", + ], +) +def test_uuid_failures(value): + import colander + + node = object() + + with pytest.raises(colander.Invalid) as exc: + colander.uuid(node, value) + + assert exc.value.node is node + assert exc.value.messages() == ["Invalid UUID string"] + + +@pytest.mark.parametrize( + "value", + [ + "123e4567e89b12d3a456426655440000", + "123e4567-e89b-12d3-a456-426655440000", + "123E4567-E89B-12D3-A456-426655440000", + "{123e4567-e89b-12d3-a456-426655440000}", + "urn:uuid:{123e4567-e89b-12d3-a456-426655440000}", + ], +) +def test_uuid_successes(value): + import colander + + node = object() + + assert colander.uuid(node, value) is None From e86ea4573e6f7cba31de1cc95b6be26d547b5f18 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 9 Aug 2024 14:23:34 -0400 Subject: [PATCH 12/16] chore: test types using pytest idioms --- tests/relative.py | 4 + tests/test_colander.py | 2285 ----------------------------- tests/test_types.py | 3178 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 3182 insertions(+), 2285 deletions(-) create mode 100644 tests/test_types.py diff --git a/tests/relative.py b/tests/relative.py index f21a506..aca58a0 100644 --- a/tests/relative.py +++ b/tests/relative.py @@ -1,3 +1,7 @@ # For the benefit of TestGlobalObject class ImportableClass: pass + + +def importable_func(): + pass diff --git a/tests/test_colander.py b/tests/test_colander.py index 4d3854f..90302ec 100644 --- a/tests/test_colander.py +++ b/tests/test_colander.py @@ -15,2291 +15,6 @@ def invalid_exc(func, *arg, **kw): raise AssertionError('Invalid not raised') # pragma: no cover -class TestSchemaType(unittest.TestCase): - def _makeOne(self, *arg, **kw): - from colander import SchemaType - - return SchemaType(*arg, **kw) - - def test_flatten(self): - node = DummySchemaNode(None, name='node') - typ = self._makeOne() - result = typ.flatten(node, 'appstruct') - self.assertEqual(result, {'node': 'appstruct'}) - - def test_flatten_listitem(self): - node = DummySchemaNode(None, name='node') - typ = self._makeOne() - result = typ.flatten(node, 'appstruct', listitem=True) - self.assertEqual(result, {'': 'appstruct'}) - - def test_unflatten(self): - node = DummySchemaNode(None, name='node') - typ = self._makeOne() - result = typ.unflatten(node, ['node'], {'node': 'appstruct'}) - self.assertEqual(result, 'appstruct') - - def test_set_value(self): - typ = self._makeOne() - self.assertRaises( - AssertionError, typ.set_value, None, None, None, None - ) - - def test_get_value(self): - typ = self._makeOne() - self.assertRaises(AssertionError, typ.get_value, None, None, None) - - def test_cstruct_children(self): - typ = self._makeOne() - self.assertEqual(typ.cstruct_children(None, None), []) - - -class TestMapping(unittest.TestCase): - def _makeOne(self, *arg, **kw): - from colander import Mapping - - return Mapping(*arg, **kw) - - def test_ctor_bad_unknown(self): - self.assertRaises(ValueError, self._makeOne, 'badarg') - - def test_ctor_good_unknown(self): - try: - self._makeOne('ignore') - self._makeOne('raise') - self._makeOne('preserve') - except ValueError as e: # pragma: no cover - raise AssertionError(e) - - def test_deserialize_not_a_mapping(self): - node = DummySchemaNode(None) - typ = self._makeOne() - - # None - e = invalid_exc(typ.deserialize, node, None) - self.assertTrue( - e.msg.interpolate().startswith('"None" is not a mapping type') - ) - - # list - e = invalid_exc(typ.deserialize, node, []) - self.assertTrue( - e.msg.interpolate().startswith('"[]" is not a mapping type') - ) - - # str - e = invalid_exc(typ.deserialize, node, "") - self.assertTrue( - e.msg.interpolate().startswith('"" is not a mapping type') - ) - - # tuple - e = invalid_exc(typ.deserialize, node, ()) - self.assertTrue( - e.msg.interpolate().startswith('"()" is not a mapping type') - ) - - def test_deserialize_null(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, colander.null) - self.assertEqual(result, colander.null) - - def test_deserialize_no_subnodes(self): - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, {}) - self.assertEqual(result, {}) - - def test_deserialize_ok(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - result = typ.deserialize(node, {'a': 1}) - self.assertEqual(result, {'a': 1}) - - def test_deserialize_unknown_raise(self): - - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne(unknown='raise') - e = invalid_exc(typ.deserialize, node, {'a': 1, 'b': 2}) - self.assertTrue(isinstance(e, colander.UnsupportedFields)) - self.assertEqual(e.fields, {'b': 2}) - self.assertEqual( - e.msg.interpolate(), "Unrecognized keys in mapping: \"{'b': 2}\"" - ) - - def test_deserialize_unknown_preserve(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne(unknown='preserve') - result = typ.deserialize(node, {'a': 1, 'b': 2}) - self.assertEqual(result, {'a': 1, 'b': 2}) - - def test_deserialize_subnodes_raise(self): - node = DummySchemaNode(None) - node.children = [ - DummySchemaNode(None, name='a', exc='Wrong 2'), - DummySchemaNode(None, name='b', exc='Wrong 2'), - ] - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, {'a': 1, 'b': 2}) - self.assertEqual(e.msg, None) - self.assertEqual(len(e.children), 2) - - def test_deserialize_subnode_missing_default(self): - - node = DummySchemaNode(None) - node.children = [ - DummySchemaNode(None, name='a'), - DummySchemaNode(None, name='b', default='abc'), - ] - typ = self._makeOne() - result = typ.deserialize(node, {'a': 1}) - self.assertEqual(result, {'a': 1, 'b': colander.null}) - - def test_serialize_null(self): - - val = colander.null - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, {}) - - def test_serialize_not_a_mapping(self): - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.serialize, node, None) - self.assertTrue( - e.msg.interpolate().startswith('"None" is not a mapping type') - ) - - def test_serialize_no_subnodes(self): - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, {}) - self.assertEqual(result, {}) - - def test_serialize_ok(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - result = typ.serialize(node, {'a': 1}) - self.assertEqual(result, {'a': 1}) - - def test_serialize_with_unknown(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - result = typ.serialize(node, {'a': 1, 'b': 2}) - self.assertEqual(result, {'a': 1}) - - def test_serialize_value_is_null(self): - node = DummySchemaNode(None) - from colander import null - - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - result = typ.serialize(node, null) - self.assertEqual(result, {'a': null}) - - def test_serialize_value_has_drop(self): - from colander import drop - - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - result = typ.serialize(node, {'a': drop}) - self.assertEqual(result, {}) - - def test_flatten(self): - node = DummySchemaNode(None, name='node') - int1 = DummyType() - int2 = DummyType() - node.children = [ - DummySchemaNode(int1, name='a'), - DummySchemaNode(int2, name='b'), - ] - typ = self._makeOne() - result = typ.flatten(node, {'a': 1, 'b': 2}) - self.assertEqual(result, {'node.appstruct': 2}) - - def test_flatten_listitem(self): - node = DummySchemaNode(None, name='node') - int1 = DummyType() - int2 = DummyType() - node.children = [ - DummySchemaNode(int1, name='a'), - DummySchemaNode(int2, name='b'), - ] - typ = self._makeOne() - result = typ.flatten(node, {'a': 1, 'b': 2}, listitem=True) - self.assertEqual(result, {'appstruct': 2}) - - def test_unflatten(self): - node = DummySchemaNode(None, name='node') - int1 = DummyType() - int2 = DummyType() - node.children = [ - DummySchemaNode(int1, name='a'), - DummySchemaNode(int2, name='b'), - ] - typ = self._makeOne() - result = typ.unflatten( - node, - ['node', 'node.a', 'node.b'], - {'node': {'a': 1, 'b': 2}, 'node.a': 1, 'node.b': 2}, - ) - self.assertEqual(result, {'a': 1, 'b': 2}) - - def test_unflatten_nested(self): - node = DummySchemaNode(None, name='node') - inttype = DummyType() - one = DummySchemaNode(self._makeOne(), name='one') - one.children = [ - DummySchemaNode(inttype, name='a'), - DummySchemaNode(inttype, name='b'), - ] - two = DummySchemaNode(self._makeOne(), name='two') - two.children = [ - DummySchemaNode(inttype, name='c'), - DummySchemaNode(inttype, name='d'), - ] - node.children = [one, two] - typ = self._makeOne() - result = typ.unflatten( - node, - [ - 'node', - 'node.one', - 'node.one.a', - 'node.one.b', - 'node.two', - 'node.two.c', - 'node.two.d', - ], - { - 'node': {'one': {'a': 1, 'b': 2}, 'two': {'c': 3, 'd': 4}}, - 'node.one': {'a': 1, 'b': 2}, - 'node.two': {'c': 3, 'd': 4}, - 'node.one.a': 1, - 'node.one.b': 2, - 'node.two.c': 3, - 'node.two.d': 4, - }, - ) - self.assertEqual( - result, {'one': {'a': 1, 'b': 2}, 'two': {'c': 3, 'd': 4}} - ) - - def test_set_value(self): - typ = self._makeOne() - node1 = DummySchemaNode(typ, name='node1') - node2 = DummySchemaNode(typ, name='node2') - node1.children = [node2] - appstruct = {'node2': {'foo': 'foo', 'baz': 'baz'}} - typ.set_value(node1, appstruct, 'node2.foo', 'bar') - self.assertEqual(appstruct, {'node2': {'foo': 'bar', 'baz': 'baz'}}) - - def test_get_value(self): - typ = self._makeOne() - node1 = DummySchemaNode(typ, name='node1') - node2 = DummySchemaNode(typ, name='node2') - node1.children = [node2] - appstruct = {'node2': {'foo': 'bar', 'baz': 'baz'}} - self.assertEqual( - typ.get_value(node1, appstruct, 'node2'), - {'foo': 'bar', 'baz': 'baz'}, - ) - self.assertEqual(typ.get_value(node1, appstruct, 'node2.foo'), 'bar') - - def test_cstruct_children_cstruct_is_null(self): - from colander import null - - typ = self._makeOne() - node1 = DummySchemaNode(typ, name='node1') - node2 = DummySchemaNode(typ, name='node2') - node1.children = [node2] - result = typ.cstruct_children(node1, null) - self.assertEqual(result, [null]) - - def test_cstruct_children(self): - from colander import null - - typ = self._makeOne() - node1 = DummySchemaNode(typ, name='node1') - node2 = DummySchemaNode(typ, name='node2') - node3 = DummySchemaNode(typ, name='node3') - node1.children = [node2, node3] - result = typ.cstruct_children(node1, {'node2': 'abc'}) - self.assertEqual(result, ['abc', null]) - - -class TestTuple(unittest.TestCase): - def _makeOne(self): - from colander import Tuple - - return Tuple() - - def test_deserialize_not_iterable(self): - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, None) - self.assertEqual(e.msg.interpolate(), '"None" is not iterable') - self.assertEqual(e.node, node) - - def test_deserialize_no_subnodes(self): - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, ()) - self.assertEqual(result, ()) - - def test_deserialize_null(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, colander.null) - self.assertEqual(result, colander.null) - - def test_deserialize_ok(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - result = typ.deserialize(node, ('a',)) - self.assertEqual(result, ('a',)) - - def test_deserialize_toobig(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, ('a', 'b')) - self.assertEqual( - e.msg.interpolate(), - "\"('a', 'b')\" has an incorrect number of " - "elements (expected 1, was 2)", - ) - - def test_deserialize_toosmall(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, ()) - self.assertEqual( - e.msg.interpolate(), - '"()" has an incorrect number of elements (expected 1, was 0)', - ) - - def test_deserialize_subnodes_raise(self): - node = DummySchemaNode(None) - node.children = [ - DummySchemaNode(None, name='a', exc='Wrong 2'), - DummySchemaNode(None, name='b', exc='Wrong 2'), - ] - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, ('1', '2')) - self.assertEqual(e.msg, None) - self.assertEqual(len(e.children), 2) - - def test_serialize_null(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, colander.null) - self.assertEqual(result, colander.null) - - def test_serialize_not_iterable(self): - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.serialize, node, None) - self.assertEqual(e.msg.interpolate(), '"None" is not iterable') - self.assertEqual(e.node, node) - - def test_serialize_no_subnodes(self): - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, ()) - self.assertEqual(result, ()) - - def test_serialize_ok(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - result = typ.serialize(node, ('a',)) - self.assertEqual(result, ('a',)) - - def test_serialize_toobig(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - e = invalid_exc(typ.serialize, node, ('a', 'b')) - self.assertEqual( - e.msg.interpolate(), - "\"('a', 'b')\" has an incorrect number of " - "elements (expected 1, was 2)", - ) - - def test_serialize_toosmall(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - e = invalid_exc(typ.serialize, node, ()) - self.assertEqual( - e.msg.interpolate(), - '"()" has an incorrect number of elements (expected 1, was 0)', - ) - - def test_serialize_subnodes_raise(self): - node = DummySchemaNode(None) - node.children = [ - DummySchemaNode(None, name='a', exc='Wrong 2'), - DummySchemaNode(None, name='b', exc='Wrong 2'), - ] - typ = self._makeOne() - e = invalid_exc(typ.serialize, node, ('1', '2')) - self.assertEqual(e.msg, None) - self.assertEqual(len(e.children), 2) - - def test_flatten(self): - node = DummySchemaNode(None, name='node') - int1 = DummyType() - int2 = DummyType() - node.children = [ - DummySchemaNode(int1, name='a'), - DummySchemaNode(int2, name='b'), - ] - typ = self._makeOne() - result = typ.flatten(node, (1, 2)) - self.assertEqual(result, {'node.appstruct': 2}) - - def test_flatten_listitem(self): - node = DummySchemaNode(None, name='node') - int1 = DummyType() - int2 = DummyType() - node.children = [ - DummySchemaNode(int1, name='a'), - DummySchemaNode(int2, name='b'), - ] - typ = self._makeOne() - result = typ.flatten(node, (1, 2), listitem=True) - self.assertEqual(result, {'appstruct': 2}) - - def test_unflatten(self): - node = DummySchemaNode(None, name='node') - int1 = DummyType() - int2 = DummyType() - node.children = [ - DummySchemaNode(int1, name='a'), - DummySchemaNode(int2, name='b'), - ] - typ = self._makeOne() - result = typ.unflatten( - node, - ['node', 'node.a', 'node.b'], - {'node': (1, 2), 'node.a': 1, 'node.b': 2}, - ) - self.assertEqual(result, (1, 2)) - - def test_set_value(self): - typ = self._makeOne() - node = DummySchemaNode(typ, name='node') - node.children = [ - DummySchemaNode(typ, name='foo'), - DummySchemaNode(typ, name='bar'), - ] - node['foo'].children = [ - DummySchemaNode(None, name='a'), - DummySchemaNode(None, name='b'), - ] - node['bar'].children = [ - DummySchemaNode(None, name='c'), - DummySchemaNode(None, name='d'), - ] - appstruct = ((1, 2), (3, 4)) - result = typ.set_value(node, appstruct, 'bar.c', 34) - self.assertEqual(result, ((1, 2), (34, 4))) - - def test_set_value_bad_path(self): - typ = self._makeOne() - node = DummySchemaNode(typ, name='node') - node.children = [ - DummySchemaNode(None, name='foo'), - DummySchemaNode(None, name='bar'), - ] - self.assertRaises(KeyError, typ.set_value, node, (1, 2), 'foobar', 34) - - def test_get_value(self): - typ = self._makeOne() - node = DummySchemaNode(typ, name='node') - node.children = [ - DummySchemaNode(typ, name='foo'), - DummySchemaNode(typ, name='bar'), - ] - node['foo'].children = [ - DummySchemaNode(None, name='a'), - DummySchemaNode(None, name='b'), - ] - node['bar'].children = [ - DummySchemaNode(None, name='c'), - DummySchemaNode(None, name='d'), - ] - appstruct = ((1, 2), (3, 4)) - self.assertEqual(typ.get_value(node, appstruct, 'foo'), (1, 2)) - self.assertEqual(typ.get_value(node, appstruct, 'foo.b'), 2) - - def test_get_value_bad_path(self): - typ = self._makeOne() - node = DummySchemaNode(typ, name='node') - node.children = [ - DummySchemaNode(None, name='foo'), - DummySchemaNode(None, name='bar'), - ] - self.assertRaises(KeyError, typ.get_value, node, (1, 2), 'foobar') - - def test_cstruct_children_cstruct_is_null(self): - from colander import null - - typ = self._makeOne() - node1 = DummySchemaNode(typ, name='node1') - node2 = DummySchemaNode(typ, name='node2') - node1.children = [node2] - result = typ.cstruct_children(node1, null) - self.assertEqual(result, [null]) - - def test_cstruct_children_toomany(self): - typ = self._makeOne() - node1 = DummySchemaNode(typ, name='node1') - node2 = DummySchemaNode(typ, name='node2') - node3 = DummySchemaNode(typ, name='node3') - node1.children = [node2, node3] - result = typ.cstruct_children(node1, ['one', 'two', 'three']) - self.assertEqual(result, ['one', 'two']) - - def test_cstruct_children_toofew(self): - from colander import null - - typ = self._makeOne() - node1 = DummySchemaNode(typ, name='node1') - node2 = DummySchemaNode(typ, name='node2') - node3 = DummySchemaNode(typ, name='node3') - node1.children = [node2, node3] - result = typ.cstruct_children(node1, ['one']) - self.assertEqual(result, ['one', null]) - - def test_cstruct_children_justright(self): - typ = self._makeOne() - node1 = DummySchemaNode(typ, name='node1') - node2 = DummySchemaNode(typ, name='node2') - node3 = DummySchemaNode(typ, name='node3') - node1.children = [node2, node3] - result = typ.cstruct_children(node1, ['one', 'two']) - self.assertEqual(result, ['one', 'two']) - - -class TestSet(unittest.TestCase): - def _makeOne(self, **kw): - from colander import Set - - return Set(**kw) - - def test_serialize(self): - typ = self._makeOne() - node = DummySchemaNode(typ) - provided = [] - result = typ.serialize(node, provided) - self.assertTrue(result is provided) - - def test_serialize_null(self): - from colander import null - - typ = self._makeOne() - node = DummySchemaNode(typ) - result = typ.serialize(node, null) - self.assertTrue(result is null) - - def test_deserialize_no_iter(self): - typ = self._makeOne() - node = DummySchemaNode(typ) - e = invalid_exc(typ.deserialize, node, 1) - self.assertEqual(e.msg, '${cstruct} is not iterable') - - def test_deserialize_str_no_iter(self): - typ = self._makeOne() - node = DummySchemaNode(typ) - e = invalid_exc(typ.deserialize, node, "foo") - self.assertEqual(e.msg, '${cstruct} is not iterable') - - def test_deserialize_null(self): - from colander import null - - typ = self._makeOne() - node = DummySchemaNode(typ) - result = typ.deserialize(node, null) - self.assertEqual(result, null) - - def test_deserialize_valid(self): - typ = self._makeOne() - node = DummySchemaNode(typ) - result = typ.deserialize(node, ('a',)) - self.assertEqual(result, {'a'}) - - def test_deserialize_empty_set(self): - typ = self._makeOne() - node = DummySchemaNode(typ) - result = typ.deserialize(node, set()) - self.assertEqual(result, set()) - - -class TestList(unittest.TestCase): - def _makeOne(self, **kw): - from colander import List - - return List(**kw) - - def test_serialize(self): - typ = self._makeOne() - node = DummySchemaNode(typ) - provided = [] - result = typ.serialize(node, provided) - self.assertTrue(result is provided) - - def test_serialize_null(self): - from colander import null - - typ = self._makeOne() - node = DummySchemaNode(typ) - result = typ.serialize(node, null) - self.assertTrue(result is null) - - def test_deserialize_no_iter(self): - typ = self._makeOne() - node = DummySchemaNode(typ) - e = invalid_exc(typ.deserialize, node, 1) - self.assertEqual(e.msg, '${cstruct} is not iterable') - - def test_deserialize_str_no_iter(self): - typ = self._makeOne() - node = DummySchemaNode(typ) - e = invalid_exc(typ.deserialize, node, "foo") - self.assertEqual(e.msg, '${cstruct} is not iterable') - - def test_deserialize_null(self): - from colander import null - - typ = self._makeOne() - node = DummySchemaNode(typ) - result = typ.deserialize(node, null) - self.assertEqual(result, null) - - def test_deserialize_valid(self): - typ = self._makeOne() - node = DummySchemaNode(typ) - result = typ.deserialize(node, ('a', 'z', 'b')) - self.assertEqual(result, ['a', 'z', 'b']) - - def test_deserialize_empty_set(self): - typ = self._makeOne() - node = DummySchemaNode(typ) - result = typ.deserialize(node, ()) - self.assertEqual(result, []) - - -class TestSequence(unittest.TestCase): - def _makeOne(self, **kw): - from colander import Sequence - - return Sequence(**kw) - - def test_alias(self): - from colander import Seq - from colander import Sequence - - self.assertEqual(Seq, Sequence) - - def test_deserialize_not_iterable(self): - node = DummySchemaNode(None) - typ = self._makeOne() - node.children = [node] - e = invalid_exc(typ.deserialize, node, None) - self.assertEqual(e.msg.interpolate(), '"None" is not iterable') - self.assertEqual(e.node, node) - - def test_deserialize_not_iterable_accept_scalar(self): - node = DummySchemaNode(None) - typ = self._makeOne(accept_scalar=True) - node.children = [node] - result = typ.deserialize(node, None) - self.assertEqual(result, [None]) - - def test_deserialize_string_accept_scalar(self): - node = DummySchemaNode(None) - typ = self._makeOne(accept_scalar=True) - node.children = [node] - result = typ.deserialize(node, 'abc') - self.assertEqual(result, ['abc']) - - def test_deserialize_no_subnodes(self): - typ = self._makeOne() - node = DummySchemaNode(None) - node.children = [node] - result = typ.deserialize(node, ()) - self.assertEqual(result, []) - - def test_deserialize_no_null(self): - - typ = self._makeOne() - result = typ.deserialize(None, colander.null) - self.assertEqual(result, colander.null) - - def test_deserialize_ok(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - node.children = [node] - result = typ.deserialize(node, ('a',)) - self.assertEqual(result, ['a']) - - def test_deserialize_subnodes_raise(self): - node = DummySchemaNode(None, exc='Wrong') - typ = self._makeOne() - node.children = [node] - e = invalid_exc(typ.deserialize, node, ('1', '2')) - self.assertEqual(e.msg, None) - self.assertEqual(len(e.children), 2) - - def test_serialize_null(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, colander.null) - self.assertEqual(result, colander.null) - - def test_serialize_drop(self): - from colander import drop - - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - result = typ.serialize(node, (drop,)) - self.assertEqual(result, []) - - def test_serialize_not_iterable(self): - node = DummySchemaNode(None) - typ = self._makeOne() - node.children = [node] - e = invalid_exc(typ.serialize, node, None) - self.assertEqual(e.msg.interpolate(), '"None" is not iterable') - self.assertEqual(e.node, node) - - def test_serialize_not_iterable_accept_scalar(self): - node = DummySchemaNode(None) - typ = self._makeOne(accept_scalar=True) - node.children = [node] - result = typ.serialize(node, None) - self.assertEqual(result, [None]) - - def test_serialize_string_accept_scalar(self): - node = DummySchemaNode(None) - typ = self._makeOne(accept_scalar=True) - node.children = [node] - result = typ.serialize(node, 'abc') - self.assertEqual(result, ['abc']) - - def test_serialize_no_subnodes(self): - node = DummySchemaNode(None) - node.children = [node] - typ = self._makeOne() - result = typ.serialize(node, ()) - self.assertEqual(result, []) - - def test_serialize_ok(self): - node = DummySchemaNode(None) - node.children = [DummySchemaNode(None, name='a')] - typ = self._makeOne() - result = typ.serialize(node, ('a',)) - self.assertEqual(result, ['a']) - - def test_serialize_subnodes_raise(self): - node = DummySchemaNode(None, exc='Wrong') - typ = self._makeOne() - node.children = [node] - e = invalid_exc(typ.serialize, node, ('1', '2')) - self.assertEqual(e.msg, None) - self.assertEqual(len(e.children), 2) - - def test_flatten(self): - node = DummySchemaNode(None, name='node') - node.children = [DummySchemaNode(DummyType(), name='foo')] - typ = self._makeOne() - result = typ.flatten(node, [1, 2]) - self.assertEqual(result, {'node.0': 1, 'node.1': 2}) - - def test_flatten_with_integer(self): - from colander import Integer - - node = DummySchemaNode(None, name='node') - node.children = [DummySchemaNode(Integer(), name='foo')] - typ = self._makeOne() - result = typ.flatten(node, [1, 2]) - self.assertEqual(result, {'node.0': 1, 'node.1': 2}) - - def test_flatten_listitem(self): - node = DummySchemaNode(None, name='node') - node.children = [DummySchemaNode(DummyType(), name='foo')] - typ = self._makeOne() - result = typ.flatten(node, [1, 2], listitem=True) - self.assertEqual(result, {'0': 1, '1': 2}) - - def test_unflatten(self): - node = DummySchemaNode(None, name='node') - node.children = [DummySchemaNode(DummyType(), name='foo')] - typ = self._makeOne() - result = typ.unflatten( - node, ['node.0', 'node.1'], {'node.0': 'a', 'node.1': 'b'} - ) - self.assertEqual(result, ['a', 'b']) - - def test_setvalue(self): - typ = self._makeOne() - node1 = DummySchemaNode(typ, name='seq1') - node2 = DummySchemaNode(typ, name='seq2') - node1.children = [node2] - node2.children = DummySchemaNode(None, name='items') - appstruct = [[1, 2], [3, 4]] - typ.set_value(node1, appstruct, '1.0', 34) - self.assertEqual(appstruct, [[1, 2], [34, 4]]) - - def test_getvalue(self): - typ = self._makeOne() - node1 = DummySchemaNode(typ, name='seq1') - node2 = DummySchemaNode(typ, name='seq2') - node1.children = [node2] - node2.children = DummySchemaNode(None, name='items') - appstruct = [[1, 2], [3, 4]] - self.assertEqual(typ.get_value(node1, appstruct, '1'), [3, 4]) - self.assertEqual(typ.get_value(node1, appstruct, '1.0'), 3) - - def test_cstruct_children_cstruct_is_null(self): - from colander import SequenceItems - from colander import null - - typ = self._makeOne() - result = typ.cstruct_children(None, null) - self.assertEqual(result, SequenceItems([])) - - def test_cstruct_children_cstruct_is_non_null(self): - from colander import SequenceItems - - typ = self._makeOne() - result = typ.cstruct_children(None, ['a']) - self.assertEqual(result, SequenceItems(['a'])) - - -class TestString(unittest.TestCase): - def _makeOne(self, encoding=None, allow_empty=False): - from colander import String - - return String(encoding, allow_empty) - - def test_alias(self): - from colander import Str - from colander import String - - self.assertEqual(Str, String) - - def test_deserialize_emptystring(self): - from colander import null - - node = DummySchemaNode(None) - typ = self._makeOne(None) - result = typ.deserialize(node, '') - self.assertEqual(result, null) - typ = self._makeOne(None, allow_empty=True) - result = typ.deserialize(node, '') - self.assertEqual(result, '') - - def test_deserialize_uncooperative(self): - val = Uncooperative() - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, val) - self.assertTrue(e.msg) - - def test_deserialize_unicode_from_None(self): - uni = str(b'\xe3\x81\x82', 'utf-8') - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, uni) - self.assertEqual(result, uni) - - def test_deserialize_nonunicode_from_None(self): - - value = object() - node = DummySchemaNode(None) - typ = self._makeOne() - self.assertRaises(colander.Invalid, typ.deserialize, node, value) - - def test_deserialize_from_utf8(self): - uni = str(b'\xe3\x81\x82', 'utf-8') - utf8 = uni.encode('utf-8') - node = DummySchemaNode(None) - typ = self._makeOne('utf-8') - result = typ.deserialize(node, utf8) - self.assertEqual(result, uni) - - def test_deserialize_from_utf16(self): - uni = str(b'\xe3\x81\x82', 'utf-8') - utf16 = uni.encode('utf-16') - node = DummySchemaNode(None) - typ = self._makeOne('utf-16') - result = typ.deserialize(node, utf16) - self.assertEqual(result, uni) - - def test_deserialize_from_nonstring_obj(self): - - value = object() - node = DummySchemaNode(None) - typ = self._makeOne() - self.assertRaises(colander.Invalid, typ.deserialize, node, value) - - def test_serialize_null(self): - from colander import null - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, null) - self.assertEqual(result, null) - - def test_serialize_emptystring(self): - val = '' - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, val) - - def test_serialize_uncooperative(self): - val = Uncooperative() - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.serialize, node, val) - self.assertTrue(e.msg) - - def test_serialize_nonunicode_to_None(self): - value = object() - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, value) - self.assertEqual(result, str(value)) - - def test_serialize_unicode_to_None(self): - value = 'abc' - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, value) - self.assertEqual(result, value) - - def test_serialize_to_utf8(self): - uni = str(b'\xe3\x81\x82', 'utf-8') - utf8 = uni.encode('utf-8') - node = DummySchemaNode(None) - typ = self._makeOne('utf-8') - result = typ.serialize(node, uni) - self.assertEqual(result, utf8) - - def test_serialize_to_utf16(self): - uni = str(b'\xe3\x81\x82', 'utf-8') - utf16 = uni.encode('utf-16') - node = DummySchemaNode(None) - typ = self._makeOne('utf-16') - result = typ.serialize(node, uni) - self.assertEqual(result, utf16) - - def test_serialize_bytes(self): - not_utf8 = b'\xff\xfe\xf8\x00' - node = DummySchemaNode(None) - typ = self._makeOne('utf-8') - result = typ.serialize(node, not_utf8) - self.assertEqual(result, str(not_utf8).encode('utf8')) - - def test_serialize_encoding_with_non_string_type(self): - utf8 = b'123' - node = DummySchemaNode(None) - typ = self._makeOne('utf-8') - result = typ.serialize(node, 123) - self.assertEqual(result, utf8) - - -class TestInteger(unittest.TestCase): - def _makeOne(self, strict=False): - from colander import Integer - - return Integer(strict=strict) - - def test_alias(self): - from colander import Int - from colander import Integer - - self.assertEqual(Int, Integer) - - def test_serialize_null(self): - - val = colander.null - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_serialize_none(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, None) - self.assertEqual(result, colander.null) - - def test_deserialize_emptystring(self): - - val = '' - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, val) - self.assertEqual(result, colander.null) - - def test_deserialize_fails(self): - val = 'P' - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, val) - self.assertTrue(e.msg) - - def test_deserialize_ok(self): - val = '1' - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, val) - self.assertEqual(result, 1) - - def test_serialize_fails(self): - val = 'P' - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.serialize, node, val) - self.assertTrue(e.msg) - - def test_serialize_ok(self): - val = 1 - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, '1') - - def test_serialize_zero(self): - val = 0 - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, '0') - - def test_serialize_strict_float(self): - val = 1.2 - node = DummySchemaNode(None) - typ = self._makeOne(strict=True) - e = invalid_exc(typ.serialize, node, val) - self.assertTrue(e.msg) - - def test_serialize_strict_int(self): - val = 1 - node = DummySchemaNode(None) - typ = self._makeOne(strict=True) - result = typ.serialize(node, val) - self.assertEqual(result, '1') - - def test_deserialize_strict(self): - val = '58' - node = DummySchemaNode(None) - typ = self._makeOne(strict=True) - result = typ.deserialize(node, val) - self.assertEqual(result, 58) - - def test_serialize_truncates(self): - val = 1.4 - node = DummySchemaNode(None) - typ = self._makeOne(strict=False) - result = typ.serialize(node, val) - self.assertEqual(result, '1') - - -class TestFloat(unittest.TestCase): - def _makeOne(self): - from colander import Float - - return Float() - - def test_serialize_null(self): - - val = colander.null - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_serialize_none(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, None) - self.assertEqual(result, colander.null) - - def test_serialize_zero(self): - val = 0 - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, '0.0') - - def test_serialize_emptystring(self): - - val = '' - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, val) - self.assertEqual(result, colander.null) - - def test_deserialize_fails(self): - val = 'P' - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, val) - self.assertTrue(e.msg) - - def test_deserialize_ok(self): - val = '1.0' - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, val) - self.assertEqual(result, 1.0) - - def test_serialize_fails(self): - val = 'P' - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.serialize, node, val) - self.assertTrue(e.msg) - - def test_serialize_ok(self): - val = 1.0 - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, '1.0') - - -class TestDecimal(unittest.TestCase): - def _makeOne(self, quant=None, rounding=None, normalize=False): - from colander import Decimal - - return Decimal(quant, rounding, normalize) - - def test_serialize_null(self): - - val = colander.null - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_serialize_none(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, None) - self.assertEqual(result, colander.null) - - def test_serialize_zero(self): - val = 0 - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, '0') - - def test_serialize_emptystring(self): - - val = '' - node = DummySchemaNode(None) - typ = self._makeOne() - self.assertRaises(colander.Invalid, typ.serialize, node, val) - - def test_serialize_quantize_no_rounding(self): - val = '.000001' - node = DummySchemaNode(None) - typ = self._makeOne('.01') - result = typ.serialize(node, val) - self.assertEqual(result, '0.00') - - def test_serialize_quantize_with_rounding_up(self): - import decimal - - val = '.000001' - node = DummySchemaNode(None) - typ = self._makeOne('.01', decimal.ROUND_UP) - result = typ.serialize(node, val) - self.assertEqual(result, '0.01') - - def test_serialize_normalize(self): - from decimal import Decimal - - val = Decimal('1.00') - node = DummySchemaNode(None) - typ = self._makeOne(normalize=True) - result = typ.serialize(node, val) - self.assertEqual(result, '1') - - def test_deserialize_fails(self): - val = 'P' - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, val) - self.assertTrue(e.msg) - - def test_deserialize_ok(self): - import decimal - - val = '1.0' - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, val) - self.assertEqual(result, decimal.Decimal('1.0')) - - def test_deserialize_with_quantize(self): - import decimal - - val = '1.00000001' - node = DummySchemaNode(None) - typ = self._makeOne('.01', decimal.ROUND_UP) - result = typ.deserialize(node, val) - self.assertEqual(result, decimal.Decimal('1.01')) - - def test_deserialize_with_normalize(self): - from decimal import Decimal - - val = '1.00' - node = DummySchemaNode(None) - typ = self._makeOne(normalize=True) - result = typ.deserialize(node, val) - self.assertEqual(result, Decimal('1')) - self.assertEqual(str(result), '1') - - def test_serialize_fails(self): - val = 'P' - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.serialize, node, val) - self.assertTrue(e.msg) - - def test_serialize_ok(self): - val = 1.0 - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, '1.0') - - -class TestMoney(unittest.TestCase): - def _makeOne(self): - from colander import Money - - return Money() - - def test_serialize_rounds_up(self): - val = '1.000001' - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, '1.01') - - def test_deserialize_rounds_up(self): - import decimal - - val = '1.00000001' - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, val) - self.assertEqual(result, decimal.Decimal('1.01')) - - -class TestBoolean(unittest.TestCase): - def _makeOne(self): - from colander import Boolean - - return Boolean() - - def test_alias(self): - from colander import Bool - from colander import Boolean - - self.assertEqual(Bool, Boolean) - - def test_serialize_null(self): - - val = colander.null - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_deserialize(self): - typ = self._makeOne() - node = DummySchemaNode(None) - self.assertEqual(typ.deserialize(node, 'false'), False) - self.assertEqual(typ.deserialize(node, 'FALSE'), False) - self.assertEqual(typ.deserialize(node, '0'), False) - self.assertEqual(typ.deserialize(node, 'true'), True) - self.assertEqual(typ.deserialize(node, 'other'), True) - - def test_deserialize_unstringable(self): - typ = self._makeOne() - node = DummySchemaNode(None) - e = invalid_exc(typ.deserialize, node, Uncooperative()) - self.assertTrue(e.msg.endswith('not a string')) - - def test_deserialize_null(self): - - typ = self._makeOne() - node = DummySchemaNode(None) - result = typ.deserialize(node, colander.null) - self.assertEqual(result, colander.null) - - def test_serialize(self): - typ = self._makeOne() - node = DummySchemaNode(None) - self.assertEqual(typ.serialize(node, 1), 'true') - self.assertEqual(typ.serialize(node, True), 'true') - self.assertEqual(typ.serialize(node, None), 'false') - self.assertEqual(typ.serialize(node, False), 'false') - - -class TestBooleanCustomFalseReprs(unittest.TestCase): - def _makeOne(self): - from colander import Boolean - - return Boolean(false_choices=('n', 'f')) - - def test_deserialize(self): - typ = self._makeOne() - node = DummySchemaNode(None) - self.assertEqual(typ.deserialize(node, 'f'), False) - self.assertEqual(typ.deserialize(node, 'N'), False) - self.assertEqual(typ.deserialize(node, 'other'), True) - - -class TestBooleanCustomFalseAndTrueReprs(unittest.TestCase): - def _makeOne(self): - from colander import Boolean - - return Boolean(false_choices=('n', 'f'), true_choices=('y', 't')) - - def test_deserialize(self): - - typ = self._makeOne() - node = DummySchemaNode(None) - self.assertEqual(typ.deserialize(node, 'f'), False) - self.assertEqual(typ.deserialize(node, 'N'), False) - self.assertEqual(typ.deserialize(node, 'T'), True) - self.assertEqual(typ.deserialize(node, 'y'), True) - self.assertRaises(colander.Invalid, typ.deserialize, node, 'other') - try: - typ.deserialize(node, 'other') - except colander.Invalid as exc: - self.assertEqual(exc.msg.mapping['false_choices'], "'n', 'f'") - self.assertEqual(exc.msg.mapping['true_choices'], "'y', 't'") - - -class TestBooleanCustomSerializations(unittest.TestCase): - def _makeOne(self): - from colander import Boolean - - return Boolean(false_val='no', true_val='yes') - - def test_serialize(self): - typ = self._makeOne() - node = DummySchemaNode(None) - self.assertEqual(typ.serialize(node, 1), 'yes') - self.assertEqual(typ.serialize(node, True), 'yes') - self.assertEqual(typ.serialize(node, None), 'no') - self.assertEqual(typ.serialize(node, False), 'no') - - -class TestGlobalObject(unittest.TestCase): - def _makeOne(self, package=None): - from colander import GlobalObject - - return GlobalObject(package) - - def test_zope_dottedname_style_resolve_absolute(self): - typ = self._makeOne() - result = typ._zope_dottedname_style( - None, 'tests.test_colander.TestGlobalObject' - ) - self.assertEqual(result, self.__class__) - - def test_zope_dottedname_style_irrresolveable_absolute(self): - typ = self._makeOne() - self.assertRaises( - ImportError, - typ._zope_dottedname_style, - None, - 'tests.nonexisting', - ) - - def test__zope_dottedname_style_resolve_relative(self): - from tests.relative import ImportableClass - - typ = self._makeOne(package=tests) - node = DummySchemaNode(None) - result = typ._zope_dottedname_style(node, '.relative.ImportableClass') - self.assertEqual(result, ImportableClass().__class__) - - def test__zope_dottedname_style_resolve_relative_leading_dots(self): - - typ = self._makeOne(package=tests) - node = DummySchemaNode(None) - result = typ._zope_dottedname_style( - node, '..tests.test_colander.TestGlobalObject' - ) - self.assertEqual(result, self.__class__) - - def test__zope_dottedname_style_resolve_relative_is_dot(self): - - typ = self._makeOne(package=tests) - result = typ._zope_dottedname_style(None, '.') - self.assertEqual(result, tests) - - def test__zope_dottedname_style_irresolveable_relative_is_dot(self): - typ = self._makeOne() - e = invalid_exc(typ._zope_dottedname_style, None, '.') - self.assertEqual( - e.msg.interpolate(), - 'relative name "." irresolveable without package', - ) - - def test_zope_dottedname_style_resolve_relative_nocurrentpackage(self): - typ = self._makeOne() - e = invalid_exc(typ._zope_dottedname_style, None, '.whatever') - self.assertEqual( - e.msg.interpolate(), - 'relative name ".whatever" irresolveable without package', - ) - - def test_zope_dottedname_style_irrresolveable_relative(self): - - typ = self._makeOne(package=colander) - self.assertRaises( - ImportError, typ._zope_dottedname_style, None, '.notexisting' - ) - - def test__zope_dottedname_style_resolveable_relative(self): - from tests import relative - - typ = self._makeOne(package=tests) - result = typ._zope_dottedname_style(None, '.relative') - self.assertEqual(result, relative) - - def test__zope_dottedname_style_irresolveable_absolute(self): - typ = self._makeOne() - self.assertRaises( - ImportError, typ._zope_dottedname_style, None, 'colander.fudge.bar' - ) - - def test__zope_dottedname_style_resolveable_absolute(self): - typ = self._makeOne() - result = typ._zope_dottedname_style( - None, 'tests.test_colander.TestGlobalObject' - ) - self.assertEqual(result, self.__class__) - - def test__pkg_resources_style_resolve_absolute(self): - typ = self._makeOne() - result = typ._pkg_resources_style( - None, 'tests.test_colander:TestGlobalObject' - ) - self.assertEqual(result, self.__class__) - - def test__pkg_resources_style_irrresolveable_absolute(self): - typ = self._makeOne() - self.assertRaises( - ImportError, - typ._pkg_resources_style, - None, - 'tests.test_colander:nonexisting', - ) - - def test__pkg_resources_style_resolve_relative_startswith_colon(self): - - typ = self._makeOne(package=tests) - result = typ._pkg_resources_style(None, ':fixture') - self.assertEqual(result, 1) - - def test__pkg_resources_style_resolve_relative_startswith_dot(self): - from tests.relative import ImportableClass - - typ = self._makeOne(package=tests) - result = typ._pkg_resources_style(None, '.relative:ImportableClass') - self.assertEqual(result, ImportableClass().__class__) - - def test__pkg_resources_style_resolve_relative_is_dot(self): - - typ = self._makeOne(package=tests) - result = typ._pkg_resources_style(None, '.') - self.assertEqual(result, tests) - - def test__pkg_resources_style_resolve_relative_nocurrentpackage(self): - typ = self._makeOne() - - self.assertRaises( - colander.Invalid, typ._pkg_resources_style, None, '.whatever' - ) - - def test__pkg_resources_style_irrresolveable_relative(self): - - typ = self._makeOne(package=colander) - self.assertRaises( - ImportError, typ._pkg_resources_style, None, ':notexisting' - ) - - def test_deserialize_None(self): - - typ = self._makeOne() - node = DummySchemaNode(None) - result = typ.deserialize(node, None) - self.assertEqual(result, colander.null) - - def test_deserialize_null(self): - - typ = self._makeOne() - node = DummySchemaNode(None) - result = typ.deserialize(node, colander.null) - self.assertEqual(result, colander.null) - - def test_deserialize_notastring(self): - - typ = self._makeOne() - node = DummySchemaNode(None) - self.assertRaises(colander.Invalid, typ.deserialize, node, True) - - def test_deserialize_using_pkgresources_style(self): - typ = self._makeOne() - node = DummySchemaNode(None) - result = typ.deserialize(node, 'tests.test_colander:TestGlobalObject') - self.assertEqual(result, self.__class__) - - def test_deserialize_using_zope_dottedname_style(self): - typ = self._makeOne() - node = DummySchemaNode(None) - result = typ.deserialize(node, 'tests.test_colander.TestGlobalObject') - self.assertEqual(result, self.__class__) - - def test_deserialize_style_raises(self): - typ = self._makeOne() - node = DummySchemaNode(None) - e = invalid_exc(typ.deserialize, node, 'cant.be.found') - self.assertEqual( - e.msg.interpolate(), - 'The dotted name "cant.be.found" cannot be imported', - ) - - def test_serialize_null(self): - - val = colander.null - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_serialize_ok(self): - - typ = self._makeOne() - node = DummySchemaNode(None) - result = typ.serialize(node, tests) - self.assertEqual(result, 'tests') - - typ = self._makeOne() - node = DummySchemaNode(None) - result = typ.serialize(node, tests) - self.assertEqual(result, 'tests') - - def test_serialize_class(self): - cls = self.__class__ - typ = self._makeOne() - node = DummySchemaNode(None) - result = typ.serialize(node, cls) - self.assertEqual(result, 'tests.test_colander.TestGlobalObject') - - def test_deserialize_class_ok(self): - from tests.relative import ImportableClass - - names = ( - 'tests.relative.ImportableClass', - '.relative.ImportableClass', - ) - typ = self._makeOne(tests) - node = DummySchemaNode(None) - for name in names: - result = typ.deserialize(node, name) - self.assertEqual(result, ImportableClass) - - names = ('.ImportableClass',) - typ = self._makeOne(tests.relative) - node = DummySchemaNode(None) - for name in names: - result = typ.deserialize(node, name) - self.assertEqual(result, ImportableClass) - - def test_deserialize_class_fail(self): - - names = ('.test_colander.TestGlobalObject', '.TestGlobalObject') - typ = self._makeOne(colander) - node = DummySchemaNode(None) - for name in names: - e = invalid_exc(typ.deserialize, node, name) - self.assertEqual( - e.msg.interpolate(), - f'The dotted name "{name}" cannot be imported', - ) - - def test_serialize_fail(self): - typ = self._makeOne() - node = DummySchemaNode(None) - e = invalid_exc(typ.serialize, node, None) - self.assertEqual(e.msg.interpolate(), '"None" has no __name__') - - -class TestDateTime(unittest.TestCase): - def _makeOne(self, *arg, **kw): - from colander import DateTime - - return DateTime(*arg, **kw) - - def _dt(self): - import datetime - - return datetime.datetime(2010, 4, 26, 10, 48) - - def _today(self): - import datetime - - return datetime.date.today() - - def test_ctor_default_tzinfo_not_specified(self): - from iso8601 import iso8601 - - typ = self._makeOne() - self.assertIs(typ.default_tzinfo, iso8601.UTC) - - def test_ctor_default_tzinfo_None(self): - typ = self._makeOne(default_tzinfo=None) - self.assertEqual(typ.default_tzinfo, None) - - def test_ctor_default_tzinfo_non_None(self): - from iso8601 import iso8601 - - tzinfo = iso8601.FixedOffset(1, 0, 'myname') - typ = self._makeOne(default_tzinfo=tzinfo) - self.assertEqual(typ.default_tzinfo, tzinfo) - - def test_serialize_null(self): - - val = colander.null - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_serialize_none(self): - - val = None - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_serialize_with_garbage(self): - typ = self._makeOne() - node = DummySchemaNode(None) - e = invalid_exc(typ.serialize, node, 'garbage') - self.assertEqual( - e.msg.interpolate(), '"garbage" is not a datetime object' - ) - - def test_serialize_with_date(self): - import datetime - - typ = self._makeOne() - date = self._today() - node = DummySchemaNode(None) - result = typ.serialize(node, date) - expected = datetime.datetime.combine(date, datetime.time()) - expected = expected.replace(tzinfo=typ.default_tzinfo).isoformat() - self.assertEqual(result, expected) - - def test_serialize_with_naive_datetime(self): - typ = self._makeOne() - node = DummySchemaNode(None) - dt = self._dt() - result = typ.serialize(node, dt) - expected = dt.replace(tzinfo=typ.default_tzinfo).isoformat() - self.assertEqual(result, expected) - - def test_serialize_with_naive_datetime_and_custom_format(self): - fmt = '%Y%m%d!%H%M%S' - typ = self._makeOne(format=fmt) - node = DummySchemaNode(None) - dt = self._dt() - result = typ.serialize(node, dt) - expected = dt.replace(tzinfo=typ.default_tzinfo).strftime(fmt) - self.assertEqual(result, expected) - - def test_serialize_with_none_tzinfo_naive_datetime(self): - typ = self._makeOne(default_tzinfo=None) - node = DummySchemaNode(None) - dt = self._dt() - result = typ.serialize(node, dt) - self.assertEqual(result, dt.isoformat()) - - def test_serialize_with_none_tzinfo_naive_datetime_custom_format(self): - fmt = '%Y%m%d!%H%M%S' - typ = self._makeOne(default_tzinfo=None, format=fmt) - node = DummySchemaNode(None) - dt = self._dt() - result = typ.serialize(node, dt) - self.assertEqual(result, dt.strftime(fmt)) - - def test_serialize_with_tzware_datetime(self): - from iso8601 import iso8601 - - typ = self._makeOne() - dt = self._dt() - tzinfo = iso8601.FixedOffset(1, 0, 'myname') - dt = dt.replace(tzinfo=tzinfo) - node = DummySchemaNode(None) - result = typ.serialize(node, dt) - expected = dt.isoformat() - self.assertEqual(result, expected) - - def test_deserialize_date(self): - import datetime - from iso8601 import iso8601 - - date = self._today() - typ = self._makeOne() - formatted = date.isoformat() - node = DummySchemaNode(None) - result = typ.deserialize(node, formatted) - expected = datetime.datetime.combine(result, datetime.time()) - expected = expected.replace(tzinfo=iso8601.UTC) - self.assertEqual(result.isoformat(), expected.isoformat()) - - def test_deserialize_invalid_ParseError(self): - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, 'garbage') - self.assertTrue('Invalid' in e.msg) - - def test_deserialize_slashes_invalid(self): - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, '2013/05/31') - self.assertTrue('Invalid' in e.msg) - - def test_deserialize_null(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, colander.null) - self.assertEqual(result, colander.null) - - def test_deserialize_empty(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, '') - self.assertEqual(result, colander.null) - - def test_deserialize_success(self): - from iso8601 import iso8601 - - typ = self._makeOne() - dt = self._dt() - tzinfo = iso8601.FixedOffset(1, 0, 'myname') - dt = dt.replace(tzinfo=tzinfo) - iso = dt.isoformat() - node = DummySchemaNode(None) - result = typ.deserialize(node, iso) - self.assertEqual(result.isoformat(), iso) - - def test_deserialize_datetime_with_custom_format(self): - from iso8601 import iso8601 - - fmt = '%Y%m%d.%H%M%S' - typ = self._makeOne(format=fmt) - dt = self._dt() - tzinfo = iso8601.FixedOffset(1, 0, 'myname') - dt = dt.replace(tzinfo=tzinfo) - expected = dt.strftime(fmt) - node = DummySchemaNode(None) - result = typ.deserialize(node, expected) - self.assertEqual(result.strftime(fmt), expected) - - def test_deserialize_naive_with_default_tzinfo(self): - from iso8601 import iso8601 - - tzinfo = iso8601.FixedOffset(1, 0, 'myname') - typ = self._makeOne(default_tzinfo=tzinfo) - dt = self._dt() - dt_with_tz = dt.replace(tzinfo=tzinfo) - iso = dt.isoformat() - node = DummySchemaNode(None) - result = typ.deserialize(node, iso) - self.assertEqual(result.isoformat(), dt_with_tz.isoformat()) - - def test_deserialize_none_tzinfo(self): - typ = self._makeOne(default_tzinfo=None) - dt = self._dt() - iso = dt.isoformat() - node = DummySchemaNode(None) - result = typ.deserialize(node, iso) - self.assertEqual(result.isoformat(), dt.isoformat()) - self.assertEqual(result.tzinfo, None) - - def test_deserialize_invalid_type(self): - from colander import Invalid - - typ = self._makeOne(default_tzinfo=None, format='%d/%m/%Y') - node = DummySchemaNode(None) - self.assertRaises(Invalid, typ.deserialize, node, 10012001) - - class Anon: - pass - - self.assertRaises(Invalid, typ.deserialize, node, Anon()) - - -class TestDate(unittest.TestCase): - def _makeOne(self, *arg, **kw): - from colander import Date - - return Date(*arg, **kw) - - def _dt(self): - import datetime - - return datetime.datetime(2010, 4, 26, 10, 48) - - def _today(self): - import datetime - - return datetime.date.today() - - def test_serialize_null(self): - - val = colander.null - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_serialize_none(self): - - val = None - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_serialize_with_garbage(self): - typ = self._makeOne() - node = DummySchemaNode(None) - e = invalid_exc(typ.serialize, node, 'garbage') - self.assertEqual(e.msg.interpolate(), '"garbage" is not a date object') - - def test_serialize_with_date(self): - typ = self._makeOne() - date = self._today() - node = DummySchemaNode(None) - result = typ.serialize(node, date) - expected = date.isoformat() - self.assertEqual(result, expected) - - def test_serialize_with_datetime(self): - typ = self._makeOne() - dt = self._dt() - node = DummySchemaNode(None) - result = typ.serialize(node, dt) - expected = dt.date().isoformat() - self.assertEqual(result, expected) - - def test_deserialize_invalid_ParseError(self): - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, 'garbage') - self.assertTrue('Invalid' in e.msg) - - def test_deserialize_invalid_weird(self): - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, '10-10-10-10') - self.assertTrue('Invalid' in e.msg) - - def test_deserialize_null(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, colander.null) - self.assertEqual(result, colander.null) - - def test_deserialize_empty(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, '') - self.assertEqual(result, colander.null) - - def test_deserialize_success_date(self): - typ = self._makeOne() - date = self._today() - iso = date.isoformat() - node = DummySchemaNode(None) - result = typ.deserialize(node, iso) - self.assertEqual(result.isoformat(), iso) - - def test_deserialize_success_datetime(self): - dt = self._dt() - typ = self._makeOne() - iso = dt.isoformat() - node = DummySchemaNode(None) - result = typ.deserialize(node, iso) - self.assertEqual(result.isoformat(), dt.date().isoformat()) - - def test_serialize_date_with_custom_format(self): - fmt = '%m,%Y,%d' - typ = self._makeOne(format=fmt) - date = self._today() - node = DummySchemaNode(None) - result = typ.serialize(node, date) - self.assertEqual(result, date.strftime(fmt)) - - def test_deserialize_date_with_custom_format(self): - date = self._today() - fmt = '%d/%m/%Y' - typ = self._makeOne(format=fmt) - formatted = date.strftime(fmt) - node = DummySchemaNode(None) - result = typ.deserialize(node, formatted) - self.assertEqual(result, date) - - def test_deserialize_date_with_custom_format_invalid_type(self): - from colander import Invalid - - typ = self._makeOne(format='%d/%m/%Y') - node = DummySchemaNode(None) - self.assertRaises(Invalid, typ.deserialize, node, 123) - - class Anon: - pass - - self.assertRaises(Invalid, typ.deserialize, node, Anon()) - - def test_deserialize_date_with_incorrect_format(self): - from colander import Invalid - - typ = self._makeOne(format='%d/%m/%Y') - node = DummySchemaNode(None) - self.assertRaises(Invalid, typ.deserialize, node, "2001-01-01") - self.assertRaises(Invalid, typ.deserialize, node, "01012001") - - -class TestTime(unittest.TestCase): - def _makeOne(self, *arg, **kw): - from colander import Time - - return Time(*arg, **kw) - - def _dt(self): - import datetime - - return datetime.datetime(2010, 4, 26, 10, 48, 0, 424242) - - def _now(self): - import datetime - - return datetime.datetime.now().time() - - def test_serialize_null(self): - - val = colander.null - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_serialize_none(self): - - val = None - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.serialize(node, val) - self.assertEqual(result, colander.null) - - def test_serialize_with_garbage(self): - typ = self._makeOne() - node = DummySchemaNode(None) - e = invalid_exc(typ.serialize, node, 'garbage') - self.assertEqual(e.msg.interpolate(), '"garbage" is not a time object') - - def test_serialize_with_time(self): - typ = self._makeOne() - time = self._now() - node = DummySchemaNode(None) - result = typ.serialize(node, time) - expected = time.isoformat() - self.assertEqual(result, expected) - - def test_serialize_with_zero_time(self): - import datetime - - typ = self._makeOne() - time = datetime.time(0) - node = DummySchemaNode(None) - result = typ.serialize(node, time) - expected = time.isoformat() - self.assertEqual(result, expected) - - def test_serialize_with_datetime(self): - typ = self._makeOne() - dt = self._dt() - node = DummySchemaNode(None) - result = typ.serialize(node, dt) - expected = dt.time().isoformat() - self.assertEqual(result, expected) - - def test_deserialize_invalid_ParseError(self): - node = DummySchemaNode(None) - typ = self._makeOne() - e = invalid_exc(typ.deserialize, node, 'garbage') - self.assertTrue('Invalid' in e.msg) - - def test_deserialize_three_digit_string(self): - import datetime - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, '11:00:11') - self.assertEqual(result, datetime.time(11, 0, 11)) - - def test_deserialize_four_digit_string(self): - import datetime - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, '11:00:11.424242') - self.assertEqual(result, datetime.time(11, 0, 11, 424242)) - - def test_deserialize_two_digit_string(self): - import datetime - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, '11:00') - self.assertEqual(result, datetime.time(11, 0)) - - def test_deserialize_null(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, colander.null) - self.assertEqual(result, colander.null) - - def test_deserialize_empty(self): - - node = DummySchemaNode(None) - typ = self._makeOne() - result = typ.deserialize(node, '') - self.assertEqual(result, colander.null) - - def test_deserialize_missing_seconds(self): - import datetime - - typ = self._makeOne() - node = DummySchemaNode(None) - result = typ.deserialize(node, '10:12') - self.assertEqual(result, datetime.time(10, 12)) - - def test_deserialize_success_time(self): - import datetime - - typ = self._makeOne() - node = DummySchemaNode(None) - result = typ.deserialize(node, '10:12:13') - self.assertEqual(result, datetime.time(10, 12, 13)) - - def test_deserialize_success_datetime(self): - dt = self._dt() - typ = self._makeOne() - iso = dt.isoformat() - node = DummySchemaNode(None) - result = typ.deserialize(node, iso) - self.assertEqual(result.isoformat(), dt.time().isoformat()) - - -class TestEnum(unittest.TestCase): - def _makeOne(self): - - import enum - - class DummyEnum(enum.Enum): - red = 0 - green = 1 - blue = 2 - - typ = colander.Enum(DummyEnum) - return DummyEnum, typ - - def _makeOneIntVal(self): - - import enum - - class DummyEnum(enum.Enum): - red = 0 - green = 1 - blue = 2 - - typ = colander.Enum(DummyEnum, attr='value', typ=colander.Integer()) - return DummyEnum, typ - - def _makeOneStrVal(self): - - import enum - - class DummyEnum(enum.Enum): - red = 'RED' - green = 'GREEN' - blue = 'BLUE' - - typ = colander.Enum(DummyEnum, attr='value', typ=colander.String()) - return DummyEnum, typ - - def test_non_unique_failure(self): - - import enum - - class NonUniqueEnum(enum.Enum): - one = 1 - other = 1 - - self.assertRaises( - ValueError, - colander.Enum, - NonUniqueEnum, - attr='value', - typ=colander.Integer(), - ) - - def test_non_unique_failure2(self): - - import enum - - class NonUniqueEnum(enum.Enum): - some = (1, 1) - other = (1, 2) - - def __init__(self, val0, val1): - self.val0 = val0 - self.val1 = val1 - - self.assertRaises( - ValueError, - colander.Enum, - NonUniqueEnum, - attr='val0', - typ=colander.Integer(), - ) - - def test_serialize_null(self): - - e, typ = self._makeOne() - val = colander.null - node = DummySchemaNode(None) - result = typ.serialize(node, val) - self.assertIs(result, colander.null) - - def test_serialize_name(self): - e, typ = self._makeOne() - val = e.red - node = DummySchemaNode(None) - result = typ.serialize(node, val) - self.assertEqual(result, 'red') - - def test_serialize_value_int(self): - e, typ = self._makeOneIntVal() - val = e.green - node = DummySchemaNode(None) - result = typ.serialize(node, val) - self.assertEqual(result, '1') - - def test_serialize_value_str(self): - e, typ = self._makeOneStrVal() - val = e.blue - node = DummySchemaNode(None) - result = typ.serialize(node, val) - self.assertEqual(result, 'BLUE') - - def test_serialize_failure(self): - e, typ = self._makeOne() - val = 'not a enum' - node = DummySchemaNode(None) - invalid_exc(typ.serialize, node, val) - - def test_deserialize_null(self): - - e, typ = self._makeOne() - val = '' - node = DummySchemaNode(None) - result = typ.deserialize(node, val) - self.assertIs(result, colander.null) - - def test_deserialize_name(self): - e, typ = self._makeOne() - val = 'green' - node = DummySchemaNode(None) - result = typ.deserialize(node, val) - self.assertIs(result, e.green) - - def test_deserialize_value_int(self): - e, typ = self._makeOneIntVal() - val = '2' - node = DummySchemaNode(None) - result = typ.deserialize(node, val) - self.assertIs(result, e.blue) - - def test_deserialize_value_str(self): - e, typ = self._makeOneStrVal() - val = 'BLUE' - node = DummySchemaNode(None) - result = typ.deserialize(node, val) - self.assertIs(result, e.blue) - - def test_deserialize_failure(self): - e, typ = self._makeOne() - val = 'not a enum' - node = DummySchemaNode(None) - invalid_exc(typ.deserialize, node, val) - - def test_deserialize_failure_typ(self): - e, typ = self._makeOneIntVal() - val = 'not a int' - node = DummySchemaNode(None) - invalid_exc(typ.deserialize, node, val) - - class TestSchemaNode(unittest.TestCase): def _makeOne(self, *arg, **kw): from colander import SchemaNode diff --git a/tests/test_types.py b/tests/test_types.py new file mode 100644 index 0000000..7561f32 --- /dev/null +++ b/tests/test_types.py @@ -0,0 +1,3178 @@ +from unittest import mock + +import pytest +import translationstring + +NODE_NAME = "test_node" +NODE_VALUE = "test_node_value" +MARKER = object() +UNI = str(b'\xe3\x81\x82', 'utf-8') +UTF8 = UNI.encode("utf-8") +UTF16 = UNI.encode("utf-16") + + +@pytest.mark.parametrize( + "prefix, listitem, exp_key", + [ + (None, None, NODE_NAME), + (None, False, NODE_NAME), + (None, True, ""), + ("foo", None, f"foo{NODE_NAME}"), + ("foo", False, f"foo{NODE_NAME}"), + ("foo", True, "foo"), + ("foo.", None, f"foo.{NODE_NAME}"), + ("foo.", False, f"foo.{NODE_NAME}"), + ("foo.", True, "foo"), + ], +) +def test_SchemaType_flatten(prefix, listitem, exp_key): + from colander import SchemaType + + node = mock.Mock(spec_set=["name"]) + node.name = NODE_NAME + appstruct = mock.Mock(spec_set=()) + typ = SchemaType() + + kw = {} + + if prefix is not None: + kw["prefix"] = prefix + + if listitem is not None: + kw["listitem"] = listitem + + result = typ.flatten(node, appstruct, **kw) + + assert result == {exp_key: appstruct} + + +def test_SchemaType_unflatten(): + from colander import SchemaType + + node = mock.Mock(spec_set=["name"]) + node.name = NODE_NAME + paths = [NODE_NAME] + appstruct = mock.Mock(spec_set=()) + fstruct = {NODE_NAME: appstruct} + typ = SchemaType() + + assert typ.unflatten(node, paths, fstruct) is appstruct + + +def test_SchemaType_set_value(): + from colander import SchemaType + + node = mock.Mock(spec_set=["name"]) + node.name = NODE_NAME + appstruct = mock.Mock(spec_set=()) + path = NODE_NAME + typ = SchemaType() + + with pytest.raises(AssertionError): + typ.set_value(node, appstruct, path, NODE_VALUE) + + +def test_SchemaType_get_value(): + from colander import SchemaType + + node = mock.Mock(spec_set=["name"]) + node.name = NODE_NAME + appstruct = mock.Mock(spec_set=()) + path = NODE_NAME + typ = SchemaType() + + with pytest.raises(AssertionError): + typ.get_value(node, appstruct, path) + + +def test_SchemaType_cstruct_children(): + from colander import SchemaType + + node = mock.Mock(spec_set=()) + cstruct = mock.Mock(spec_set=()) + typ = SchemaType() + + assert typ.cstruct_children(node, cstruct) == [] + + +@pytest.mark.parametrize( + "unknown, exp_unknown", + [ + (None, "ignore"), + ("ignore", "ignore"), + ("raise", "raise"), + ("preserve", "preserve"), + ], +) +def test_Mapping_ctor(unknown, exp_unknown): + from colander import Mapping + + kw = {} + if unknown is not None: + kw["unknown"] = unknown + + typ = Mapping(**kw) + + assert typ.unknown == exp_unknown + + +@pytest.mark.parametrize( + "value, raises", + [ + ("ignore", False), + ("raise", False), + ("preserve", False), + ("bogus", True), + ], +) +def test_Mapping_unknwon_setter(value, raises): + from colander import Mapping + + typ = Mapping() + + if raises: + with pytest.raises(ValueError): + typ.unknown = value + else: + typ.unknown = value + assert typ.unknown == value + + +@pytest.mark.parametrize("value", [None, "", [], ()]) +def test_Mapping__validate_miss(value): + from colander import Invalid + from colander import Mapping + + node = mock.Mock(spec_set=()) + typ = Mapping() + + with pytest.raises(Invalid) as exc: + typ._validate(node, value) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a mapping type: ${err}' + assert invalid.msg.mapping["val"] is value + + err = invalid.msg.mapping["err"] + assert isinstance(err, TypeError) + assert err.args == ("Does not implement dict-like functionality.",) + + +def test_Mapping__validate_w_dict(): + from colander import Mapping + + node = mock.Mock(spec_set=()) + value = {"foo": "Foo"} + typ = Mapping() + + result = typ._validate(node, value) + + assert result == value + + +def test_Mapping__validate_w_dictlike(): + from collections import abc + + from colander import Mapping + + node = mock.Mock(spec_set=()) + value = mock.create_autospec(abc.Mapping) + value.items = [("foo", "Foo")] + expected = dict(value) + typ = Mapping() + + result = typ._validate(node, value) + + assert result == expected + + +def test_Mapping_cstruct_children_w_null_wo_children(): + from colander import Mapping + from colander import null + + node = mock.Mock(spec_set=["children"]) + node.children = [] + typ = Mapping() + + result = typ.cstruct_children(node, null) + + assert result == [] + + +def test_Mapping_cstruct_children_w_null_w_children(): + from colander import Mapping + from colander import null + + node = mock.Mock(spec_set=["children"]) + subnode = mock.Mock(spec_set=["name", "serialize"]) + subnode.name = "sub" + node.children = [subnode] + typ = Mapping() + + result = typ.cstruct_children(node, null) + + assert result == [subnode.serialize.return_value] + + +def test_Mapping_cstruct_children_w_dict_wo_children(): + from colander import Mapping + + node = mock.Mock(spec_set=["children"]) + node.children = [] + typ = Mapping() + + result = typ.cstruct_children(node, {"foo": "Foo"}) + + assert result == [] + + +def test_Mapping_cstruct_children_w_dict_w_children(): + from colander import Mapping + + node = mock.Mock(spec_set=["children"]) + node.children = [] + subnode_1 = mock.Mock(spec_set=["name", "serialize"]) + subnode_1.name = "foo" + subnode_2 = mock.Mock(spec_set=["name", "serialize"]) + subnode_2.name = "bar" + node.children = [subnode_1, subnode_2] + typ = Mapping() + + result = typ.cstruct_children(node, {"foo": "Foo"}) + + assert result == ["Foo", subnode_2.serialize.return_value] + + +@pytest.mark.parametrize( + "unknown, cstruct, exp_or_raises", + [ + ("ignore", "null", "null"), + ("ignore", {}, {}), + ("ignore", {"foo": "Foo"}, {}), + ("preserve", {"foo": "Foo"}, {"foo": "Foo"}), + ("raise", {"foo": "Foo"}, "UnsupportedFields"), + ], +) +def test_Mapping_deserialize_wo_children(unknown, cstruct, exp_or_raises): + from colander import Mapping + from colander import UnsupportedFields + from colander import null + + if cstruct == "null": + cstruct = null + + if exp_or_raises == "null": + exp_or_raises = null + + node = mock.Mock(spec_set=["children"]) + node.children = [] + typ = Mapping(unknown) + + if exp_or_raises == "UnsupportedFields": + with pytest.raises(UnsupportedFields): + typ.deserialize(node, cstruct) + else: + result = typ.deserialize(node, cstruct) + + assert result == exp_or_raises + + +def test_Mapping_deserialize_w_child_wo_missing(): + from colander import Invalid + from colander import Mapping + from colander import null + + node = mock.Mock(spec_set=["children", "typ"]) + sub = mock.Mock(spec_set=["name", "deserialize", "missing"]) + + sub.name = "foo" + sub.missing = None + + def _assert_not_null(value): + if value is null: + raise Invalid(sub) + + sub.deserialize = _assert_not_null + + node.children = [sub] + typ = Mapping() + cstruct = {} + + with pytest.raises(Invalid): + typ.deserialize(node, cstruct) + + +@pytest.mark.parametrize( + "cstruct, exp_appstruct", + [ + ("null", "null"), + ({}, {"foo": "Foo", "baz": "null"}), + ({"foo": "Foo"}, {"foo": "Foo", "baz": "null"}), + ( + {"foo": "Foo", "bar": "Bar"}, + {"foo": "Foo", "bar": "Bar", "baz": "null"}, + ), + ( + {"foo": "drop", "bar": "Bar"}, + {"bar": "Bar", "baz": "null"}, + ), + ( + {"foo": "subdrop", "bar": "Bar"}, + {"bar": "Bar", "baz": "null"}, + ), + ( + {"foo": "Foo", "baz": "Baz"}, + {"foo": "Foo", "baz": "Baz"}, + ), + ( + {"foo": "Foo", "bar": "Bar", "baz": "Baz"}, + {"foo": "Foo", "bar": "Bar", "baz": "Baz"}, + ), + ], +) +def test_Mapping_deserialize_w_children(cstruct, exp_appstruct): + from colander import Mapping + from colander import drop + from colander import null + + if cstruct == "null": + cstruct = null + else: + for key, value in list(cstruct.items()): + if value == "drop": + cstruct[key] = drop + + if exp_appstruct == "null": + exp_appstruct = null + else: + for key, value in list(exp_appstruct.items()): + if value == "null": + exp_appstruct[key] = null + + def _make_subnode(name, missing): + sub = mock.Mock(spec_set=["name", "deserialize", "missing"]) + sub.name = name + if cstruct is not null and cstruct.get(name) == "subdrop": + sub.deserialize.return_value = drop + elif exp_appstruct is not null and name in exp_appstruct: + sub.deserialize.return_value = exp_appstruct[name] + else: + sub.deserialize.side_effect = AssertionError + sub.missing = missing + return sub + + node = mock.Mock(spec_set=["children"]) + node.children = [ + _make_subnode("foo", None), + _make_subnode("bar", drop), + _make_subnode("baz", null), + ] + typ = Mapping() + + result = typ.deserialize(node, cstruct) + + assert result == exp_appstruct + + +@pytest.mark.parametrize( + "unknown, appstruct, exp_or_raises", + [ + ("ignore", "null", {}), + ("ignore", {}, {}), + ("ignore", {"foo": "Foo"}, {}), + ("preserve", {"foo": "Foo"}, {"foo": "Foo"}), + ("raise", {"foo": "Foo"}, "UnsupportedFields"), + ], +) +def test_Mapping_serialize_wo_children(unknown, appstruct, exp_or_raises): + from colander import Mapping + from colander import UnsupportedFields + from colander import null + + if appstruct == "null": + appstruct = null + + node = mock.Mock(spec_set=["children"]) + node.children = [] + typ = Mapping(unknown) + + if exp_or_raises == "UnsupportedFields": + with pytest.raises(UnsupportedFields): + typ.serialize(node, appstruct) + else: + result = typ.serialize(node, appstruct) + + assert result == exp_or_raises + + +def test_Mapping_serialize_w_child_wo_default(): + from colander import Invalid + from colander import Mapping + from colander import null + + node = mock.Mock(spec_set=["children", "typ"]) + sub = mock.Mock(spec_set=["name", "serialize", "default"]) + + sub.name = "foo" + sub.default = None + + def _assert_not_null(value): + if value is null: + raise Invalid(sub) + + sub.serialize = _assert_not_null + + node.children = [sub] + typ = Mapping() + appstruct = {} + + with pytest.raises(Invalid): + typ.serialize(node, appstruct) + + +@pytest.mark.parametrize( + "appstruct, exp_cstruct", + [ + ("null", {"foo": "null", "baz": "null"}), + ({}, {"foo": "Foo", "baz": "null"}), + ({"foo": "Foo"}, {"foo": "Foo", "baz": "null"}), + ( + {"foo": "Foo", "bar": "Bar"}, + {"foo": "Foo", "bar": "Bar", "baz": "null"}, + ), + ( + {"foo": "Foo", "baz": "Baz"}, + {"foo": "Foo", "baz": "Baz"}, + ), + ( + {"foo": "Foo", "bar": "Bar", "baz": "Baz"}, + {"foo": "Foo", "bar": "Bar", "baz": "Baz"}, + ), + ], +) +def test_Mapping_serialize_w_children(appstruct, exp_cstruct): + from colander import Mapping + from colander import drop + from colander import null + + if appstruct == "null": + appstruct = null + + if exp_cstruct == "null": + exp_cstruct = null + else: + for key, value in list(exp_cstruct.items()): + if value == "null": + exp_cstruct[key] = null + + def _make_subnode(name, default): + sub = mock.Mock(spec_set=["name", "default", "serialize"]) + sub.name = name + sub.default = default + + if exp_cstruct is not null and name in exp_cstruct: + sub.serialize.return_value = exp_cstruct[name] + elif default is null: + sub.serialize.return_value = null + else: + sub.serialize.side_effect = AssertionError + + return sub + + node = mock.Mock(spec_set=["children"]) + node.children = [ + _make_subnode("foo", None), + _make_subnode("bar", drop), + _make_subnode("baz", null), + ] + typ = Mapping() + + result = typ.serialize(node, appstruct) + + assert result == exp_cstruct + + +@pytest.mark.parametrize( + "prefix, listitem", + [ + (None, None), + (None, False), + (None, True), + ("foo", None), + ("foo", False), + ("foo", True), + ], +) +def test_Mapping_flatten_wo_children(prefix, listitem): + from colander import Mapping + + node = mock.Mock(spec_set=["name", "children"]) + node.name = NODE_NAME + node.children = [] + appstruct = mock.Mock(spec_set=()) + typ = Mapping() + + kw = {} + + if prefix is not None: + kw["prefix"] = prefix + + if listitem is not None: + kw["listitem"] = listitem + + result = typ.flatten(node, appstruct, **kw) + + assert result == {} + + +@pytest.mark.parametrize( + "prefix, listitem, expected", + [ + (None, None, {"test_node.foo": "Foo", "test_node.bar": "null"}), + (None, False, {"test_node.foo": "Foo", "test_node.bar": "null"}), + (None, True, {"foo": "Foo", "bar": "null"}), + ("foo", None, {"footest_node.foo": "Foo", "footest_node.bar": "null"}), + ( + "foo", + False, + {"footest_node.foo": "Foo", "footest_node.bar": "null"}, + ), + ("foo", True, {"foofoo": "Foo", "foobar": "null"}), + ], +) +def test_Mapping_flatten_w_children(prefix, listitem, expected): + from colander import Mapping + from colander import SchemaType + from colander import null + + node = mock.Mock(spec_set=["name", "children"]) + node.name = NODE_NAME + + subnode_1 = mock.Mock(spec_set=["name", "typ"]) + subnode_1.name = "foo" + subnode_1.typ = SchemaType() + + subnode_2 = mock.Mock(spec_set=["name", "typ"]) + subnode_2.name = "bar" + subnode_2.typ = SchemaType() + + node.children = [subnode_1, subnode_2] + appstruct = {"foo": "Foo"} + typ = Mapping() + + kw = {} + + if prefix is not None: + kw["prefix"] = prefix + + if listitem is not None: + kw["listitem"] = listitem + + for key, value in list(expected.items()): + if value == "null": + expected[key] = null + + result = typ.flatten(node, appstruct, **kw) + + assert result == expected + + +def test_Mapping_unflatten(): + from colander import Mapping + + node = mock.Mock(spec_set=()) + paths = [] + fstruct = {} + typ = Mapping() + + with mock.patch("colander._unflatten_mapping") as um: + result = typ.unflatten(node, paths, fstruct) + + assert result is um.return_value + um.assert_called_once_with(node, paths, fstruct) + + +@pytest.mark.parametrize("node_name", ["", "name"]) +def test_Mapping_unflatten_w_node_name(node_name): + from colander import Mapping + + class Node(dict): + name = node_name + + node = Node() + paths = [node_name] + fstruct = {} + typ = Mapping() + + result = typ.unflatten(node, paths, fstruct) + + assert result == {} + + +@pytest.mark.parametrize( + "path, exp_appstruct", + [ + ("foo", {"foo": 123, "child": {"grand": {}}}), + ("child.foo", {"child": {"foo": 123, "grand": {}}}), + ("child.grand.foo", {"child": {"grand": {"foo": 123}}}), + ], +) +def test_Mapping_set_value(path, exp_appstruct): + from colander import Mapping + + class _Node(dict): + def __init__(self): + self.typ = Mapping() + + node = _Node() + child = node["child"] = _Node() + child.typ = Mapping() + grand = child["grand"] = _Node() + grand.typ = Mapping() + + typ = Mapping() + appstruct = {"child": {"grand": {}}} + + result = typ.set_value(node, appstruct, path, 123) + + assert result == exp_appstruct + + +@pytest.mark.parametrize( + "path, expected", + [ + ("foo", 123), + ("child.foo", 234), + ("child.grand.foo", 345), + ], +) +def test_Mapping_get_value(path, expected): + from colander import Mapping + + class _Node(dict): + def __init__(self): + self.typ = Mapping() + + node = _Node() + child = node["child"] = _Node() + child.typ = Mapping() + grand = child["grand"] = _Node() + grand.typ = Mapping() + + typ = Mapping() + appstruct = {"foo": 123, "child": {"foo": 234, "grand": {"foo": 345}}} + + result = typ.get_value(node, appstruct, path) + + assert result == expected + + +@pytest.mark.parametrize("value", [None, MARKER]) +def test_Tuple__validate_not_iterable(value): + from colander import Invalid + from colander import Tuple + + node = mock.Mock(spec_set=()) + typ = Tuple() + + with pytest.raises(Invalid) as exc: + typ._validate(node, value) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not iterable' + assert invalid.msg.mapping["val"] is value + + +@pytest.mark.parametrize("value", [(0, 1, 2), [0, 1, 2]]) +def test_Tuple__validate_wrong_length(value): + from colander import Invalid + from colander import Tuple + + node = mock.Mock(spec_set=["children"]) + node.children = ["a", "b"] + typ = Tuple() + + with pytest.raises(Invalid) as exc: + typ._validate(node, value) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == ( + '"${val}" has an incorrect number of elements ' + '(expected ${exp}, was ${was})' + ) + assert invalid.msg.mapping["val"] is value + assert invalid.msg.mapping["exp"] == 2 + assert invalid.msg.mapping["was"] == len(value) + + +@pytest.mark.parametrize("value", [(0, 1, 2), [0, 1, 2]]) +def test_Tuple__validate_hit(value): + from colander import Tuple + + node = mock.Mock(spec_set=["children"]) + node.children = ["a", "b", "c"] + typ = Tuple() + + result = typ._validate(node, value) + + assert result == list(value) + + +def test_Tuple_cstruct_children_w_null_wo_children(): + from colander import Tuple + from colander import null + + node = mock.Mock(spec_set=["children"]) + node.children = [] + typ = Tuple() + + result = typ.cstruct_children(node, null) + + assert result == [] + + +def test_Tuple_cstruct_children_w_null_w_children(): + from colander import Tuple + from colander import null + + node = mock.Mock(spec_set=["children"]) + subnode = mock.Mock(spec_set=["name", "serialize"]) + subnode.name = "sub" + node.children = [subnode] + typ = Tuple() + + result = typ.cstruct_children(node, null) + + assert result == [subnode.serialize.return_value] + + +def test_Tuple_cstruct_children_w_list_w_fewer_children(): + from colander import Tuple + + node = mock.Mock(spec_set=["children"]) + node.children = ["a"] + typ = Tuple() + + result = typ.cstruct_children(node, [0, 1, 2]) + + assert result == [0] + + +def test_Tuple_cstruct_children_w_dict_w_more_children(): + from colander import Tuple + + node = mock.Mock(spec_set=["children"]) + node.children = [] + subnode_1 = mock.Mock(spec_set=["name", "serialize"]) + subnode_1.name = "foo" + subnode_2 = mock.Mock(spec_set=["name", "serialize"]) + subnode_2.name = "bar" + node.children = [subnode_1, subnode_2] + typ = Tuple() + + result = typ.cstruct_children(node, [0]) + + assert result == [0, subnode_2.serialize.return_value] + + +def test_Tuple_cstruct_children_w_dict_w_same_num_children(): + from colander import Tuple + + node = mock.Mock(spec_set=["children"]) + node.children = [] + subnode_1 = mock.Mock(spec_set=["name", "serialize"]) + subnode_1.name = "foo" + subnode_2 = mock.Mock(spec_set=["name", "serialize"]) + subnode_2.name = "bar" + node.children = [subnode_1, subnode_2] + typ = Tuple() + + result = typ.cstruct_children(node, [0, 1]) + + assert result == [0, 1] + + +@pytest.mark.parametrize( + "appstruct, expected", + [ + ("null", "null"), + ([], ()), + ((), ()), + ], +) +def test_Tuple_serialize_wo_children(appstruct, expected): + from colander import Tuple + from colander import null + + if appstruct == "null": + appstruct = null + + if expected == "null": + expected = null + + node = mock.Mock(spec_set=["children"]) + node.children = [] + typ = Tuple() + + result = typ.serialize(node, appstruct) + + assert result == expected + + +@pytest.mark.parametrize( + "appstruct, expected", + [ + ("null", "null"), + ([0, 1], ("a", "b")), + ((0, 1), ("a", "b")), + ], +) +def test_Tuple_serialize_w_children(appstruct, expected): + from colander import Tuple + from colander import null + + if appstruct == "null": + appstruct = null + + if expected == "null": + expected = null + + node = mock.Mock(spec_set=["children"]) + subnode_1 = mock.Mock(spec_set=["serialize"]) + subnode_1.serialize.return_value = "a" + subnode_2 = mock.Mock(spec_set=["serialize"]) + subnode_2.serialize.return_value = "b" + node.children = [subnode_1, subnode_2] + typ = Tuple() + + result = typ.serialize(node, appstruct) + + assert result == expected + + +def test_Tuple_serialize_w_child_node_raising(): + from colander import Invalid + from colander import Tuple + + appstruct = [0, 1] + node = mock.Mock(spec_set=["children", "typ"]) + subnode_1 = mock.Mock(spec_set=["serialize"]) + subnode_1.serialize.return_value = "a" + subnode_2 = mock.Mock(spec_set=["serialize"]) + subnode_2.serialize.side_effect = Invalid(subnode_2, "testing") + node.children = [subnode_1, subnode_2] + typ = node.typ = Tuple() + + with pytest.raises(Invalid) as exc: + typ.serialize(node, appstruct) + + invalid = exc.value + assert invalid.node is node + assert invalid.msg is None + + assert len(invalid.children) == 1 + sub_inv = exc.value.children[0] + assert sub_inv.node is subnode_2 + assert sub_inv.msg == "testing" + + +@pytest.mark.parametrize( + "cstruct, exp_or_raises", + [ + ("null", "null"), + ((), ()), + ([], ()), + ], +) +def test_Tuple_deserialize_wo_children(cstruct, exp_or_raises): + from colander import Tuple + from colander import null + + if cstruct == "null": + cstruct = null + + if exp_or_raises == "null": + exp_or_raises = null + + node = mock.Mock(spec_set=["children"]) + node.children = [] + typ = Tuple() + + result = typ.deserialize(node, cstruct) + + assert result == exp_or_raises + + +@pytest.mark.parametrize( + "cstruct, expected", + [ + ("null", "null"), + ([0, 1], ("a", "b")), + ((0, 1), ("a", "b")), + ], +) +def test_Tuple_deserialize_w_children(cstruct, expected): + from colander import Tuple + from colander import null + + if cstruct == "null": + cstruct = null + + if expected == "null": + expected = null + + node = mock.Mock(spec_set=["children"]) + subnode_1 = mock.Mock(spec_set=["deserialize"]) + subnode_1.deserialize.return_value = "a" + subnode_2 = mock.Mock(spec_set=["deserialize"]) + subnode_2.deserialize.return_value = "b" + node.children = [subnode_1, subnode_2] + typ = Tuple() + + result = typ.deserialize(node, cstruct) + + assert result == expected + + +def test_Tuple_deserialize_w_child_node_raising(): + from colander import Invalid + from colander import Tuple + + cstruct = [0, 1] + node = mock.Mock(spec_set=["children", "typ"]) + subnode_1 = mock.Mock(spec_set=["deserialize"]) + subnode_1.deserialize.return_value = "a" + subnode_2 = mock.Mock(spec_set=["deserialize"]) + subnode_2.deserialize.side_effect = Invalid(subnode_2, "testing") + node.children = [subnode_1, subnode_2] + typ = node.typ = Tuple() + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert invalid.msg is None + + assert len(invalid.children) == 1 + sub_inv = exc.value.children[0] + assert sub_inv.node is subnode_2 + assert sub_inv.msg == "testing" + + +@pytest.mark.parametrize( + "prefix, listitem", + [ + (None, None), + (None, False), + (None, True), + ("foo", None), + ("foo", False), + ("foo", True), + ], +) +def test_Tuple_flatten_wo_children(prefix, listitem): + from colander import Tuple + + node = mock.Mock(spec_set=["name", "children"]) + node.name = NODE_NAME + node.children = [] + appstruct = mock.Mock(spec_set=()) + typ = Tuple() + + kw = {} + + if prefix is not None: + kw["prefix"] = prefix + + if listitem is not None: + kw["listitem"] = listitem + + result = typ.flatten(node, appstruct, **kw) + + assert result == {} + + +@pytest.mark.parametrize( + "prefix, listitem, expected", + [ + (None, None, {"test_node.foo": 0, "test_node.bar": 1}), + (None, False, {"test_node.foo": 0, "test_node.bar": 1}), + (None, True, {"foo": 0, "bar": 1}), + ("foo", None, {"footest_node.foo": 0, "footest_node.bar": 1}), + ( + "foo", + False, + {"footest_node.foo": 0, "footest_node.bar": 1}, + ), + ("foo", True, {"foofoo": 0, "foobar": 1}), + ], +) +def test_Tuple_flatten_w_children(prefix, listitem, expected): + from colander import SchemaType + from colander import Tuple + from colander import null + + node = mock.Mock(spec_set=["name", "children"]) + node.name = NODE_NAME + + subnode_1 = mock.Mock(spec_set=["name", "typ"]) + subnode_1.name = "foo" + subnode_1.typ = SchemaType() + + subnode_2 = mock.Mock(spec_set=["name", "typ"]) + subnode_2.name = "bar" + subnode_2.typ = SchemaType() + + node.children = [subnode_1, subnode_2] + appstruct = [0, 1] + typ = Tuple() + + kw = {} + + if prefix is not None: + kw["prefix"] = prefix + + if listitem is not None: + kw["listitem"] = listitem + + for key, value in list(expected.items()): + if value == "null": + expected[key] = null + + result = typ.flatten(node, appstruct, **kw) + + assert result == expected + + +def test_Tuple_unflatten_wo_children(): + from colander import Tuple + + node = mock.Mock(spec_set=["children"]) + node.children = [] + paths = [] + fstruct = {} + typ = Tuple() + + with mock.patch("colander._unflatten_mapping") as um: + um.return_value = {} + result = typ.unflatten(node, paths, fstruct) + + assert result == () + um.assert_called_once_with(node, paths, fstruct) + + +def test_Tuple_unflatten_w_children(): + from colander import Tuple + + node = mock.Mock(spec_set=["children"]) + subnode_1 = mock.Mock(spec_set=["name"]) + subnode_1.name = "foo" + subnode_2 = mock.Mock(spec_set=["name"]) + subnode_2.name = "bar" + node.children = [subnode_1, subnode_2] + paths = [] + fstruct = {} + typ = Tuple() + + with mock.patch("colander._unflatten_mapping") as um: + um.return_value = {"foo": 0, "bar": 1} + result = typ.unflatten(node, paths, fstruct) + + assert result == (0, 1) + um.assert_called_once_with(node, paths, fstruct) + + +@pytest.mark.parametrize("node_name", ["", "name"]) +def test_Tuple_unflatten_w_node_name(node_name): + from colander import Tuple + + class Node(dict): + name = node_name + children = () + + node = Node() + paths = [node_name] + fstruct = {} + typ = Tuple() + + result = typ.unflatten(node, paths, fstruct) + + assert result == () + + +@pytest.mark.parametrize( + "path, exp_appstruct", + [ + ("foo", (123, (3, 4))), + ("foo.a", ((123, 2), (3, 4))), + ("foo.b", ((1, 123), (3, 4))), + ("foo.c", "KeyError"), + ("bar", ((1, 2), 123)), + ("bar.c", ((1, 2), (123, 4))), + ("bar.d", ((1, 2), (3, 123))), + ("bar.a", "KeyError"), + ("baz", "KeyError"), + ], +) +def test_Tuple_set_value(path, exp_appstruct): + from colander import SchemaType + from colander import Tuple + + class _Node(dict): + def __init__(self, name, typ): + self.name = name + self.typ = typ + + @property + def children(self): + return self.values() + + node = _Node("root", Tuple()) + node["foo"] = _Node("foo", Tuple()) + node["foo"]["a"] = _Node("a", SchemaType()) + node["foo"]["b"] = _Node("b", SchemaType()) + node["bar"] = _Node("bar", Tuple()) + node["bar"]["c"] = _Node("c", SchemaType()) + node["bar"]["d"] = _Node("d", SchemaType()) + + typ = Tuple() + appstruct = ((1, 2), (3, 4)) + + if exp_appstruct == "KeyError": + + with pytest.raises(KeyError): + typ.set_value(node, appstruct, path, 123) + + else: + result = typ.set_value(node, appstruct, path, 123) + assert result == exp_appstruct + + +@pytest.mark.parametrize( + "path, exp_value", + [ + ("foo", (1, 2)), + ("foo.a", 1), + ("foo.b", 2), + ("foo.c", "KeyError"), + ("bar", (3, 4)), + ("bar.c", 3), + ("bar.d", 4), + ("bar.a", "KeyError"), + ("baz", "KeyError"), + ], +) +def test_Tuple_get_value(path, exp_value): + from colander import SchemaType + from colander import Tuple + + class _Node(dict): + def __init__(self, name, typ): + self.name = name + self.typ = typ + + @property + def children(self): + return self.values() + + node = _Node("root", Tuple()) + node["foo"] = _Node("foo", Tuple()) + node["foo"]["a"] = _Node("a", SchemaType()) + node["foo"]["b"] = _Node("b", SchemaType()) + node["bar"] = _Node("bar", Tuple()) + node["bar"]["c"] = _Node("c", SchemaType()) + node["bar"]["d"] = _Node("d", SchemaType()) + + typ = Tuple() + appstruct = ((1, 2), (3, 4)) + + if exp_value == "KeyError": + + with pytest.raises(KeyError): + typ.get_value(node, appstruct, path) + + else: + result = typ.get_value(node, appstruct, path) + assert result == exp_value + + +@pytest.mark.parametrize( + "appstruct", + [ + "null", + [], + (), + [0, 1, 2], + (3, 4, 5), + ], +) +def test_Set_serialize(appstruct): + from colander import Set + from colander import null + + if appstruct == "null": + appstruct = null + + node = mock.Mock(spec_set=()) + typ = Set() + + result = typ.serialize(node, appstruct) + + assert result == appstruct + + +@pytest.mark.parametrize( + "cstruct, expected", + [ + ("null", "null"), + ([], set()), + ((), set()), + ([0, 1, 2], set([0, 1, 2])), + ((3, 4, 5), set([3, 4, 5])), + ("foo", "Invalid"), + (MARKER, "Invalid"), + ], +) +def test_Set_deserialize(cstruct, expected): + from colander import Invalid + from colander import Set + from colander import null + + if cstruct == "null": + cstruct = null + + if expected == "null": + expected = null + + node = mock.Mock(spec_set=()) + typ = Set() + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '${cstruct} is not iterable' + assert invalid.msg.mapping["cstruct"] is cstruct + else: + result = typ.deserialize(node, cstruct) + assert result == expected + + +@pytest.mark.parametrize( + "appstruct", + [ + "null", + [], + (), + [0, 1, 2], + (3, 4, 5), + ], +) +def test_List_serialize(appstruct): + from colander import List + from colander import null + + if appstruct == "null": + appstruct = null + + node = mock.Mock(spec_set=()) + typ = List() + + result = typ.serialize(node, appstruct) + + assert result == appstruct + + +@pytest.mark.parametrize( + "cstruct, expected", + [ + ("null", "null"), + ([], []), + ((), []), + ([0, 1, 2], [0, 1, 2]), + ((3, 4, 5), [3, 4, 5]), + ("foo", "Invalid"), + (MARKER, "Invalid"), + ], +) +def test_List_deserialize(cstruct, expected): + from colander import Invalid + from colander import List + from colander import null + + if cstruct == "null": + cstruct = null + + if expected == "null": + expected = null + + node = mock.Mock(spec_set=()) + typ = List() + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '${cstruct} is not iterable' + assert invalid.msg.mapping["cstruct"] is cstruct + else: + result = typ.deserialize(node, cstruct) + assert result == expected + + +@pytest.mark.parametrize("accept_scalar", [None, False, True]) +def test_Sequence_ctor(accept_scalar): + from colander import Sequence + + if accept_scalar is None: + typ = Sequence() + else: + typ = Sequence(accept_scalar=accept_scalar) + + assert typ.accept_scalar == bool(accept_scalar) + + +@pytest.mark.parametrize( + "accept_scalar, value, expected", + [ + (False, (), []), + (False, (1, 2), [1, 2]), + (False, set(), []), + (False, set([1, 2]), [1, 2]), + (False, [], []), + (False, [1, 2], [1, 2]), + (False, b"foo", [ord(b"f"), ord(b"o"), ord(b"o")]), + (False, MARKER, "Invalid"), + (False, "foo", "Invalid"), + (False, {"foo": "Foo"}, "Invalid"), + (True, (), []), + (True, (1, 2), [1, 2]), + (True, set(), []), + (True, set([1, 2]), [1, 2]), + (True, [], []), + (True, [1, 2], [1, 2]), + (True, b"foo", [ord(b"f"), ord(b"o"), ord(b"o")]), + (True, MARKER, [MARKER]), + (True, "foo", ["foo"]), + (True, {"foo": "Foo"}, [{"foo": "Foo"}]), + ], +) +def test_Sequence__validate(accept_scalar, value, expected): + from colander import Invalid + from colander import Sequence + + node = mock.Mock(spec_set=()) + typ = Sequence() + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ._validate(node, value, accept_scalar) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not iterable' + assert invalid.msg.mapping["val"] is value + + else: + assert typ._validate(node, value, accept_scalar) == expected + + +@pytest.mark.parametrize( + "cstruct, expected_list", + [ + ("null", []), + ((), []), + ((1, 2), [1, 2]), + (set(), []), + (set([1, 2]), [1, 2]), + ([], []), + ([1, 2], [1, 2]), + (b"foo", [ord(b"f"), ord(b"o"), ord(b"o")]), + ], +) +def test_Sequence_cstruct_children(cstruct, expected_list): + from colander import Sequence + from colander import SequenceItems + from colander import null + + if cstruct == "null": + cstruct = null + + node = mock.Mock(spec_set=()) + typ = Sequence() + + result = typ.cstruct_children(node, cstruct) + + assert isinstance(result, SequenceItems) + assert result == expected_list + + +@pytest.mark.parametrize( + "accept_scalar, default, appstruct, exp_cstruct", + [ + (None, None, "null", "null"), + (None, None, [], []), + (None, None, [0, 1], ["0", "1"]), + (None, None, ["drop"], []), + (None, None, ["null"], ["null"]), + (None, "drop", ["null"], []), + (None, "drop", [0, "null", 1], ["0", "1"]), + (True, None, 0, ["0"]), + ], +) +def test_Sequence_serialize( + accept_scalar, + default, + appstruct, + exp_cstruct, +): + from colander import Integer + from colander import Sequence + from colander import drop + from colander import null + + _converters = { + "drop": drop, + "null": null, + } + + if default == "drop": + default = drop + + if appstruct == "null": + appstruct = null + else: + appstruct = [_converters.get(value, value) for value in exp_cstruct] + + if exp_cstruct == "null": + exp_cstruct = null + else: + exp_cstruct = [_converters.get(value, value) for value in exp_cstruct] + + int_type = Integer() + + def _make_subnode(default): + sub = mock.Mock(spec_set=["typ", "default", "serialize"]) + sub.typ = Integer + sub.default = default + sub.serialize = lambda appstruct: int_type.serialize(sub, appstruct) + return sub + + node = mock.Mock(spec_set=["children"]) + node.children = [ + _make_subnode(default), + ] + typ = Sequence() + + if accept_scalar is not None: + result = typ.serialize(node, appstruct, accept_scalar=accept_scalar) + else: + result = typ.serialize(node, appstruct) + + assert result == exp_cstruct + + +@pytest.mark.parametrize( + "accept_scalar, missing, cstruct, exp_appstruct", + [ + (None, None, "null", "null"), + (None, None, [], []), + (None, None, ["0", "1"], [0, 1]), + (None, None, ["drop"], []), + (None, None, ["subdrop"], []), + (None, None, ["null"], ["null"]), + (None, "drop", ["null"], []), + (None, "drop", ["0", "null", "1"], [0, 1]), + (True, None, "0", [0]), + ], +) +def test_Sequence_deserialize( + accept_scalar, + missing, + cstruct, + exp_appstruct, +): + from colander import Integer + from colander import Sequence + from colander import drop + from colander import null + + _converters = { + "drop": drop, + "null": null, + } + + if missing == "drop": + missing = drop + + if cstruct == "null": + cstruct = null + else: + cstruct = [_converters.get(value, value) for value in cstruct] + + if exp_appstruct == "null": + exp_appstruct = null + else: + exp_appstruct = [ + _converters.get(value, value) for value in exp_appstruct + ] + + int_type = Integer() + + def _subnode_deserialize(cstruct): + if cstruct == "subdrop": + return drop + return int_type.deserialize(None, cstruct) + + def _make_subnode(missing): + sub = mock.Mock(spec_set=["typ", "missing", "deserialize"]) + sub.typ = Integer + sub.missing = missing + sub.deserialize = _subnode_deserialize + return sub + + node = mock.Mock(spec_set=["children"]) + node.children = [ + _make_subnode(missing), + ] + typ = Sequence() + + if accept_scalar is not None: + result = typ.deserialize(node, cstruct, accept_scalar=accept_scalar) + else: + result = typ.deserialize(node, cstruct) + + assert result == exp_appstruct + + +@pytest.mark.parametrize( + "prefix, listitem, expected", + [ + (None, None, {"test.0": "bar", "test.1": "null"}), + (None, False, {"test.0": "bar", "test.1": "null"}), + (None, True, {"0": "bar", "1": "null"}), + ("foo", None, {"footest.0": "bar", "footest.1": "null"}), + ("foo", False, {"footest.0": "bar", "footest.1": "null"}), + ("foo", True, {"foo0": "bar", "foo1": "null"}), + ], +) +def test_Sequence_flatten(prefix, listitem, expected): + from colander import SchemaType + from colander import Sequence + from colander import null + + node = mock.Mock(spec_set=["name", "children"]) + node.name = "test" + subnode_1 = mock.Mock(spec_set=["name", "typ"]) + subnode_1.name = "foo" + subnode_1.typ = SchemaType() + node.children = [subnode_1] + + appstruct = ["bar", null] + typ = Sequence() + + kw = {} + + if prefix is not None: + kw["prefix"] = prefix + + if listitem is not None: + kw["listitem"] = listitem + + for key, value in list(expected.items()): + if value == "null": + expected[key] = null + + result = typ.flatten(node, appstruct, **kw) + + assert result == expected + + +def test_Sequence_unflatten(): + from colander import Sequence + + node = mock.Mock(spec_set=["children"]) + subnode_1 = mock.Mock(spec_set=["name"]) + subnode_1.name = "foo" + node.children = [subnode_1] + + paths = [] + fstruct = {} + typ = Sequence() + + with mock.patch("colander._unflatten_mapping") as um: + um.return_value = {"0": 0, "1": 1} + result = typ.unflatten(node, paths, fstruct) + + assert result == [0, 1] + um.assert_called_once_with(node, paths, fstruct, mock.ANY, mock.ANY) + _, _, _, g_c, r_s = um.call_args_list[0].args + + assert g_c("foo") is subnode_1 + assert r_s("waaa.blah") == "foo.blah" + assert r_s("waaa") == "foo" + + +@pytest.mark.parametrize("node_name", ["", "name"]) +def test_Sequence_unflatten_w_node_name(node_name): + from colander import Sequence + + child = mock.Mock(spec_set=["name"]) + child.name = "child" + + class Node(dict): + name = node_name + children = [child] + + node = Node() + paths = [node_name] + fstruct = {} + typ = Sequence() + + result = typ.unflatten(node, paths, fstruct) + + assert result == [] + + +@pytest.mark.parametrize( + "path, exp_appstruct", + [ + ("0", [123, [3, 4]]), + ("0.0", [[123, 2], [3, 4]]), + ("0.1", [[1, 123], [3, 4]]), + ("0.2", "IndexError"), + ("1", [[1, 2], 123]), + ("1.0", [[1, 2], [123, 4]]), + ("1.1", [[1, 2], [3, 123]]), + ("1.2", "IndexError"), + ("2", "IndexError"), + ], +) +def test_Sequence_set_value(path, exp_appstruct): + from colander import Integer + from colander import Sequence + + class _Node(dict): + def __init__(self, name, typ): + self.name = name + self.typ = typ + + @property + def children(self): + return list(self.values()) + + node = _Node("root", Sequence()) + node["0"] = _Node("foo", Sequence()) + node["0"]["0"] = _Node("a", Integer()) + node["1"] = _Node("bar", Sequence()) + node["1"]["0"] = _Node("c", Integer()) + + typ = Sequence() + appstruct = [[1, 2], [3, 4]] + + if exp_appstruct == "IndexError": + + with pytest.raises(IndexError): + typ.set_value(node, appstruct, path, 123) + + else: + result = typ.set_value(node, appstruct, path, 123) + assert result == exp_appstruct + + +@pytest.mark.parametrize( + "path, exp_value", + [ + ("0", [1, 2]), + ("0.0", 1), + ("0.1", 2), + ("0.2", "IndexError"), + ("1", [3, 4]), + ("1.0", 3), + ("1.1", 4), + ("1.2", "IndexError"), + ("2", "IndexError"), + ], +) +def test_Sequence_get_value(path, exp_value): + from colander import Integer + from colander import Sequence + + class _Node(dict): + def __init__(self, name, typ): + self.name = name + self.typ = typ + + @property + def children(self): + return list(self.values()) + + node = _Node("root", Sequence()) + node["0"] = _Node("0", Sequence()) + node["0"]["0"] = _Node("0", Integer()) + node["1"] = _Node("1", Sequence()) + node["1"]["0"] = _Node("c", Integer()) + + typ = Sequence() + appstruct = [[1, 2], [3, 4]] + + if exp_value == "IndexError": + + with pytest.raises(IndexError): + typ.get_value(node, appstruct, path) + + else: + result = typ.get_value(node, appstruct, path) + assert result == exp_value + + +@pytest.mark.parametrize("allow_empty", [None, False, True]) +@pytest.mark.parametrize("encoding", [None, "utf8"]) +def test_String_ctor(encoding, allow_empty): + from colander import String + + kw = {} + + if encoding is not None: + kw["encoding"] = encoding + + if allow_empty is not None: + kw["allow_empty"] = allow_empty + + typ = String(**kw) + + assert typ.encoding == encoding + assert typ.allow_empty == bool(allow_empty) + + +class Uncooperative: + def __str__(self): + raise ValueError('I wont cooperate') + + +@pytest.mark.parametrize( + "encoding, appstruct, expected", + [ + (None, "null", "null"), + (None, "", ""), + (None, "abc", "abc"), + (None, 0, "0"), + (None, MARKER, str(MARKER)), + (None, Uncooperative(), "Invalid"), + ("utf-8", UNI, UTF8), + ("utf-8", 123, b"123"), + ("utf-16", UNI, UTF16), + ], +) +def test_String_serialize(encoding, appstruct, expected): + from colander import Invalid + from colander import String + from colander import null + + node = mock.Mock(spec_set=()) + typ = String(encoding) + + if appstruct == "null": + appstruct = null + + if expected == "null": + expected = null + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.serialize(node, appstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '${val} cannot be serialized: ${err}' + assert invalid.msg.mapping["val"] is appstruct + + else: + result = typ.serialize(node, appstruct) + assert result == expected + + +@pytest.mark.parametrize( + "encoding, allow_empty, cstruct, expected", + [ + (None, False, "null", "null"), + (None, False, "", "null"), + (None, True, "", ""), + (None, False, "abc", "abc"), + (None, False, MARKER, "Invalid"), + ("ascii", False, b"123", "123"), + ("utf-8", False, b"123", "123"), + ("utf-8", False, UTF8, UNI), + ("utf-16", False, UTF16, UNI), + ], +) +def test_String_deserialize(encoding, allow_empty, cstruct, expected): + from colander import Invalid + from colander import String + from colander import null + + node = mock.Mock(spec_set=()) + kw = {} + + if encoding is not None: + kw["encoding"] = encoding + + if cstruct == "null": + cstruct = null + + if expected == "null": + expected = null + + if allow_empty is not None: + kw["allow_empty"] = allow_empty + + typ = String(**kw) + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '${val} is not a string: ${err}' + assert invalid.msg.mapping["val"] is cstruct + + else: + result = typ.deserialize(node, cstruct) + assert result == expected + + +@pytest.mark.parametrize( + "appstruct, expected", + [ + ("null", "null"), + (0, "0"), + (-1, "-1"), + (3, "Invalid"), + (2.71828, "2"), + ], +) +def test_Number_serialize(appstruct, expected): + from colander import Invalid + from colander import Number + from colander import null + + def _anything_but_3(val): + assert val != 3 + return int(val) + + node = mock.Mock(spec_set=()) + typ = Number() + typ.num = _anything_but_3 + + if appstruct == "null": + appstruct = null + + if expected == "null": + expected = null + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.serialize(node, appstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a number' + assert invalid.msg.mapping["val"] is appstruct + + else: + result = typ.serialize(node, appstruct) + assert result == expected + + +@pytest.mark.parametrize( + "cstruct, expected", + [ + ("null", "null"), + ("", "null"), + ("0", 0), + ("-1", -1), + ("2", 2), + ("3", "Invalid"), + ], +) +def test_Number_deserialize(cstruct, expected): + from colander import Invalid + from colander import Number + from colander import null + + def _anything_but_3(val): + assert val != "3" + return int(val) + + node = mock.Mock(spec_set=()) + typ = Number() + typ.num = _anything_but_3 + + if cstruct == "null": + cstruct = null + + if expected == "null": + expected = null + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a number' + assert invalid.msg.mapping["val"] is cstruct + + else: + result = typ.deserialize(node, cstruct) + assert result == expected + + +@pytest.mark.parametrize( + "value, raises", + [ + (0, False), + (0.0, False), + (-1, False), + (-1.0, False), + (2.71828, True), + ], +) +def test_Integer_ctor_strict(value, raises): + from colander import Integer + + typ = Integer(strict=True) + + if raises: + + with pytest.raises(ValueError) as exc: + typ.num(value) + + verr = exc.value + assert verr.args[0] == "Value is not an Integer" + else: + typ.num(value) # no raise + + +@pytest.mark.parametrize( + "appstruct, expected", + [ + ("null", "null"), + (0, "0"), + (-1, "-1"), + (2.71828, "2"), + ], +) +def test_Integer_serialize(appstruct, expected): + from colander import Integer + from colander import Invalid + from colander import null + + node = mock.Mock(spec_set=()) + typ = Integer() + + if appstruct == "null": + appstruct = null + + if expected == "null": + expected = null + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.serialize(node, appstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a number' + assert invalid.msg.mapping["val"] is appstruct + + else: + result = typ.serialize(node, appstruct) + assert result == expected + + +@pytest.mark.parametrize( + "cstruct, expected", + [ + ("null", "null"), + ("", "null"), + ("0", 0), + ("-1", -1), + ("2", 2), + (MARKER, "Invalid"), + ], +) +def test_Integer_deserialize(cstruct, expected): + from colander import Integer + from colander import Invalid + from colander import null + + node = mock.Mock(spec_set=()) + typ = Integer() + + if cstruct == "null": + cstruct = null + + if expected == "null": + expected = null + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a number' + assert invalid.msg.mapping["val"] is cstruct + + else: + result = typ.deserialize(node, cstruct) + assert result == expected + + +@pytest.mark.parametrize( + "appstruct, expected", + [ + ("null", "null"), + (0, "0.0"), + (-1, "-1.0"), + (2.71828, "2.71828"), + (MARKER, "Invalid"), + ], +) +def test_Float_serialize(appstruct, expected): + from colander import Float + from colander import Invalid + from colander import null + + node = mock.Mock(spec_set=()) + typ = Float() + + if appstruct == "null": + appstruct = null + + if expected == "null": + expected = null + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.serialize(node, appstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a number' + assert invalid.msg.mapping["val"] is appstruct + + else: + result = typ.serialize(node, appstruct) + assert result == expected + + +@pytest.mark.parametrize( + "cstruct, expected", + [ + ("null", "null"), + ("", "null"), + ("0", 0.0), + ("-1", -1), + ("2.71828", 2.71828), + ], +) +def test_Float_deserialize(cstruct, expected): + from colander import Float + from colander import Invalid + from colander import null + + node = mock.Mock(spec_set=()) + typ = Float() + + if cstruct == "null": + cstruct = null + + if expected == "null": + expected = null + + if expected == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a number' + assert invalid.msg.mapping["val"] is cstruct + + else: + result = typ.deserialize(node, cstruct) + assert result == expected + + +@pytest.mark.parametrize( + "quant, rounding, normalize", + [ + (None, None, None), + ("1.000", None, None), + ("1.000", "ROUND_UP", False), + ("1.000", "ROUND_DOWN", True), + ], +) +def test_Decimal_ctor(quant, rounding, normalize): + import decimal + + from colander import Decimal + + kw = {} + + if quant is not None: + kw["quant"] = quant + + if rounding is not None: + kw["rounding"] = getattr(decimal, rounding) + + if normalize is not None: + kw["normalize"] = normalize + + typ = Decimal(**kw) + + if quant is None: + assert typ.quant is None + else: + assert typ.quant == decimal.Decimal(quant) + + if rounding is None: + assert typ.rounding is None + else: + assert typ.rounding == kw["rounding"] + + assert typ.normalize == bool(normalize) + + +@pytest.mark.parametrize( + "value, quant, rounding, normalize, expected", + [ + ("pi", None, None, None, "value"), + ("pi", "1.00", None, None, "3.14"), + ("pi", "1.00", "ROUND_UP", None, "3.15"), + ("1.10", None, None, True, "1.1"), + ], +) +def test_Decimal_num(value, quant, rounding, normalize, expected): + import decimal + import math + + from colander import Decimal + + if value == "pi": + value = math.pi + + if expected == "value": + expected = decimal.Decimal(str(value)) + else: + expected = decimal.Decimal(expected) + + kw = {} + + if quant is not None: + kw["quant"] = quant + + if rounding is not None: + kw["rounding"] = rounding + + if normalize is not None: + kw["normalize"] = normalize + + typ = Decimal(**kw) + + result = typ.num(value) + + assert result == expected + + +def test_Money_ctor(): + import decimal + + from colander import Money + + typ = Money() + + assert typ.quant == decimal.Decimal("0.01") + assert typ.rounding == decimal.ROUND_UP + + +@pytest.mark.parametrize( + "false_chx, true_chx, false_val, true_val", + [ + (None, None, None, None), + (("F", "N"), ("T", "Y"), "False", "True"), + ], +) +def test_Boolean_ctor(false_chx, true_chx, false_val, true_val): + from colander import Boolean + + kw = {} + + if false_chx is not None: + exp_false_chx = kw["false_choices"] = false_chx + else: + exp_false_chx = ('false', '0') + + if true_chx is not None: + exp_true_chx = kw["true_choices"] = true_chx + else: + exp_true_chx = () + + if false_val is not None: + exp_false_val = kw["false_val"] = false_val + else: + exp_false_val = "false" + + if true_val is not None: + exp_true_val = kw["true_val"] = true_val + else: + exp_true_val = "true" + + typ = Boolean(**kw) + + assert typ.false_choices == exp_false_chx + assert typ.false_val == exp_false_val + assert typ.true_choices == exp_true_chx + assert typ.true_val == exp_true_val + + +@pytest.mark.parametrize( + "appstruct, false_val, true_val, exp_cstruct", + [ + ("null", None, None, "null"), + (False, None, None, "false"), + (None, None, None, "false"), + (0, "nope", None, "nope"), + (True, None, None, "true"), + (1, None, "yep", "yep"), + ], +) +def test_Boolean_serialize(appstruct, false_val, true_val, exp_cstruct): + from colander import Boolean + from colander import null + + kw = {} + + if false_val is not None: + kw["false_val"] = false_val + + if true_val is not None: + kw["true_val"] = true_val + + if appstruct == "null": + appstruct = null + + if exp_cstruct == "null": + exp_cstruct = null + + typ = Boolean(**kw) + node = mock.Mock(spec_set=()) + + result = typ.serialize(node, appstruct) + assert result == exp_cstruct + + +@pytest.mark.parametrize( + "cstruct, false_chx, true_chx, exp_appstruct", + [ + ("null", None, None, "null"), + (False, None, None, False), + ("false", None, None, False), + (0, None, None, False), + ("0", None, None, False), + ("nope", ("0", "nope"), None, False), + (True, None, None, True), + ("1", None, None, True), + ("yep", None, None, True), + ("1", None, ("1", "yep"), True), + ("yep", None, ("1", "yep"), True), + ("yep", None, ("1", "true"), "Invalid"), + (Uncooperative(), None, None, "Invalid"), + ], +) +def test_Boolean_deserialize(cstruct, false_chx, true_chx, exp_appstruct): + from colander import Boolean + from colander import Invalid + from colander import null + + kw = {} + + if false_chx is not None: + kw["false_choices"] = false_chx + + if true_chx is not None: + kw["true_choices"] = true_chx + + if cstruct == "null": + cstruct = null + + if exp_appstruct == "null": + exp_appstruct = null + + typ = Boolean(**kw) + node = mock.Mock(spec_set=()) + + if exp_appstruct == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + + assert invalid.msg.mapping["val"] is cstruct + + if len(invalid.msg.mapping) == 1: + assert invalid.msg.default == '${val} is not a string' + else: + assert invalid.msg.default == ( + '"${val}" is neither in (${false_choices}) ' + 'nor in (${true_choices})' + ) + + else: + result = typ.deserialize(node, cstruct) + assert result == exp_appstruct + + +@pytest.mark.parametrize("package", [None, pytest]) +def test_GlobalObject_ctor(package): + from colander import GlobalObject + + typ = GlobalObject(package) + + assert typ.package is package + + +@pytest.mark.parametrize( + "appstruct, expected", + [ + ("null", "null"), + (MARKER, "Unnamed"), + ("tests", "tests"), + ("relative", "tests.relative"), + ("ImportableClass", "tests.relative.ImportableClass"), + ("importable_func", "tests.relative.importable_func"), + ], +) +def test_GlobalObject_serialize(appstruct, expected): + from colander import GlobalObject + from colander import Invalid + from colander import null + import tests + from tests import relative + + if appstruct == "null": + appstruct = null + elif appstruct == "tests": + appstruct = tests + elif appstruct == "relative": + appstruct = relative + elif appstruct == "ImportableClass": + appstruct = relative.ImportableClass + elif appstruct == "importable_func": + appstruct = relative.importable_func + + if expected == "null": + expected = null + + typ = GlobalObject(None) + node = mock.Mock(spec_set=()) + + if expected == "Unnamed": + + with pytest.raises(Invalid) as exc: + typ.serialize(node, appstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" has no __name__' + assert invalid.msg.mapping["val"] == appstruct + + else: + result = typ.serialize(node, appstruct) + assert result == expected + + +@pytest.mark.parametrize( + "package, cstruct, expected", + [ + (None, "null", "null"), + (None, MARKER, "Nonstring"), + (None, "tests.nonesuch", "ImportError"), + (None, "tests:nonesuch", "ImportError"), + (None, ".", "Irresolvable"), + (None, ":", "Irresolvable"), + (None, ".relative.ImportableClass", "Irresolvable"), + (None, ".relative:ImportableClass", "Irresolvable"), + (None, "pytest.skip", pytest.skip), + (None, "pytest:skip", pytest.skip), + (None, "tests.relative.ImportableClass", "Importable"), + (None, "tests.relative:ImportableClass", "Importable"), + ("tests", "pytest.skip", pytest.skip), + ("tests", "pytest:skip", pytest.skip), + ("tests", ".", "tests"), + ("tests", ":", "tests"), + ("tests", ".relative", "relative"), + ("tests", ":relative", "relative"), + ("tests", "..tests.relative", "relative"), + ("tests", ".relative.ImportableClass", "Importable"), + ("tests", "..tests.relative.ImportableClass", "Importable"), + ("tests", ".relative:ImportableClass", "Importable"), + ("relative", ".", "relative"), + ("relative", ":", "relative"), + ("relative", ".ImportableClass", "Importable"), + ("relative", "..relative.ImportableClass", "Importable"), + ("relative", "...tests.relative.ImportableClass", "Importable"), + ("relative", ":ImportableClass", "Importable"), + ], +) +def test_GlobalObject_deserialize(package, cstruct, expected): + from colander import GlobalObject + from colander import Invalid + from colander import null + import tests + from tests import relative + + _GLOBAL_MSG_TEMPLATES = { + "Nonstring": '"${val}" is not a string', + "ImportError": 'The dotted name "${name}" cannot be imported', + "Irresolvable": ( + 'relative name "${val}" irresolveable without package' + ), + } + + if package == "tests": + package = tests + elif package == "relative": + package = relative + + if cstruct == "null": + cstruct = null + + if expected == "null": + expected = null + elif expected == "tests": + expected = tests + elif expected == "relative": + expected = relative + elif expected == "Importable": + expected = relative.ImportableClass + + typ = GlobalObject(package) + node = mock.Mock(spec_set=()) + + if expected in ("Nonstring", "ImportError", "Irresolvable"): + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + + assert invalid.msg.default == _GLOBAL_MSG_TEMPLATES[expected] + + if expected == "ImportError": + assert invalid.msg.mapping["name"] is cstruct + else: + assert invalid.msg.mapping["val"] is cstruct + + else: + result = typ.deserialize(node, cstruct) + assert result is expected + + +@pytest.mark.parametrize("dtz", [None, "GMT_4"]) +@pytest.mark.parametrize("fmt", [None, "%c"]) +def test_DateTime_ctor(dtz, fmt): + import iso8601 + + from colander import DateTime + + GMT_4 = iso8601.FixedOffset(-4, 0, "GMT-4") + + kw = {} + + if dtz == "GMT_4": + dtz = GMT_4 + + if dtz is not None: + exp_dtz = kw["default_tzinfo"] = dtz + else: + exp_dtz = iso8601.UTC + + if fmt is not None: + kw["format"] = fmt + + typ = DateTime(**kw) + + assert typ.default_tzinfo == exp_dtz + assert typ.format == fmt + + +@pytest.mark.parametrize( + "dtz, fmt, appstruct, exp_cstruct", + [ + (None, None, "null", "null"), + (None, None, None, "null"), + (None, None, MARKER, "Invalid"), + # non-naive datetime + (None, None, "now_utc", "now_utc_iso"), + (None, "%c", "now_utc", "now_utc_locale"), + ("GMT_4", None, "now_utc", "now_utc_iso"), + ("GMT_4", "%c", "now_utc", "now_utc_locale"), + (None, None, "now_gmt_4", "now_gmt_4_iso"), + (None, "%c", "now_gmt_4", "now_gmt_4_locale"), + ("GMT_4", None, "now_gmt_4", "now_gmt_4_iso"), + ("GMT_4", "%c", "now_gmt_4", "now_gmt_4_locale"), + # naive datetime + (None, None, "now_naive", "now_utc_iso"), + (None, "%c", "now_naive", "now_utc_locale"), + ("GMT_4", None, "now_naive", "now_gmt_4_iso"), + ("GMT_4", "%c", "now_naive", "now_gmt_4_locale"), + # date + (None, None, "today", "today_mdn_utc_iso"), + (None, "%c", "today", "today_mdn_utc_locale"), + ("GMT_4", None, "today", "today_mdn_gmt_4_iso"), + ("GMT_4", "%c", "today", "today_mdn_gmt_4_locale"), + ], +) +def test_DateTime_serialize(dtz, fmt, appstruct, exp_cstruct): + import datetime + import iso8601 + + from colander import DateTime + from colander import Invalid + from colander import null + + GMT_4 = iso8601.FixedOffset(-4, 0, "GMT-4") + + today = datetime.date.today() + midnight_utc = datetime.time(0, 0, 0, 0, tzinfo=datetime.timezone.utc) + midnight_gmt_4 = datetime.time(0, 0, 0, 0, tzinfo=GMT_4) + today_mdn_utc = datetime.datetime.combine(today, midnight_utc) + today_mdn_gmt_4 = datetime.datetime.combine(today, midnight_gmt_4) + + now_naive = datetime.datetime.now() + now_utc = now_naive.replace(tzinfo=datetime.timezone.utc) + now_gmt_4 = now_naive.replace(tzinfo=GMT_4) + + if appstruct == "null": + appstruct = null + elif appstruct == "now_utc": + appstruct = now_utc + elif appstruct == "now_gmt_4": + appstruct = now_gmt_4 + elif appstruct == "now_naive": + appstruct = now_naive + elif appstruct == "today": + appstruct = today + + if exp_cstruct == "null": + exp_cstruct = null + elif exp_cstruct == "now_utc_iso": + exp_cstruct = now_utc.isoformat() + elif exp_cstruct == "now_utc_locale": + exp_cstruct = now_utc.strftime("%c") + elif exp_cstruct == "now_gmt_4_iso": + exp_cstruct = now_gmt_4.isoformat() + elif exp_cstruct == "now_gmt_4_locale": + exp_cstruct = now_gmt_4.strftime("%c") + elif exp_cstruct == "today_mdn_utc_iso": + exp_cstruct = today_mdn_utc.isoformat() + elif exp_cstruct == "today_mdn_utc_locale": + exp_cstruct = today_mdn_utc.strftime("%c") + elif exp_cstruct == "today_mdn_gmt_4_iso": + exp_cstruct = today_mdn_gmt_4.isoformat() + elif exp_cstruct == "today_mdn_gmt_4_locale": + exp_cstruct = today_mdn_gmt_4.strftime("%c") + + kw = {} + + if dtz == "GMT_4": + dtz = GMT_4 + + if dtz is not None: + kw["default_tzinfo"] = dtz + + if fmt is not None: + kw["format"] = fmt + + typ = DateTime(**kw) + node = mock.Mock(set_spec=()) + + if exp_cstruct == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.serialize(node, appstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a datetime object' + assert invalid.msg.mapping["val"] is appstruct + + else: + result = typ.serialize(node, appstruct) + assert result == exp_cstruct + + +@pytest.mark.parametrize( + "dtz, fmt, cstruct, exp_appstruct", + [ + (None, None, "null", "null"), + (None, None, None, "null"), + (None, None, MARKER, "Invalid"), + # non-naive datetime + (None, None, "now_utc_iso", "now_utc"), + (None, "%c %z", "now_utc_locale", "now_utc_no_usecs"), + ("GMT_4", None, "now_utc_iso", "now_utc"), + ("GMT_4", "%c %z", "now_utc_locale", "now_utc_no_usecs"), + # naive datetime + (None, None, "now_naive_iso", "now_utc"), + (None, "%c", "now_naive_locale", "now_utc_no_usecs"), + ("GMT_4", None, "now_naive_iso", "now_gmt_4"), + ("GMT_4", "%c", "now_naive_locale", "now_gmt_4_no_usecs"), + ], +) +def test_DateTime_deserialize(dtz, fmt, cstruct, exp_appstruct): + import datetime + import iso8601 + + from colander import DateTime + from colander import Invalid + from colander import null + + GMT_4 = iso8601.FixedOffset(-4, 0, "GMT-4") + + now_naive = datetime.datetime.now() + now_utc = now_naive.replace(tzinfo=datetime.timezone.utc) + now_gmt_4 = now_naive.replace(tzinfo=GMT_4) + + if cstruct == "null": + cstruct = null + elif cstruct == "now_naive_iso": + cstruct = now_naive.isoformat() + elif cstruct == "now_naive_locale": + cstruct = now_naive.strftime("%c") + elif cstruct == "now_utc_iso": + cstruct = now_utc.isoformat() + elif cstruct == "now_utc_locale": + cstruct = now_utc.strftime("%c %z") + elif cstruct == "now_gmt_4_iso": + cstruct = now_gmt_4.isoformat() + elif cstruct == "now_gmt_4_locale": + cstruct = now_gmt_4.strftime("%c") + + if exp_appstruct == "null": + exp_appstruct = null + elif exp_appstruct == "now_utc": + exp_appstruct = now_utc + elif exp_appstruct == "now_utc_no_usecs": + exp_appstruct = now_utc.replace(microsecond=0) + elif exp_appstruct == "now_gmt_4": + exp_appstruct = now_gmt_4 + elif exp_appstruct == "now_gmt_4_no_usecs": + exp_appstruct = now_gmt_4.replace(microsecond=0) + + kw = {} + + if dtz == "GMT_4": + dtz = GMT_4 + + if dtz is not None: + kw["default_tzinfo"] = dtz + + if fmt is not None: + kw["format"] = fmt + + typ = DateTime(**kw) + node = mock.Mock(set_spec=()) + + if exp_appstruct == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == DateTime.err_template + assert invalid.msg.mapping["val"] is cstruct + + else: + result = typ.deserialize(node, cstruct) + assert result == exp_appstruct + + +@pytest.mark.parametrize("fmt", [None, "%c"]) +def test_Date_ctor(fmt): + from colander import Date + + kw = {} + + if fmt is not None: + kw["format"] = fmt + + typ = Date(**kw) + + assert typ.format == fmt + + +@pytest.mark.parametrize( + "fmt, appstruct, exp_cstruct", + [ + (None, "null", "null"), + (None, None, "null"), + (None, MARKER, "Invalid"), + (None, "today", "today_iso"), + (None, "now", "today_iso"), + ("%c", "today", "today_locale"), + ("%c", "now", "today_locale"), + ], +) +def test_Date_serialize(fmt, appstruct, exp_cstruct): + import datetime + + from colander import Date + from colander import Invalid + from colander import null + + today = datetime.date.today() + now = datetime.datetime.now() + + kw = {} + + if fmt is not None: + kw["format"] = fmt + + typ = Date(**kw) + node = mock.Mock(set_spec=()) + + if appstruct == "null": + appstruct = null + elif appstruct == "today": + appstruct = today + elif appstruct == "now": + appstruct = now + + if exp_cstruct == "null": + exp_cstruct = null + elif exp_cstruct == "today_iso": + exp_cstruct = today.isoformat() + elif exp_cstruct == "today_locale": + exp_cstruct = today.strftime("%c") + + if exp_cstruct == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.serialize(node, appstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a date object' + assert invalid.msg.mapping["val"] is appstruct + + else: + result = typ.serialize(node, appstruct) + assert result == exp_cstruct + + +@pytest.mark.parametrize( + "fmt, cstruct, exp_appstruct", + [ + (None, "null", "null"), + (None, None, "null"), + (None, MARKER, "Invalid"), + (None, "today_iso", "today"), + ("%c", "today_locale", "today"), + ], +) +def test_Date_deserialize(fmt, cstruct, exp_appstruct): + import datetime + + from colander import Date + from colander import Invalid + from colander import null + + today = datetime.date.today() + + kw = {} + + if fmt is not None: + kw["format"] = fmt + + typ = Date(**kw) + node = mock.Mock(set_spec=()) + + if cstruct == "null": + cstruct = null + elif cstruct == "today_iso": + cstruct = today.isoformat() + elif cstruct == "today_locale": + cstruct = today.strftime("%c") + + if exp_appstruct == "null": + exp_appstruct = null + elif exp_appstruct == "today": + exp_appstruct = today + + if exp_appstruct == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == Date.err_template + assert invalid.msg.mapping["val"] is cstruct + + else: + result = typ.deserialize(node, cstruct) + assert result == exp_appstruct + + +@pytest.mark.parametrize( + "appstruct, exp_cstruct", + [ + ("null", "null"), + (None, "null"), + (MARKER, "Invalid"), + ("now", "now_time_iso"), + ("now_time", "now_time_iso"), + ], +) +def test_Time_serialize(appstruct, exp_cstruct): + import datetime + + from colander import Invalid + from colander import Time + from colander import null + + now = datetime.datetime.now() + now_time = now.time() + + typ = Time() + node = mock.Mock(set_spec=()) + + if appstruct == "null": + appstruct = null + elif appstruct == "now": + appstruct = now + elif appstruct == "now_time": + appstruct = now_time + + if exp_cstruct == "null": + exp_cstruct = null + elif exp_cstruct == "now_time_iso": + exp_cstruct = now_time.isoformat() + + if exp_cstruct == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.serialize(node, appstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a time object' + assert invalid.msg.mapping["val"] is appstruct + + else: + result = typ.serialize(node, appstruct) + assert result == exp_cstruct + + +@pytest.mark.parametrize( + "fmt, cstruct, exp_appstruct", + [ + (None, "null", "null"), + (None, None, "null"), + (None, MARKER, "Invalid"), + (None, "now_iso", "now_time"), + (None, "now_time_iso", "now_time"), + (None, "now_time_hhmmss_f", "now_time"), + (None, "now_time_hhmmss", "now_time_no_usecs"), + (None, "now_time_hhmm", "now_time_no_secs"), + ], +) +def test_Time_deserialize(fmt, cstruct, exp_appstruct): + import datetime + + from colander import Invalid + from colander import Time + from colander import null + + now = datetime.datetime.now() + now_time = now.time() + + kw = {} + + if fmt is not None: + kw["format"] = fmt + + typ = Time(**kw) + node = mock.Mock(set_spec=()) + + if cstruct == "null": + cstruct = null + elif cstruct == "now_iso": + cstruct = now.isoformat() + elif cstruct == "now_time_iso": + cstruct = now_time.isoformat() + elif cstruct == "now_time_hhmmss_f": + cstruct = now_time.strftime('%H:%M:%S.%f') + elif cstruct == "now_time_hhmmss": + cstruct = now_time.strftime('%H:%M:%S') + elif cstruct == "now_time_hhmm": + cstruct = now_time.strftime('%H:%M') + + if exp_appstruct == "null": + exp_appstruct = null + elif exp_appstruct == "now_time": + exp_appstruct = now_time + elif exp_appstruct == "now_time_no_usecs": + exp_appstruct = now_time.replace(microsecond=0) + elif exp_appstruct == "now_time_no_secs": + exp_appstruct = now_time.replace(second=0, microsecond=0) + + if exp_appstruct == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == Time.err_template + assert invalid.msg.mapping["val"] is cstruct + + else: + result = typ.deserialize(node, cstruct) + assert result == exp_appstruct + + +@pytest.fixture(scope="module") +def test_enum(): + import enum + + class TestEnum(enum.Enum): + FOO = 1 + BAR = 2 + BAZ = 3 + + @property + def valstr(self): + return str(self.value) + + return TestEnum + + +@pytest.mark.parametrize( + "attr, exp_attr", + [ + (None, "name"), + ("name", "name"), + ("valstr", "valstr"), + ("bogus", "AttributeError"), + ("FOO", "ValueError"), + ], +) +@pytest.mark.parametrize( + "val_typ, exp_typ", + [ + (None, "string"), + ("integer", "integer"), + ], +) +def test_Enum_ctor(test_enum, attr, exp_attr, val_typ, exp_typ): + from colander import Enum + from colander import Integer + from colander import String + + kw = {} + + if attr is not None: + kw["attr"] = attr + + if val_typ == "integer": + val_typ = Integer + + if exp_typ == "string": + exp_typ = String + elif exp_typ == "integer": + exp_typ = Integer + + if val_typ is not None: + kw["typ"] = val_typ() + + if exp_attr == "AttributeError": + + with pytest.raises(AttributeError): + Enum(test_enum, **kw) + + elif exp_attr == "ValueError": + + with pytest.raises(ValueError): + Enum(test_enum, **kw) + + else: + + typ = Enum(test_enum, **kw) + + assert typ.enum_cls is test_enum + assert typ.attr == exp_attr + assert isinstance(typ.typ, exp_typ) + + if exp_attr == "name": + assert typ.values == test_enum.__members__ + + elif exp_attr == "valstr": + assert typ.values["1"] == test_enum.FOO + assert typ.values["2"] == test_enum.BAR + assert typ.values["3"] == test_enum.BAZ + + +@pytest.mark.parametrize( + "attr, val_typ, appstruct, exp_cstruct", + [ + ("name", "string", "null", "null"), + ("name", "string", MARKER, "Invalid"), + ("name", "string", "FOO", "FOO"), + ("value", "integer", "FOO", "1"), + ("valstr", "string", "BAR", "2"), + ], +) +def test_Enum_serialize(test_enum, attr, val_typ, appstruct, exp_cstruct): + from colander import Enum + from colander import Integer + from colander import Invalid + from colander import String + from colander import null + + if val_typ == "integer": + val_typ = Integer + elif val_typ == "string": + val_typ = String + + if appstruct == "null": + appstruct = null + elif appstruct is not MARKER: + appstruct = getattr(test_enum, appstruct) + + if exp_cstruct == "null": + exp_cstruct = null + + typ = Enum(test_enum, attr=attr, typ=val_typ()) + node = mock.Mock(set_spec=()) + + if exp_cstruct == "Invalid": + + with pytest.raises(Invalid) as exc: + typ.serialize(node, appstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == '"${val}" is not a valid "${cls}"' + assert invalid.msg.mapping["val"] is appstruct + assert invalid.msg.mapping["cls"] == test_enum.__name__ + else: + result = typ.serialize(node, appstruct) + assert result == exp_cstruct + + +@pytest.mark.parametrize( + "attr, val_typ, cstruct, exp_appstruct", + [ + ("name", "string", "null", "null"), + ("name", "string", MARKER, "InvalidType"), + ("name", "string", "QUX", "InvalidValue"), + ("name", "string", "FOO", "FOO"), + ("value", "integer", 1, "FOO"), + ("valstr", "string", "2", "BAR"), + ], +) +def test_Enum_deserialize(test_enum, attr, val_typ, cstruct, exp_appstruct): + from colander import Enum + from colander import Integer + from colander import Invalid + from colander import String + from colander import null + + if val_typ == "integer": + val_typ = Integer + elif val_typ == "string": + val_typ = String + + if cstruct == "null": + cstruct = null + + if exp_appstruct == "null": + exp_appstruct = null + elif not exp_appstruct.startswith("Invalid"): + exp_appstruct = getattr(test_enum, exp_appstruct) + + typ = Enum(test_enum, attr=attr, typ=val_typ()) + node = mock.Mock(set_spec=()) + + if exp_appstruct in ("InvalidType", "InvalidValue"): + + with pytest.raises(Invalid) as exc: + typ.deserialize(node, cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + + if exp_appstruct == "InvalidType": + assert invalid.msg.default == '${val} is not a string: ${err}' + assert invalid.msg.mapping["val"] is cstruct + elif exp_appstruct == "InvalidValue": + assert invalid.msg.default == '"${val}" is not a valid "${cls}"' + assert invalid.msg.mapping["val"] is cstruct + assert invalid.msg.mapping["cls"] == test_enum.__name__ + else: + result = typ.deserialize(node, cstruct) + assert result == exp_appstruct From a600f405bbc14327e3b2c266ba2767da4f5e91c2 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 12 Aug 2024 22:32:54 -0400 Subject: [PATCH 13/16] tests: test schemas using pytest Split declarative tests out to separate 'tests/test_declarative.py'. --- tests/test_colander.py | 1146 ------------------------------------- tests/test_declarative.py | 429 ++++++++++++++ tests/test_schema.py | 1115 ++++++++++++++++++++++++++++++++++++ 3 files changed, 1544 insertions(+), 1146 deletions(-) create mode 100644 tests/test_declarative.py create mode 100644 tests/test_schema.py diff --git a/tests/test_colander.py b/tests/test_colander.py index 90302ec..5cc8e72 100644 --- a/tests/test_colander.py +++ b/tests/test_colander.py @@ -15,1152 +15,6 @@ def invalid_exc(func, *arg, **kw): raise AssertionError('Invalid not raised') # pragma: no cover -class TestSchemaNode(unittest.TestCase): - def _makeOne(self, *arg, **kw): - from colander import SchemaNode - - return SchemaNode(*arg, **kw) - - def test_new_sets_order(self): - node = self._makeOne(None) - self.assertTrue(hasattr(node, '_order')) - - def test_ctor_no_title(self): - child = DummySchemaNode(None, name='fred') - node = self._makeOne( - None, - child, - validator=1, - default=2, - name='name_a', - missing='missing', - ) - self.assertEqual(node.typ, None) - self.assertEqual(node.children, [child]) - self.assertEqual(node.validator, 1) - self.assertEqual(node.default, 2) - self.assertEqual(node.missing, 'missing') - self.assertEqual(node.name, 'name_a') - self.assertEqual(node.title, 'Name A') - - def test_ctor_with_title(self): - child = DummySchemaNode(None, name='fred') - node = self._makeOne( - None, child, validator=1, default=2, name='name', title='title' - ) - self.assertEqual(node.typ, None) - self.assertEqual(node.children, [child]) - self.assertEqual(node.validator, 1) - self.assertEqual(node.default, 2) - self.assertEqual(node.name, 'name') - self.assertEqual(node.title, 'title') - - def test_ctor_with_description(self): - node = self._makeOne( - None, - validator=1, - default=2, - name='name', - title='title', - description='desc', - ) - self.assertEqual(node.description, 'desc') - - def test_ctor_with_widget(self): - node = self._makeOne(None, widget='abc') - self.assertEqual(node.widget, 'abc') - - def test_ctor_with_preparer(self): - node = self._makeOne(None, preparer='abc') - self.assertEqual(node.preparer, 'abc') - - def test_ctor_without_preparer(self): - node = self._makeOne(None) - self.assertEqual(node.preparer, None) - - def test_ctor_with_unknown_kwarg(self): - node = self._makeOne(None, foo=1) - self.assertEqual(node.foo, 1) - - def test_ctor_with_kwarg_typ(self): - node = self._makeOne(typ='foo') - self.assertEqual(node.typ, 'foo') - - def test_ctor_children_kwarg_typ(self): - subnode1 = DummySchemaNode(None, name='sub1') - subnode2 = DummySchemaNode(None, name='sub2') - node = self._makeOne(subnode1, subnode2, typ='foo') - self.assertEqual(node.children, [subnode1, subnode2]) - - def test_ctor_without_type(self): - self.assertRaises(NotImplementedError, self._makeOne) - - def test_required_true(self): - node = self._makeOne(None) - self.assertEqual(node.required, True) - - def test_required_false(self): - node = self._makeOne(None, missing=1) - self.assertEqual(node.required, False) - - def test_required_deferred(self): - from colander import deferred - - node = self._makeOne(None, missing=deferred(lambda: '123')) - self.assertEqual(node.required, True) - - def test_deserialize_no_validator(self): - typ = DummyType() - node = self._makeOne(typ) - result = node.deserialize(1) - self.assertEqual(result, 1) - - def test_deserialize_with_preparer(self): - from colander import Invalid - - typ = DummyType() - - def preparer(value): - return 'prepared_' + value - - def validator(node, value): - if not value.startswith('prepared'): - raise Invalid(node, 'not prepared') # pragma: no cover - - node = self._makeOne(typ, preparer=preparer, validator=validator) - self.assertEqual(node.deserialize('value'), 'prepared_value') - - def test_deserialize_with_multiple_preparers(self): - from colander import Invalid - - typ = DummyType() - - def preparer1(value): - return 'prepared1_' + value - - def preparer2(value): - return 'prepared2_' + value - - def validator(node, value): - if not value.startswith('prepared2_prepared1'): - raise Invalid(node, 'not prepared') # pragma: no cover - - node = self._makeOne( - typ, preparer=[preparer1, preparer2], validator=validator - ) - self.assertEqual( - node.deserialize('value'), 'prepared2_prepared1_value' - ) - - def test_deserialize_preparer_before_missing_check(self): - from colander import null - - typ = DummyType() - - def preparer(value): - return null - - node = self._makeOne(typ, preparer=preparer) - e = invalid_exc(node.deserialize, 1) - self.assertEqual(e.msg, 'Required') - - def test_deserialize_with_validator(self): - typ = DummyType() - validator = DummyValidator(msg='Wrong') - node = self._makeOne(typ, validator=validator) - e = invalid_exc(node.deserialize, 1) - self.assertEqual(e.msg, 'Wrong') - - def test_deserialize_with_unbound_validator(self): - from colander import Invalid - from colander import UnboundDeferredError - from colander import deferred - - typ = DummyType() - - def validator(node, kw): - def _validate(node, value): - node.raise_invalid('Invalid') - - return _validate - - node = self._makeOne(typ, validator=deferred(validator)) - self.assertRaises(UnboundDeferredError, node.deserialize, None) - self.assertRaises(Invalid, node.bind(foo='foo').deserialize, None) - - def test_deserialize_value_is_null_no_missing(self): - from colander import Invalid - from colander import null - - typ = DummyType() - node = self._makeOne(typ) - self.assertRaises(Invalid, node.deserialize, null) - - def test_deserialize_value_is_null_with_missing(self): - from colander import null - - typ = DummyType() - node = self._makeOne(typ) - node.missing = 'abc' - self.assertEqual(node.deserialize(null), 'abc') - - def test_deserialize_value_is_null_with_missing_msg(self): - from colander import null - - typ = DummyType() - node = self._makeOne(typ, missing_msg='Missing') - e = invalid_exc(node.deserialize, null) - self.assertEqual(e.msg, 'Missing') - - def test_deserialize_value_with_interpolated_missing_msg(self): - from colander import null - - typ = DummyType() - node = self._makeOne( - typ, missing_msg='Missing attribute ${title}', name='name_a' - ) - e = invalid_exc(node.deserialize, null) - self.assertEqual(e.msg.interpolate(), 'Missing attribute Name A') - - def test_deserialize_noargs_uses_default(self): - typ = DummyType() - node = self._makeOne(typ) - node.missing = 'abc' - self.assertEqual(node.deserialize(), 'abc') - - def test_deserialize_null_can_be_used_as_missing(self): - from colander import null - - typ = DummyType() - node = self._makeOne(typ) - node.missing = null - self.assertEqual(node.deserialize(null), null) - - def test_deserialize_appstruct_deferred(self): - from colander import Invalid - from colander import deferred - from colander import null - - typ = DummyType() - node = self._makeOne(typ) - node.missing = deferred(lambda: '123') - self.assertRaises(Invalid, node.deserialize, null) - - def test_serialize(self): - typ = DummyType() - node = self._makeOne(typ) - result = node.serialize(1) - self.assertEqual(result, 1) - - def test_serialize_value_is_null_no_default(self): - from colander import null - - typ = DummyType() - node = self._makeOne(typ) - result = node.serialize(null) - self.assertEqual(result, null) - - def test_serialize_value_is_null_with_default(self): - from colander import null - - typ = DummyType() - node = self._makeOne(typ) - node.default = 1 - result = node.serialize(null) - self.assertEqual(result, 1) - - def test_serialize_noargs_uses_default(self): - typ = DummyType() - node = self._makeOne(typ) - node.default = 'abc' - self.assertEqual(node.serialize(), 'abc') - - def test_serialize_default_deferred(self): - from colander import deferred - from colander import null - - typ = DummyType() - node = self._makeOne(typ) - node.default = deferred(lambda: 'abc') - self.assertEqual(node.serialize(), null) - - def test_add(self): - node = self._makeOne(None) - node.add(1) - self.assertEqual(node.children, [1]) - - def test_insert(self): - node = self._makeOne(None) - node.children = [99, 99] - node.insert(1, 'foo') - self.assertEqual(node.children, [99, 'foo', 99]) - - def test_repr(self): - node = self._makeOne(None, name='flub') - result = repr(node) - self.assertTrue(result.startswith('")) - - def test___getitem__success(self): - node = self._makeOne(None) - another = self._makeOne(None, name='another') - node.add(another) - self.assertEqual(node['another'], another) - - def test___getitem__failure(self): - node = self._makeOne(None) - self.assertRaises(KeyError, node.__getitem__, 'another') - - def test___delitem__success(self): - node = self._makeOne(None) - another = self._makeOne(None, name='another') - node.add(another) - del node['another'] - self.assertEqual(node.children, []) - - def test___delitem__failure(self): - node = self._makeOne(None) - self.assertRaises(KeyError, node.__delitem__, 'another') - - def test___setitem__override(self): - node = self._makeOne(None) - another = self._makeOne(None, name='another') - node.add(another) - andanother = self._makeOne(None, name='andanother') - node['another'] = andanother - self.assertEqual(node['another'], andanother) - self.assertEqual(andanother.name, 'another') - - def test___setitem__no_override(self): - another = self._makeOne(None, name='another') - node = self._makeOne(None) - node['another'] = another - self.assertEqual(node['another'], another) - self.assertEqual(node.children[0], another) - - def test___iter__(self): - node = self._makeOne(None) - node.children = ['a', 'b', 'c'] - it = node.__iter__() - self.assertEqual(list(it), ['a', 'b', 'c']) - - def test___contains__(self): - node = self._makeOne(None) - another = self._makeOne(None, name='another') - node.add(another) - self.assertEqual('another' in node, True) - self.assertEqual('b' in node, False) - - def test_clone(self): - inner_typ = DummyType() - outer_typ = DummyType() - outer_node = self._makeOne(outer_typ, name='outer') - inner_node = self._makeOne(inner_typ, name='inner') - outer_node.foo = 1 - inner_node.foo = 2 - outer_node.children = [inner_node] - outer_clone = outer_node.clone() - self.assertFalse(outer_clone is outer_node) - self.assertEqual(outer_clone.typ, outer_typ) - self.assertEqual(outer_clone.name, 'outer') - self.assertEqual(outer_node.foo, 1) - self.assertEqual(len(outer_clone.children), 1) - inner_clone = outer_clone.children[0] - self.assertFalse(inner_clone is inner_node) - self.assertEqual(inner_clone.typ, inner_typ) - self.assertEqual(inner_clone.name, 'inner') - self.assertEqual(inner_clone.foo, 2) - - def test_clone_with_modified_schema_instance(self): - class Schema(colander.MappingSchema): - n1 = colander.SchemaNode(colander.String()) - n2 = colander.SchemaNode(colander.String()) - - def compare_children(schema, cloned): - # children of the clone must match the cloned node's children and - # have to be clones themselves. - self.assertEqual(len(schema.children), len(cloned.children)) - for child, child_clone in zip(schema.children, cloned.children): - self.assertIsNot(child, child_clone) - for name in child.__dict__.keys(): - self.assertEqual( - getattr(child, name), getattr(child_clone, name) - ) - - # add a child node before cloning - schema = Schema() - schema.add(colander.SchemaNode(colander.String(), name='n3')) - compare_children(schema, schema.clone()) - # remove a child node before cloning - schema = Schema() - del schema['n1'] - compare_children(schema, schema.clone()) - # reorder children before cloning - schema = Schema() - schema.children = list(reversed(schema.children)) - compare_children(schema, schema.clone()) - - def test_clone_mapping_references(self): - class Schema(colander.MappingSchema): - n1 = colander.SchemaNode(colander.Mapping(unknown='preserve')) - - foo = {"n1": {"bar": {"baz": "qux"}}} - bar = Schema().serialize(foo) - bar["n1"]["bar"]["baz"] = "foobar" - self.assertEqual(foo["n1"]["bar"]["baz"], "qux") - - def test_bind(self): - from colander import deferred - - inner_typ = DummyType() - outer_typ = DummyType() - - def dv(node, kw): - self.assertTrue(node.name in ['outer', 'inner']) - self.assertTrue('a' in kw) - return '123' - - dv = deferred(dv) - outer_node = self._makeOne(outer_typ, name='outer', missing=dv) - inner_node = self._makeOne( - inner_typ, name='inner', validator=dv, missing=dv - ) - outer_node.children = [inner_node] - outer_clone = outer_node.bind(a=1) - self.assertFalse(outer_clone is outer_node) - self.assertEqual(outer_clone.missing, '123') - inner_clone = outer_clone.children[0] - self.assertFalse(inner_clone is inner_node) - self.assertEqual(inner_clone.missing, '123') - self.assertEqual(inner_clone.validator, '123') - - def test_bind_with_after_bind(self): - from colander import deferred - - inner_typ = DummyType() - outer_typ = DummyType() - - def dv(node, kw): - self.assertTrue(node.name in ['outer', 'inner']) - self.assertTrue('a' in kw) - return '123' - - dv = deferred(dv) - - def remove_inner(node, kw): - self.assertEqual(kw, {'a': 1}) - del node['inner'] - - outer_node = self._makeOne( - outer_typ, name='outer', missing=dv, after_bind=remove_inner - ) - inner_node = self._makeOne( - inner_typ, name='inner', validator=dv, missing=dv - ) - outer_node.children = [inner_node] - outer_clone = outer_node.bind(a=1) - self.assertFalse(outer_clone is outer_node) - self.assertEqual(outer_clone.missing, '123') - self.assertEqual(len(outer_clone.children), 0) - self.assertEqual(len(outer_node.children), 1) - - def test_declarative_name_reassignment(self): - # see https://github.com/Pylons/colander/issues/39 - - class FnordSchema(colander.Schema): - fnord = colander.SchemaNode( - colander.Sequence(), - colander.SchemaNode(colander.Integer(), name=''), - name="fnord[]", - ) - - schema = FnordSchema() - self.assertEqual(schema['fnord[]'].name, 'fnord[]') - - def test_cstruct_children(self): - typ = DummyType() - typ.cstruct_children = lambda *arg: ['foo'] - node = self._makeOne(typ) - self.assertEqual(node.cstruct_children(None), ['foo']) - - def test_cstruct_children_warning(self): - import warnings - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - typ = None - node = self._makeOne(typ) - self.assertEqual(node.cstruct_children(None), []) - self.assertEqual(len(w), 1) - - def test_raise_invalid(self): - - typ = DummyType() - node = self._makeOne(typ) - self.assertRaises(colander.Invalid, node.raise_invalid, 'Wrong') - - -class TestSchemaNodeSubclassing(unittest.TestCase): - def test_subclass_uses_validator_method(self): - class MyNode(colander.SchemaNode): - schema_type = colander.Int - name = 'my' - - def validator(self, node, cstruct): - if cstruct > 10: - self.raise_invalid('Wrong') - - node = MyNode() - self.assertRaises(colander.Invalid, node.deserialize, 20) - - def test_subclass_uses_missing(self): - class MyNode(colander.SchemaNode): - schema_type = colander.Int - name = 'my' - missing = 10 - - node = MyNode() - result = node.deserialize(colander.null) - self.assertEqual(result, 10) - - def test_subclass_uses_title(self): - class MyNode(colander.SchemaNode): - schema_type = colander.Int - title = 'some title' - - node = MyNode(name='my') - self.assertEqual(node.title, 'some title') - - def test_subclass_title_overwritten_by_constructor(self): - class MyNode(colander.SchemaNode): - schema_type = colander.Int - title = 'some title' - - node = MyNode(name='my', title='other title') - self.assertEqual(node.title, 'other title') - - def test_subelement_title_not_overwritten(self): - class SampleNode(colander.SchemaNode): - schema_type = colander.String - title = 'Some Title' - - class SampleSchema(colander.Schema): - node = SampleNode() - - schema = SampleSchema() - self.assertEqual('Some Title', schema.children[0].title) - - def test_subclass_value_overridden_by_constructor(self): - class MyNode(colander.SchemaNode): - schema_type = colander.Int - name = 'my' - missing = 10 - - node = MyNode(missing=5) - result = node.deserialize(colander.null) - self.assertEqual(result, 5) - - def test_method_values_can_rely_on_binding(self): - class MyNode(colander.SchemaNode): - schema_type = colander.Int - - def amethod(self): - return self.bindings['request'] - - node = MyNode() - newnode = node.bind(request=True) - self.assertEqual(newnode.amethod(), True) - - def test_nonmethod_values_can_rely_on_after_bind(self): - class MyNode(colander.SchemaNode): - schema_type = colander.Int - - def after_bind(self, node, kw): - self.missing = kw['missing'] - - node = MyNode() - newnode = node.bind(missing=10) - self.assertEqual(newnode.deserialize(colander.null), 10) - - def test_deferred_methods_dont_quite_work_yet(self): - class MyNode(colander.SchemaNode): - schema_type = colander.Int - - @colander.deferred - def avalidator(self, node, kw): # pragma: no cover - def _avalidator(node, cstruct): - self.raise_invalid('Foo') - - return _avalidator - - node = MyNode() - self.assertRaises(TypeError, node.bind) - - def test_nonmethod_values_can_be_deferred_though(self): - def _missing(node, kw): - return 10 - - class MyNode(colander.SchemaNode): - schema_type = colander.Int - missing = colander.deferred(_missing) - - node = MyNode() - bound_node = node.bind() - self.assertEqual(bound_node.deserialize(colander.null), 10) - - def test_functions_can_be_deferred(self): - class MyNode(colander.SchemaNode): - schema_type = colander.Int - - @colander.deferred - def missing(node, kw): - return 10 - - node = MyNode() - bound_node = node.bind() - self.assertEqual(bound_node.deserialize(colander.null), 10) - - def test_nodes_can_be_deffered(self): - class MySchema(colander.MappingSchema): - @colander.deferred - def child(node, kw): - return colander.SchemaNode(colander.String(), missing='foo') - - node = MySchema() - bound_node = node.bind() - self.assertEqual(bound_node.deserialize({}), {'child': 'foo'}) - - def test_schema_child_names_conflict_with_value_names_notused(self): - class MyNode(colander.SchemaNode): - schema_type = colander.Mapping - title = colander.SchemaNode(colander.String()) - - node = MyNode() - self.assertEqual(node.title, '') - - def test_schema_child_names_conflict_with_value_names_used(self): - - doesntmatter = colander.SchemaNode(colander.String(), name='name') - - class MyNode(colander.SchemaNode): - schema_type = colander.Mapping - name = 'fred' - wontmatter = doesntmatter - - node = MyNode() - self.assertEqual(node.name, 'fred') - self.assertEqual(node['name'], doesntmatter) - - def test_schema_child_names_conflict_with_value_names_in_superclass(self): - - doesntmatter = colander.SchemaNode(colander.String(), name='name') - _name = colander.SchemaNode(colander.String()) - - class MyNode(colander.SchemaNode): - schema_type = colander.Mapping - name = 'fred' - wontmatter = doesntmatter - - class AnotherNode(MyNode): - name = _name - - node = AnotherNode() - self.assertEqual(node.name, 'fred') - self.assertEqual(node['name'], _name) - - def test_schema_child_names_conflict_with_value_names_in_subclass(self): - class MyNode(colander.SchemaNode): - name = colander.SchemaNode(colander.String(), id='name') - - class AnotherNode(MyNode): - schema_type = colander.Mapping - name = 'fred' - doesntmatter = colander.SchemaNode( - colander.String(), name='name', id='doesntmatter' - ) - - node = AnotherNode() - self.assertEqual(node.name, 'fred') - self.assertEqual(node['name'].id, 'doesntmatter') - - -class TestMappingSchemaInheritance(unittest.TestCase): - def test_single_inheritance(self): - class Friend(colander.Schema): - rank = colander.SchemaNode(colander.Int(), id='rank') - name = colander.SchemaNode(colander.String(), id='name') - serial = colander.SchemaNode(colander.Bool(), id='serial2') - - class SpecialFriend(Friend): - iwannacomefirst = colander.SchemaNode( - colander.Int(), id='iwannacomefirst2' - ) - - class SuperSpecialFriend(SpecialFriend): - iwannacomefirst = colander.SchemaNode( - colander.String(), id='iwannacomefirst1' - ) - another = colander.SchemaNode(colander.String(), id='another') - serial = colander.SchemaNode(colander.Int(), id='serial1') - - inst = SuperSpecialFriend() - self.assertEqual( - [x.id for x in inst.children], - ['rank', 'name', 'serial1', 'iwannacomefirst1', 'another'], - ) - - def test_single_inheritance_with_insert_before(self): - class Friend(colander.Schema): - rank = colander.SchemaNode(colander.Int(), id='rank') - name = colander.SchemaNode(colander.String(), id='name') - serial = colander.SchemaNode( - colander.Bool(), insert_before='name', id='serial2' - ) - - class SpecialFriend(Friend): - iwannacomefirst = colander.SchemaNode( - colander.Int(), id='iwannacomefirst2' - ) - - class SuperSpecialFriend(SpecialFriend): - iwannacomefirst = colander.SchemaNode( - colander.String(), insert_before='rank', id='iwannacomefirst1' - ) - another = colander.SchemaNode(colander.String(), id='another') - serial = colander.SchemaNode(colander.Int(), id='serial1') - - inst = SuperSpecialFriend() - self.assertEqual( - [x.id for x in inst.children], - ['iwannacomefirst1', 'rank', 'serial1', 'name', 'another'], - ) - - def test_single_inheritance2(self): - class One(colander.Schema): - a = colander.SchemaNode(colander.Int(), id='a1') - b = colander.SchemaNode(colander.Int(), id='b1') - d = colander.SchemaNode(colander.Int(), id='d1') - - class Two(One): - a = colander.SchemaNode(colander.String(), id='a2') - c = colander.SchemaNode(colander.String(), id='c2') - e = colander.SchemaNode(colander.String(), id='e2') - - class Three(Two): - b = colander.SchemaNode(colander.Bool(), id='b3') - d = colander.SchemaNode(colander.Bool(), id='d3') - f = colander.SchemaNode(colander.Bool(), id='f3') - - inst = Three() - c = inst.children - self.assertEqual(len(c), 6) - result = [x.id for x in c] - self.assertEqual(result, ['a2', 'b3', 'd3', 'c2', 'e2', 'f3']) - - def test_multiple_inheritance(self): - class One(colander.Schema): - a = colander.SchemaNode(colander.Int(), id='a1') - b = colander.SchemaNode(colander.Int(), id='b1') - d = colander.SchemaNode(colander.Int(), id='d1') - - class Two(colander.Schema): - a = colander.SchemaNode(colander.String(), id='a2') - c = colander.SchemaNode(colander.String(), id='c2') - e = colander.SchemaNode(colander.String(), id='e2') - - class Three(Two, One): - b = colander.SchemaNode(colander.Bool(), id='b3') - d = colander.SchemaNode(colander.Bool(), id='d3') - f = colander.SchemaNode(colander.Bool(), id='f3') - - inst = Three() - c = inst.children - self.assertEqual(len(c), 6) - result = [x.id for x in c] - self.assertEqual(result, ['a2', 'b3', 'd3', 'c2', 'e2', 'f3']) - - def test_insert_before_failure(self): - class One(colander.Schema): - a = colander.SchemaNode(colander.Int()) - b = colander.SchemaNode(colander.Int(), insert_before='c') - - self.assertRaises(KeyError, One) - - -class TestDeferred(unittest.TestCase): - def _makeOne(self, wrapped): - from colander import deferred - - return deferred(wrapped) - - def test_ctor(self): - wrapped = lambda: 'foo' - inst = self._makeOne(wrapped) - self.assertEqual(inst.wrapped, wrapped) - - def test___call__(self): - n = object() - k = object() - - def wrapped(node, kw): - self.assertEqual(node, n) - self.assertEqual(kw, k) - return 'abc' - - inst = self._makeOne(wrapped) - result = inst(n, k) - self.assertEqual(result, 'abc') - - def test_retain_func_details(self): - def wrapped_func(node, kw): - """Can you hear me now?""" - pass # pragma: no cover - - inst = self._makeOne(wrapped_func) - self.assertEqual(inst.__doc__, 'Can you hear me now?') - self.assertEqual(inst.__name__, 'wrapped_func') - - def test_w_callable_instance_no_name(self): - class Wrapped: - """CLASS""" - - def __call__(self, node, kw): - """METHOD""" - pass # pragma: no cover - - wrapped = Wrapped() - inst = self._makeOne(wrapped) - self.assertEqual(inst.__doc__, wrapped.__doc__) - self.assertFalse('__name__' in inst.__dict__) - - def test_w_callable_instance_no_name_or_doc(self): - class Wrapped: - def __call__(self, node, kw): - pass # pragma: no cover - - wrapped = Wrapped() - inst = self._makeOne(wrapped) - self.assertEqual(inst.__doc__, None) - self.assertFalse('__name__' in inst.__dict__) - - def test_deferred_with_insert_before(self): - def wrapped_func(node, kw): - return colander.SchemaNode( - colander.Int(), - insert_before='name2', - ) - - deferred_node = self._makeOne(wrapped_func) - - class MySchema(colander.Schema): - name2 = colander.SchemaNode(colander.Int()) - name3 = colander.SchemaNode(colander.Int()) - name1 = deferred_node - name4 = colander.SchemaNode(colander.Int()) - - inst = MySchema().bind() - self.assertEqual( - [x.name for x in inst.children], - ['name1', 'name2', 'name3', 'name4'], - ) - - -class TestSchema(unittest.TestCase): - def test_alias(self): - from colander import MappingSchema - from colander import Schema - - self.assertEqual(Schema, MappingSchema) - - def test_it(self): - class MySchema(colander.Schema): - thing_a = colander.SchemaNode(colander.String()) - thing2 = colander.SchemaNode(colander.String(), title='bar') - - node = MySchema(default='abc') - self.assertTrue(hasattr(node, '_order')) - self.assertEqual(node.default, 'abc') - self.assertTrue(isinstance(node, colander.SchemaNode)) - self.assertEqual(node.typ.__class__, colander.Mapping) - self.assertEqual(node.children[0].typ.__class__, colander.String) - self.assertEqual(node.children[0].title, 'Thing A') - self.assertEqual(node.children[1].title, 'bar') - - def test_title_munging(self): - class MySchema(colander.Schema): - thing1 = colander.SchemaNode(colander.String()) - thing2 = colander.SchemaNode(colander.String(), title=None) - thing3 = colander.SchemaNode(colander.String(), title='') - thing4 = colander.SchemaNode(colander.String(), title='thing2') - - node = MySchema() - self.assertEqual(node.children[0].title, 'Thing1') - self.assertEqual(node.children[1].title, None) - self.assertEqual(node.children[2].title, '') - self.assertEqual(node.children[3].title, 'thing2') - - def test_deserialize_drop(self): - class MySchema(colander.Schema): - a = colander.SchemaNode(colander.String()) - b = colander.SchemaNode(colander.String(), missing=colander.drop) - - node = MySchema() - expected = {'a': 'test'} - result = node.deserialize(expected) - self.assertEqual(result, expected) - - def test_serialize_drop_default(self): - class MySchema(colander.Schema): - a = colander.SchemaNode(colander.String()) - b = colander.SchemaNode(colander.String(), default=colander.drop) - - node = MySchema() - expected = {'a': 'foo'} - result = node.serialize(expected) - self.assertEqual(result, expected) - - def test_imperative_with_implicit_schema_type(self): - - node = colander.SchemaNode(colander.String()) - schema = colander.Schema(node) - self.assertEqual(schema.schema_type, colander.Mapping) - self.assertEqual(schema.children[0], node) - - def test_schema_with_cloned_nodes(self): - test_node = colander.SchemaNode(colander.String()) - - class TestSchema(colander.Schema): - a = test_node.clone() - b = test_node.clone() - - node = TestSchema() - expected = {'a': 'foo', 'b': 'bar'} - result = node.serialize(expected) - self.assertEqual(result, expected) - - -class TestMappingSchema(unittest.TestCase): - def test_succeed(self): - import colander - - class MySchema(colander.MappingSchema): - pass - - node = MySchema() - self.assertTrue(isinstance(node, colander.SchemaNode)) - self.assertEqual(node.typ.__class__, colander.Mapping) - - def test_imperative_with_implicit_schema_type(self): - import colander - - node = colander.SchemaNode(colander.String()) - schema = colander.MappingSchema(node) - self.assertEqual(schema.schema_type, colander.Mapping) - self.assertEqual(schema.children[0], node) - - def test_deserialize_missing_drop(self): - import colander - - class MySchema(colander.MappingSchema): - a = colander.SchemaNode( - colander.String(), default='abc', missing=colander.drop - ) - - node = MySchema() - result = node.deserialize({}) - self.assertEqual(result, {}) - result = node.deserialize({'a': colander.null}) - self.assertEqual(result, {}) - result = node.deserialize({'a': ''}) - self.assertEqual(result, {}) - - def test_deserialize_missing_value(self): - import colander - - class MySchema(colander.MappingSchema): - a = colander.SchemaNode( - colander.String(), default=colander.drop, missing='abc' - ) - - node = MySchema() - result = node.deserialize({}) - self.assertEqual(result, {'a': 'abc'}) - result = node.deserialize({'a': colander.null}) - self.assertEqual(result, {'a': 'abc'}) - - def test_serialize_default_drop(self): - import colander - - class MySchema(colander.MappingSchema): - a = colander.SchemaNode( - colander.String(), default=colander.drop, missing='def' - ) - - node = MySchema() - result = node.serialize({}) - self.assertEqual(result, {}) - result = node.serialize({'a': colander.null}) - self.assertEqual(result, {}) - - def test_serialize_default_value(self): - import colander - - class MySchema(colander.MappingSchema): - a = colander.SchemaNode( - colander.String(), default='abc', missing=colander.drop - ) - - node = MySchema() - result = node.serialize({}) - self.assertEqual(result, {'a': 'abc'}) - result = node.serialize({'a': colander.null}) - self.assertEqual(result, {'a': 'abc'}) - - def test_clone_with_mapping_schema(self): - import colander - - thingnode = colander.SchemaNode(colander.String(), name='foo') - schema = colander.MappingSchema(colander.Mapping(), thingnode) - result = schema.clone() - self.assertFalse(result.children[0] is thingnode) - self.assertEqual(result.children[0].name, thingnode.name) - - -class TestSequenceSchema(unittest.TestCase): - def test_succeed(self): - - _inner = colander.SchemaNode(colander.String()) - - class MySchema(colander.SequenceSchema): - inner = _inner - - node = MySchema() - self.assertTrue(hasattr(node, '_order')) - self.assertTrue(isinstance(node, colander.SchemaNode)) - self.assertEqual(node.typ.__class__, colander.Sequence) - self.assertEqual(node.children[0], _inner) - - def test_fail_toomany(self): - - thingnode = colander.SchemaNode(colander.String()) - thingnode2 = colander.SchemaNode(colander.String()) - - class MySchema(colander.SequenceSchema): - thing = thingnode - thing2 = thingnode2 - - e = invalid_exc(MySchema) - self.assertEqual( - e.msg, 'Sequence schemas must have exactly one child node' - ) - - def test_fail_toofew(self): - class MySchema(colander.SequenceSchema): - pass - - e = invalid_exc(MySchema) - self.assertEqual( - e.msg, 'Sequence schemas must have exactly one child node' - ) - - def test_imperative_with_implicit_schema_type(self): - - node = colander.SchemaNode(colander.String()) - schema = colander.SequenceSchema(node) - self.assertEqual(schema.schema_type, colander.Sequence) - self.assertEqual(schema.children[0], node) - - def test_deserialize_missing_drop(self): - import colander - - class MySchema(colander.SequenceSchema): - a = colander.SchemaNode( - colander.String(), default='abc', missing=colander.drop - ) - - node = MySchema() - result = node.deserialize([None]) - self.assertEqual(result, []) - result = node.deserialize([colander.null]) - self.assertEqual(result, []) - result = node.deserialize(['']) - self.assertEqual(result, []) - - def test_deserialize_missing_value(self): - import colander - - class MySchema(colander.SequenceSchema): - a = colander.SchemaNode( - colander.String(), default=colander.drop, missing='abc' - ) - - node = MySchema() - result = node.deserialize([None]) - self.assertEqual(result, ['abc']) - result = node.deserialize([colander.null]) - self.assertEqual(result, ['abc']) - - def test_serialize_default_drop(self): - import colander - - class MySchema(colander.SequenceSchema): - a = colander.SchemaNode( - colander.String(), default=colander.drop, missing='abc' - ) - - node = MySchema() - result = node.serialize([]) - self.assertEqual(result, []) - result = node.serialize([colander.null]) - self.assertEqual(result, []) - - def test_serialize_default_value(self): - import colander - - class MySchema(colander.SequenceSchema): - a = colander.SchemaNode( - colander.String(), default='abc', missing=colander.drop - ) - - node = MySchema() - result = node.serialize([]) - self.assertEqual(result, []) - result = node.serialize([colander.null]) - self.assertEqual(result, ['abc']) - - def test_clone_with_sequence_schema(self): - - thingnode = colander.SchemaNode(colander.String(), name='foo') - schema = colander.SequenceSchema(colander.Sequence(), thingnode) - clone = schema.clone() - self.assertIsNot(schema, clone) - self.assertEqual(schema.name, clone.name) - self.assertEqual(len(schema.children), len(clone.children)) - self.assertIsNot(schema.children[0], clone.children[0]) - self.assertEqual(schema.children[0].name, clone.children[0].name) - self.assertFalse(clone.children[0] is thingnode) - self.assertEqual(clone.children[0].name, thingnode.name) - - -class TestTupleSchema(unittest.TestCase): - def test_it(self): - class MySchema(colander.TupleSchema): - thing = colander.SchemaNode(colander.String()) - - node = MySchema() - self.assertTrue(hasattr(node, '_order')) - self.assertTrue(isinstance(node, colander.SchemaNode)) - self.assertEqual(node.typ.__class__, colander.Tuple) - self.assertEqual(node.children[0].typ.__class__, colander.String) - - def test_imperative_with_implicit_schema_type(self): - - node = colander.SchemaNode(colander.String()) - schema = colander.TupleSchema(node) - self.assertEqual(schema.schema_type, colander.Tuple) - self.assertEqual(schema.children[0], node) - - class FunctionalBase: def test_deserialize_ok(self): diff --git a/tests/test_declarative.py b/tests/test_declarative.py new file mode 100644 index 0000000..cf1c0ab --- /dev/null +++ b/tests/test_declarative.py @@ -0,0 +1,429 @@ +import pytest + +# SND: SchemaNode, declarative + + +def test_SND_validator_method(): + from colander import Integer + from colander import Invalid + from colander import SchemaNode + + class MyNode(SchemaNode): + schema_type = Integer + name = 'my' + + def validator(self, node, cstruct): + if cstruct > 10: + self.raise_invalid('Wrong') + + node = MyNode() + + with pytest.raises(Invalid): + node.deserialize(20) + + +def test_SND_missing(): + from colander import Integer + from colander import SchemaNode + from colander import null + + class MyNode(SchemaNode): + schema_type = Integer + name = 'my' + missing = 10 + + node = MyNode() + + result = node.deserialize(null) + + assert result == 10 + + +def test_SND_title(): + from colander import Integer + from colander import SchemaNode + + class MyNode(SchemaNode): + schema_type = Integer + title = 'some title' + + node = MyNode(name='my') + + assert node.title == 'some title' + + +def test_SND_title_overwritten_by_constructor(): + from colander import Integer + from colander import SchemaNode + + class MyNode(SchemaNode): + schema_type = Integer + title = 'some title' + + node = MyNode(name='my', title='other title') + + assert node.title == 'other title' + + +def test_SND_subelement_title_not_overwritten(): + from colander import Schema + from colander import SchemaNode + from colander import String + + class SampleNode(SchemaNode): + schema_type = String + title = 'Some Title' + + class SampleSchema(Schema): + node = SampleNode() + + schema = SampleSchema() + + assert 'Some Title' == schema.children[0].title + + +def test_SND_subclass_value_overridden_by_constructor(): + from colander import Integer + from colander import SchemaNode + from colander import null + + class MyNode(SchemaNode): + schema_type = Integer + name = 'my' + missing = 10 + + node = MyNode(missing=5) + + result = node.deserialize(null) + + assert result == 5 + + +def test_SND_method_values_can_rely_on_binding(): + from colander import Integer + from colander import SchemaNode + + class MyNode(SchemaNode): + schema_type = Integer + + def amethod(self): + return self.bindings['request'] + + node = MyNode() + + newnode = node.bind(request=14) + + assert newnode.amethod() == 14 + + +def test_SND_nonmethod_values_can_rely_on_after_bind(): + from colander import Integer + from colander import SchemaNode + from colander import null + + class MyNode(SchemaNode): + schema_type = Integer + + def after_bind(self, node, kw): + self.missing = kw['missing'] + + node = MyNode() + + newnode = node.bind(missing=10) + + assert newnode.deserialize(null) == 10 + + +def test_SND_deferred_methods_dont_quite_work_yet(): + from colander import Integer + from colander import SchemaNode + from colander import deferred + + class MyNode(SchemaNode): + schema_type = Integer + + @deferred + def avalidator(self, node, kw): # pragma: no cover + def _avalidator(node, cstruct): + self.raise_invalid('Foo') + + return _avalidator + + node = MyNode() + + with pytest.raises(TypeError): + node.bind() + + +def test_SND_nonmethod_values_can_be_deferred_though(): + from colander import Integer + from colander import SchemaNode + from colander import deferred + from colander import null + + def _missing(node, kw): + return 10 + + class MyNode(SchemaNode): + schema_type = Integer + missing = deferred(_missing) + + node = MyNode() + + bound_node = node.bind() + + assert bound_node.deserialize(null) == 10 + + +def test_SND_functions_can_be_deferred(): + from colander import Integer + from colander import SchemaNode + from colander import deferred + from colander import null + + class MyNode(SchemaNode): + schema_type = Integer + + @deferred + def missing(node, kw): + return 10 + + node = MyNode() + bound_node = node.bind() + assert bound_node.deserialize(null) == 10 + + +def test_SND_nodes_can_be_defered(): + from colander import MappingSchema + from colander import SchemaNode + from colander import String + from colander import deferred + + class MySchema(MappingSchema): + @deferred + def child(node, kw): + return SchemaNode(String(), missing='foo') + + node = MySchema() + bound_node = node.bind() + assert bound_node.deserialize({}) == {'child': 'foo'} + + +def test_SND_child_names_conflict_with_value_names_notused(): + from colander import Mapping + from colander import SchemaNode + from colander import String + + class MyNode(SchemaNode): + schema_type = Mapping + title = SchemaNode(String()) + + node = MyNode() + assert node.title == '' + + +def test_SND_child_names_conflict_with_value_names_used(): + from colander import Mapping + from colander import SchemaNode + from colander import String + + doesntmatter = SchemaNode(String(), name='name') + + class MyNode(SchemaNode): + schema_type = Mapping + name = 'fred' + wontmatter = doesntmatter + + node = MyNode() + assert node.name == 'fred' + assert node['name'] is doesntmatter + + +def test_SND_child_names_conflict_with_value_names_in_superclass(): + from colander import Mapping + from colander import SchemaNode + from colander import String + + doesntmatter = SchemaNode(String(), name='name') + _name = SchemaNode(String()) + + class MyNode(SchemaNode): + schema_type = Mapping + name = 'fred' + wontmatter = doesntmatter + + class AnotherNode(MyNode): + name = _name + + node = AnotherNode() + assert node.name == 'fred' + assert node['name'] is _name + + +def test_SND_child_names_conflict_with_value_names_in_subclass(): + from colander import Mapping + from colander import SchemaNode + from colander import String + + class MyNode(SchemaNode): + name = SchemaNode(String(), id='name') + + class AnotherNode(MyNode): + schema_type = Mapping + name = 'fred' + doesntmatter = SchemaNode(String(), name='name', id='doesntmatter') + + node = AnotherNode() + assert node.name == 'fred' + assert node['name'].id == 'doesntmatter' + + +def test_SND_name_reassignment(): + # see https://github.com/Pylons/colander/issues/39 + from colander import Integer + from colander import Schema + from colander import SchemaNode + from colander import Sequence + + class FnordSchema(Schema): + fnord = SchemaNode( + Sequence(), + SchemaNode(Integer(), name=''), + name="fnord[]", + ) + + schema = FnordSchema() + + assert schema['fnord[]'].name == 'fnord[]' + + +# MSI: MappingSchema inheritance + + +def test_MSI_single_inheritance(): + from colander import Boolean + from colander import Integer + from colander import Schema + from colander import SchemaNode + from colander import String + + class Friend(Schema): + rank = SchemaNode(Integer(), id='rank') + name = SchemaNode(String(), id='name') + serial = SchemaNode(Boolean(), id='serial2') + + class SpecialFriend(Friend): + iwannacomefirst = SchemaNode(Integer(), id='iwannacomefirst2') + + class SuperSpecialFriend(SpecialFriend): + iwannacomefirst = SchemaNode(String(), id='iwannacomefirst1') + another = SchemaNode(String(), id='another') + serial = SchemaNode(Integer(), id='serial1') + + inst = SuperSpecialFriend() + + result = [x.id for x in inst.children] + + assert result == ['rank', 'name', 'serial1', 'iwannacomefirst1', 'another'] + + +def test_MSI_single_inheritance_with_insert_before(): + from colander import Boolean + from colander import Integer + from colander import Schema + from colander import SchemaNode + from colander import String + + class Friend(Schema): + rank = SchemaNode(Integer(), id='rank') + name = SchemaNode(String(), id='name') + serial = SchemaNode(Boolean(), insert_before='name', id='serial2') + + class SpecialFriend(Friend): + iwannacomefirst = SchemaNode(Integer(), id='iwannacomefirst2') + + class SuperSpecialFriend(SpecialFriend): + iwannacomefirst = SchemaNode( + String(), insert_before='rank', id='iwannacomefirst1' + ) + another = SchemaNode(String(), id='another') + serial = SchemaNode(Integer(), id='serial1') + + inst = SuperSpecialFriend() + + result = [x.id for x in inst.children] + + assert result == ['iwannacomefirst1', 'rank', 'serial1', 'name', 'another'] + + +def test_MSI_single_inheritance2(): + from colander import Boolean + from colander import Integer + from colander import Schema + from colander import SchemaNode + from colander import String + + class One(Schema): + a = SchemaNode(Integer(), id='a1') + b = SchemaNode(Integer(), id='b1') + d = SchemaNode(Integer(), id='d1') + + class Two(One): + a = SchemaNode(String(), id='a2') + c = SchemaNode(String(), id='c2') + e = SchemaNode(String(), id='e2') + + class Three(Two): + b = SchemaNode(Boolean(), id='b3') + d = SchemaNode(Boolean(), id='d3') + f = SchemaNode(Boolean(), id='f3') + + inst = Three() + + result = [x.id for x in inst.children] + + assert len(result) == 6 + assert result, ['a2', 'b3', 'd3', 'c2', 'e2' == 'f3'] + + +def test_MSI_multiple_inheritance(): + from colander import Boolean + from colander import Integer + from colander import Schema + from colander import SchemaNode + from colander import String + + class One(Schema): + a = SchemaNode(Integer(), id='a1') + b = SchemaNode(Integer(), id='b1') + d = SchemaNode(Integer(), id='d1') + + class Two(Schema): + a = SchemaNode(String(), id='a2') + c = SchemaNode(String(), id='c2') + e = SchemaNode(String(), id='e2') + + class Three(Two, One): + b = SchemaNode(Boolean(), id='b3') + d = SchemaNode(Boolean(), id='d3') + f = SchemaNode(Boolean(), id='f3') + + inst = Three() + + result = [x.id for x in inst.children] + + assert len(result) == 6 + assert result, ['a2', 'b3', 'd3', 'c2', 'e2' == 'f3'] + + +def test_MSI_insert_before_failure(): + from colander import Integer + from colander import Schema + from colander import SchemaNode + + class One(Schema): + a = SchemaNode(Integer()) + b = SchemaNode(Integer(), insert_before='c') + + with pytest.raises(KeyError): + One() diff --git a/tests/test_schema.py b/tests/test_schema.py new file mode 100644 index 0000000..533082c --- /dev/null +++ b/tests/test_schema.py @@ -0,0 +1,1115 @@ +from unittest import mock + +import pytest +import translationstring + +MARKER = object() + + +@pytest.mark.parametrize( + "before, exists, exp_set, exp_add, exp_del, exp_before", + [ + (None, False, False, True, False, False), + (None, True, True, False, False, False), + ("test_before", False, False, False, False, True), + ("test_before", True, False, False, True, True), + ], +) +def test__add_node_child( + before, + exists, + exp_set, + exp_add, + exp_del, + exp_before, +): + from colander import _add_node_child + + CHILD_NAME = "child" + + class Node(dict): + _added = None + _added_before = None + + def add(self, child): + self._added = child + + def add_before(self, insert_before, child): + self._added_before = (insert_before, child) + + node = Node() + child = mock.Mock() + child.name = CHILD_NAME + + child.insert_before = before + + if exists: + node[CHILD_NAME] = object() + + _add_node_child(node, child) + + if exp_set: + assert node[CHILD_NAME] is child + + if exp_add: + assert node._added is child + + if exp_del: + assert CHILD_NAME not in node + + if exp_before: + assert node._added_before == (before, child) + + +@pytest.mark.parametrize("child_names", [[], ["a"], ["a", "b"]]) +def test__add_node_children(child_names): + from colander import _add_node_children + + node = mock.Mock(spec_set=()) + children = [mock.Mock(name=name) for name in child_names] + + with mock.patch("colander._add_node_child") as anc: + _add_node_children(node, children) + + if len(children) > 0: + assert len(anc.call_args_list) == len(children) + + for i_child, child in enumerate(children): + called = anc.call_args_list[i_child] + assert called.args == (node, child) + + else: + anc.assert_not_called() + + +def test_SchemaNode_new_sets_order(): + from colander import SchemaNode + + # Contorted because 'itertools.counter' has no other way to peek + _, (before,) = SchemaNode._counter.__reduce__() + + node = SchemaNode(None) + + _, (after,) = SchemaNode._counter.__reduce__() + assert node._order == before + assert after == before + 1 + + +@pytest.mark.parametrize( + "arg, kw, exp_typ", + [ + ((), {}, "NotImplementedError"), + ((), {"typ": "Integer"}, "Integer"), + (("Integer",), {}, "Integer"), + ], +) +def test_SchemaNode_ctor_typ(arg, kw, exp_typ): + from colander import Integer + from colander import SchemaNode + + if exp_typ == "Integer": + exp_typ = Integer + + if arg and arg[0] == "Integer": + arg = (Integer,) + arg[1:] + + if kw.get("typ") == "Integer": + kw["typ"] = Integer + + if exp_typ == "NotImplementedError": + with pytest.raises(NotImplementedError): + SchemaNode(*arg, **kw) + else: + node = SchemaNode(*arg, **kw) + + assert node.typ is Integer + assert node.children == [] + + +def test_SchemaNode_ctor_children(): + from colander import SchemaNode + + children = [SchemaNode(None, name=f"child_{i}") for i in range(10)] + + with mock.patch("colander._add_node_children") as anc: + node = SchemaNode(None, *children) + + assert len(anc.call_args_list) == 2 + assert anc.call_args_list[0].args == (node, []) # __new__ + assert anc.call_args_list[1].args == (node, tuple(children)) # __init__ + + +@pytest.mark.parametrize( + "kw, exp_title, raw_set", + [ + ({}, "", False), + ({"title": "Foo"}, "Foo", True), + ({"name": "node_name"}, "Node Name", False), + ], +) +def test_SchemaNode_ctor_title(kw, exp_title, raw_set): + from colander import SchemaNode + from colander import required + + node = SchemaNode(None, **kw) + + assert node.title == exp_title + + if raw_set: + assert node.raw_title == exp_title + else: + assert node.raw_title is required + + +@pytest.mark.parametrize( + "kw", + [ + {"preparer": 1}, + {"validator": 1}, + {"default": 1}, + {"missing": 1}, + {"missing_msg": "msg"}, + {"name": "name"}, + {"description": "description"}, + {"widget": 1}, + {"after_bind": 1}, + {"bindings": 1}, + { + "preparer": 1, + "validator": 1, + "default": 1, + "missing": 1, + "missing_msg": "msg", + "name": "name", + "description": "description", + "widget": 1, + "after_bind": 1, + "bindings": 1, + }, + {"foo": 1}, + ], +) +def test_SchemaNode_ctor_other_kw(kw): + from colander import SchemaNode + + node = SchemaNode(None, **kw) + + for key, value in kw.items(): + assert getattr(node, key) == value + + +def _later(): + pass + + +@pytest.mark.parametrize( + "missing, expected", + [(None, True), ("deferred", True), (1, False)], +) +def test_SchemaNode_required(missing, expected): + from colander import SchemaNode + from colander import deferred + + if missing == "deferred": + missing = deferred(_later) + + if missing is None: + node = SchemaNode(None) + else: + node = SchemaNode(None, missing=missing) + + assert node.required == expected + + +@pytest.mark.parametrize( + "appstruct, exp_cstruct", + [(None, "14"), ("null", "14"), ("deferred", "null"), (1, "1")], +) +def test_SchemaNode_serialize(appstruct, exp_cstruct): + from colander import Integer + from colander import SchemaNode + from colander import deferred + from colander import null + + node = SchemaNode(Integer(), default=14) + + if appstruct == "null": + appstruct = null + elif appstruct == "deferred": + appstruct = deferred(_later) + + if exp_cstruct == "null": + exp_cstruct = null + + if appstruct is None: + result = node.serialize() + else: + result = node.serialize(appstruct) + + assert result == exp_cstruct + + +def test_SchemaNode_flatten(): + from colander import SchemaNode + + typ = mock.Mock(spec_set=["flatten"]) + node = SchemaNode(typ) + appstruct = {"foo": "Foo"} + + result = node.flatten(appstruct) + + assert result is typ.flatten.return_value + typ.flatten.assert_called_once_with(node, appstruct) + + +def test_SchemaNode_unflatten(): + from colander import SchemaNode + + typ = mock.Mock(spec_set=["unflatten"]) + node = SchemaNode(typ) + fstruct = {"foo": "Foo", "bar": "Bar"} + + result = node.unflatten(fstruct) + + assert result is typ.unflatten.return_value + typ.unflatten.assert_called_once_with(node, sorted(fstruct), fstruct) + + +def test_SchemaNode_set_value(): + from colander import SchemaNode + + typ = mock.Mock(spec_set=["set_value"]) + node = SchemaNode(typ) + appstruct = {"foo": "Foo", "bar": "Bar"} + + node.set_value(appstruct, "foo", "New Foo") + + typ.set_value.assert_called_once_with(node, appstruct, "foo", "New Foo") + + +def test_SchemaNode_get_value(): + from colander import SchemaNode + + typ = mock.Mock(spec_set=["get_value"]) + node = SchemaNode(typ) + appstruct = {"foo": "Foo", "bar": "Bar"} + + result = node.get_value(appstruct, "foo") + + assert result is typ.get_value.return_value + typ.get_value.assert_called_once_with(node, appstruct, "foo") + + +@pytest.mark.parametrize( + "missing, cstruct, exp_appstruct", + [ + (None, "1", 1), + (14, None, 14), + (14, "null", 14), + (None, "null", "Invalid"), + (None, None, "Invalid"), + ("deferred", "null", "Invalid"), + ("deferred", None, "Invalid"), + ("null", "null", "null"), + ], +) +def test_SchemaNode_deserialize_wo_preparer_wo_validator( + missing, cstruct, exp_appstruct +): + from colander import Integer + from colander import Invalid + from colander import SchemaNode + from colander import deferred + from colander import null + + if missing == "null": + missing = null + elif missing == "deferred": + missing = deferred(_later) + + if missing is None: + node = SchemaNode(Integer(), name="must") + else: + node = SchemaNode(Integer(), missing=missing) + + if cstruct == "null": + cstruct = null + + if exp_appstruct == "null": + exp_appstruct = null + + if exp_appstruct == "Invalid": + + with pytest.raises(Invalid) as exc: + if cstruct is None: + node.deserialize() + else: + node.deserialize(cstruct) + + invalid = exc.value + assert invalid.node is node + assert isinstance(invalid.msg, translationstring.TranslationString) + assert invalid.msg.default == node.missing_msg + + if missing is None: + assert invalid.msg.mapping["title"] == node.title + assert invalid.msg.mapping["name"] == node.name + else: + assert invalid.msg.mapping is None + + else: + if cstruct is None: + result = node.deserialize() + else: + result = node.deserialize(cstruct) + + assert result == exp_appstruct + + +def test_SchemaNode_deserialize_w_preparer(): + from colander import Integer + from colander import SchemaNode + + cstruct = "1" + preparer = mock.Mock(spec_set=(), return_value=23) + node = SchemaNode(Integer(), preparer=preparer) + + result = node.deserialize(cstruct) + + assert result == 23 + preparer.assert_called_once_with(1) + + +def test_SchemaNode_deserialize_w_preparers(): + from colander import Integer + from colander import SchemaNode + + cstruct = "1" + prep_1 = mock.Mock(spec_set=(), return_value=45) + prep_2 = mock.Mock(spec_set=(), return_value=67) + prep_3 = mock.Mock(spec_set=(), return_value=89) + node = SchemaNode(Integer(), preparer=[prep_1, prep_2, prep_3]) + + result = node.deserialize(cstruct) + + assert result == 89 + prep_1.assert_called_once_with(1) + prep_2.assert_called_once_with(45) + prep_3.assert_called_once_with(67) + + +def test_SchemaNode_deserialize_w_validator_deferred(): + from colander import Integer + from colander import SchemaNode + from colander import UnboundDeferredError + from colander import deferred + + cstruct = "1" + validator = deferred(_later) + node = SchemaNode(Integer(), validator=validator) + + with pytest.raises(UnboundDeferredError): + node.deserialize(cstruct) + + +def test_SchemaNode_deserialize_w_validator_raises(): + from colander import Integer + from colander import SchemaNode + + cstruct = "1" + validator = mock.Mock(spec_set=(), side_effect=ValueError("testing")) + node = SchemaNode(Integer(), validator=validator) + + with pytest.raises(ValueError): + node.deserialize(cstruct) + + validator.assert_called_once_with(node, 1) + + +def test_SchemaNode_deserialize_w_validator_no_raise(): + from colander import Integer + from colander import SchemaNode + + cstruct = "1" + validator = mock.Mock(spec_set=()) + node = SchemaNode(Integer(), validator=validator) + + result = node.deserialize(cstruct) + + assert result == 1 + validator.assert_called_once_with(node, 1) + + +def test_SchemaNode_add(): + from colander import SchemaNode + + pre_1 = SchemaNode(None, name="pre_1") + pre_2 = SchemaNode(None, name="pre_2") + child = SchemaNode(None, name="child") + node = SchemaNode(None, pre_1, pre_2) + + node.add(child) + + assert node.children == [pre_1, pre_2, child] + + +def test_SchemaNode_insert(): + from colander import SchemaNode + + pre_1 = SchemaNode(None, name="pre_1") + pre_2 = SchemaNode(None, name="pre_2") + child = SchemaNode(None, name="child") + node = SchemaNode(None, pre_1, pre_2) + + node.insert(1, child) + + assert node.children == [pre_1, child, pre_2] + + +def test_SchemaNode_add_before_miss(): + from colander import SchemaNode + + child = SchemaNode(None, name="child") + node = SchemaNode(None) + + with pytest.raises(KeyError): + node.add_before("pre_2", child) + + +def test_SchemaNode_add_before_hit(): + from colander import SchemaNode + + pre_1 = SchemaNode(None, name="pre_1") + pre_2 = SchemaNode(None, name="pre_2") + child = SchemaNode(None, name="child") + node = SchemaNode(None, pre_1, pre_2) + + node.add_before("pre_2", child) + + assert node.children == [pre_1, child, pre_2] + + +@pytest.mark.parametrize( + "name, default, expected", + [ + ("c_1", None, "c_1"), + ("c_2", MARKER, "c_2"), + ("nonesuch", None, None), + ("nonesuch", MARKER, MARKER), + ], +) +def test_SchemaNode_get(name, default, expected): + from colander import SchemaNode + + c_1 = SchemaNode(None, name="c_1") + c_2 = SchemaNode(None, name="c_2") + node = SchemaNode(None, c_1, c_2) + + if expected == "c_1": + expected = c_1 + elif expected == "c_2": + expected = c_2 + + if default is None: + result = node.get(name) + else: + result = node.get(name, default) + + assert result == expected + + +@pytest.mark.parametrize( + "kw", + [ + {"preparer": 1}, + {"validator": 1}, + {"default": 1}, + {"missing": 1}, + {"missing_msg": "msg"}, + {"name": "name"}, + {"description": "description"}, + {"widget": 1}, + {"after_bind": 1}, + {"bindings": 1}, + { + "preparer": 1, + "validator": 1, + "default": 1, + "missing": 1, + "missing_msg": "msg", + "name": "name", + "description": "description", + "widget": 1, + "after_bind": 1, + "bindings": 1, + }, + {"foo": 1}, + ], +) +def test_SchemaNode_clone_wo_children(kw): + from colander import Integer + from colander import SchemaNode + + typ = Integer() + node = SchemaNode(typ, **kw) + + cloned = node.clone() + + assert cloned is not node + assert cloned.typ is typ + + for key, value in kw.items(): + assert getattr(cloned, key) == value + + +def test_SchemaNode_clone_w_children(): + from colander import Integer + from colander import SchemaNode + + c_typ = Integer() + c_1 = SchemaNode(c_typ, name="c_1") + c_2 = SchemaNode(c_typ, name="c_2") + c_3 = SchemaNode(c_typ, name="c_3") + node = SchemaNode(None, c_1, c_2, c_3) + + cloned = node.clone() + + assert cloned is not node + assert cloned.typ is None + + assert len(cloned.children) == len(node.children) + for c_cloned, c_pre in zip(cloned.children, node.children): + assert c_cloned is not c_pre + assert c_cloned.name == c_pre.name + + +@pytest.mark.parametrize( + "ctor_kw, after_bind", + [ + ({"missing": "deferred"}, False), + ({"default": "deferred"}, False), + ({"validator": "deferred"}, False), + ( + {"missing": 1, "default": 2, "validator": 3, "foo": "deferred"}, + True, + ), + ], +) +def test_SchemaNode_bind_wo_children(ctor_kw, after_bind): + from colander import Integer + from colander import SchemaNode + from colander import deferred + + deferreds = {} + + for key, value in list(ctor_kw.items()): + if value == "deferred": + deferreds[key] = func = mock.Mock(name=key, spec_set=()) + ctor_kw[key] = deferred(func) + + if after_bind: + ctor_kw["after_bind"] = mock.Mock(spec_sec=()) + + typ = Integer() + node = SchemaNode(typ, **ctor_kw) + bind_kw = {"bar": "Bar"} + + bound = node.bind(**bind_kw) + + assert bound is not node + assert bound.typ is typ + assert bound.bindings == bind_kw + + for key in ctor_kw: + value = getattr(bound, key) + + if key in deferreds: + func = deferreds[key] + assert value is func.return_value + func.assert_called_once_with(bound, bind_kw) + else: + assert getattr(bound, key) == value + + if after_bind: + ctor_kw["after_bind"].assert_called_once_with(bound, bind_kw) + + +def test_SchemaNode_bind_w_children(): + from colander import Integer + from colander import SchemaNode + + after_bound = [] + + def after_bind(node, kw): + after_bound.append((node, kw)) + + c_typ = Integer() + c_1 = SchemaNode(c_typ, name="c_1", after_bind=after_bind) + c_2 = SchemaNode(c_typ, name="c_2", after_bind=after_bind) + c_3 = SchemaNode(c_typ, name="c_3", after_bind=after_bind) + node = SchemaNode(None, c_1, c_2, c_3, after_bind=after_bind) + bind_kw = {"bar": "Bar"} + + bound = node.bind(**bind_kw) + assert after_bound[-1] == (bound, bind_kw) + + assert len(after_bound) == len(node.children) + 1 + assert len(bound.children) == len(node.children) + + for i_c, (c_bound, c_pre) in enumerate(zip(bound.children, node.children)): + assert c_bound is not c_pre + assert c_bound.name == c_pre.name + assert after_bound[i_c] == (c_bound, bind_kw) + + +@pytest.mark.parametrize("has_name", [False, True]) +@pytest.mark.parametrize("has_title", [False, True]) +def test_SchemaNode_bind_w_deferred_returning_child(has_name, has_title): + from colander import Integer + from colander import SchemaNode + from colander import deferred + + def _make_child_node(node, kw): + return SchemaNode(Integer(), **kw) + + typ = Integer() + node = SchemaNode(typ, child=deferred(_make_child_node)) + assert len(node.children) == 0 + + bind_kw = {} + + if has_name: + bind_kw["name"] = "child_name" + + if has_title: + bind_kw["title"] = "Child Title" + + bound = node.bind(**bind_kw) + + assert len(bound.children) == 1 + child = bound.children[0] + + if has_name: + assert child.name == "child_name" + else: + assert child.name == "child" + + if has_title: + assert child.title == "Child Title" + else: + assert child.title == "Child" + + +def test_SchemaNode_cstruct_children_typ_wo_c_c(): + import warnings + + from colander import SchemaNode + + node = SchemaNode(None) + cstruct = {} + + with warnings.catch_warnings(record=True) as warned: + result = node.cstruct_children(cstruct) + + assert result == [] + assert len(warned) == 1 + warning = warned[0] + assert warning.category is DeprecationWarning + + +def test_SchemaNode_cstruct_children_hit(): + from colander import SchemaNode + + typ = mock.Mock(spec_set=["cstruct_children"]) + node = SchemaNode(typ=typ) + cstruct = {} + + result = node.cstruct_children(cstruct) + + assert result is typ.cstruct_children.return_value + typ.cstruct_children.assert_called_once_with(node, cstruct) + + +def test_SchemaNode___delitem___miss(): + from colander import SchemaNode + + node = SchemaNode(None) + + with pytest.raises(KeyError): + del node["nonesuch"] + + +def test_SchemaNode___delitem___hit(): + from colander import SchemaNode + + node = SchemaNode(None) + child = SchemaNode(None, name="child_name") + node.add(child) + + del node["child_name"] + assert len(node.children) == 0 + + +def test_SchemaNode___getitem___miss(): + from colander import SchemaNode + + node = SchemaNode(None) + + with pytest.raises(KeyError): + node["nonesuch"] + + +def test_SchemaNode___getitem___hit(): + from colander import SchemaNode + + node = SchemaNode(None) + child = SchemaNode(None, name="child_name") + node.add(child) + + result = node["child_name"] + + assert result is child + + +def test_SchemaNode___setitem___new(): + from colander import SchemaNode + + node = SchemaNode(None) + child = SchemaNode(None) + + node["child_name"] = child + + assert len(node.children) == 1 + assert child.name == "child_name" + + +def test_SchemaNode___setitem___replacement(): + from colander import SchemaNode + + node = SchemaNode(None) + before = SchemaNode(None, name="child_name") + node.add(before) + after = SchemaNode(None) + + node["child_name"] = after + + assert len(node.children) == 1 + assert node.children[0] is after + assert after.name == "child_name" + + +def test_SchemaNode___iter___empty(): + from colander import SchemaNode + + node = SchemaNode(None) + + result = list(node) + + assert result == [] + + +def test_SchemaNode___iter___nonempty(): + from colander import SchemaNode + + c_1 = SchemaNode(None, name="child_1") + c_2 = SchemaNode(None, name="child_2") + c_3 = SchemaNode(None, name="child_3") + node = SchemaNode(None, c_1, c_2, c_3) + + result = list(node) + + assert result == [c_1, c_2, c_3] + + +def test_SchemaNode___contains___miss(): + from colander import SchemaNode + + node = SchemaNode(None) + + result = "nonesuch" in node + + assert not result + + +def test_SchemaNode___contains___hit(): + from colander import SchemaNode + + child = SchemaNode(None, name="child_name") + node = SchemaNode(None, child) + + result = "child_name" in node + + assert result + + +def test_SchemaNode__raise_invalid_wo_node(): + from colander import Invalid + from colander import SchemaNode + + node = SchemaNode(None) + + with pytest.raises(Invalid) as exc: + node.raise_invalid("testing") + + invalid = exc.value + assert invalid.node is node + assert invalid.msg == "testing" + + +def test_SchemaNode__raise_invalid_w_node(): + from colander import Invalid + from colander import SchemaNode + + node = SchemaNode(None) + other = SchemaNode(None) + + with pytest.raises(Invalid) as exc: + node.raise_invalid("testing", node=other) + + invalid = exc.value + assert invalid.node is other + assert invalid.msg == "testing" + + +def test_Schema_ctor_wo_children(): + from colander import Mapping + from colander import Schema + + schema = Schema() + + assert isinstance(schema.typ, Mapping) + assert schema.children == [] + + +def test_Schema_ctor_w_children(): + from colander import Mapping + from colander import Schema + from colander import SchemaNode + + c_1 = SchemaNode(None, name="c_1") + c_2 = SchemaNode(None, name="c_2") + + schema = Schema(c_1, c_2) + + assert isinstance(schema.typ, Mapping) + assert schema.children == [c_1, c_2] + assert schema["c_1"] == c_1 + assert schema["c_2"] == c_2 + + +def test_MappingSchema_ctor_wo_children(): + from colander import Mapping + from colander import MappingSchema + + schema = MappingSchema() + + assert isinstance(schema.typ, Mapping) + assert schema.children == [] + + +def test_MappingSchema_ctor_w_children(): + from colander import Mapping + from colander import MappingSchema + from colander import SchemaNode + + c_1 = SchemaNode(None, name="c_1") + c_2 = SchemaNode(None, name="c_2") + + schema = MappingSchema(c_1, c_2) + + assert isinstance(schema.typ, Mapping) + assert schema.children == [c_1, c_2] + assert schema["c_1"] == c_1 + assert schema["c_2"] == c_2 + + +def test_TupleSchema_ctor_wo_children(): + from colander import Tuple + from colander import TupleSchema + + schema = TupleSchema() + + assert isinstance(schema.typ, Tuple) + assert schema.children == [] + + +def test_TupleSchema_ctor_w_children(): + from colander import SchemaNode + from colander import Tuple + from colander import TupleSchema + + c_1 = SchemaNode(None, name="c_1") + c_2 = SchemaNode(None, name="c_2") + + schema = TupleSchema(c_1, c_2) + + assert isinstance(schema.typ, Tuple) + assert schema.children == [c_1, c_2] + assert schema["c_1"] == c_1 + assert schema["c_2"] == c_2 + + +def test_SequenceSchema_ctor_w_one_child(): + from colander import SchemaNode + from colander import Sequence + from colander import SequenceSchema + + c_1 = SchemaNode(None, name="c_1") + + schema = SequenceSchema(c_1) + + assert isinstance(schema.typ, Sequence) + assert schema.children == [c_1] + + +def test_SequenceSchema_ctor_wo_children(): + from colander import Invalid + from colander import SequenceSchema + + with pytest.raises(Invalid) as exc: + SequenceSchema() + + invalid = exc.value + assert invalid.msg == 'Sequence schemas must have exactly one child node' + + +def test_SequenceSchema_ctor_w_surplus_children(): + from colander import Invalid + from colander import SchemaNode + from colander import SequenceSchema + + c_1 = SchemaNode(None, name="c_1") + c_2 = SchemaNode(None, name="c_2") + + with pytest.raises(Invalid) as exc: + SequenceSchema(c_1, c_2) + + invalid = exc.value + assert invalid.msg == 'Sequence schemas must have exactly one child node' + + +def test_SequenceSchema_clone(): + from colander import Integer + from colander import SchemaNode + from colander import SequenceSchema + + child = SchemaNode(Integer(), name="child") + schema = SequenceSchema(child) + + cloned = schema.clone() + + assert cloned is not schema + assert cloned.typ is schema.typ + + assert len(cloned.children) == 1 + c_cloned = cloned.children[0] + assert c_cloned is not child + assert c_cloned.name == child.name + + +def test_deferred_ctor(): + from colander import deferred + + def wrapped(node, kw): + """Can you hear me now?""" + pass # pragma: no cover + + inst = deferred(wrapped) + + assert inst.wrapped is wrapped + assert inst.__doc__ == 'Can you hear me now?' + assert inst.__name__ == 'wrapped' + + +def test_deferred___call__(): + from colander import deferred + + n = object() + k = object() + + def wrapped(node, kw): + assert node == n + assert kw == k + return 'abc' + + inst = deferred(wrapped) + + result = inst(n, k) + + assert result == 'abc' + + +def test_deferred_w_callable_instance_no_name(): + from colander import deferred + + class Wrapped: + """CLASS""" + + def __call__(self, node, kw): + """METHOD""" + pass # pragma: no cover + + wrapped = Wrapped() + inst = deferred(wrapped) + + assert inst.__doc__ == wrapped.__doc__ + + assert '__name__' not in inst.__dict__ + + +def test_deferred_w_callable_instance_no_name_or_doc(): + from colander import deferred + + class Wrapped: + def __call__(self, node, kw): + pass # pragma: no cover + + wrapped = Wrapped() + + inst = deferred(wrapped) + + assert inst.__doc__ is None + assert '__name__' not in inst.__dict__ + + +def test_deferred_w_insert_before(): + from colander import Integer + from colander import Schema + from colander import SchemaNode + from colander import deferred + + def wrapped_func(node, kw): + return SchemaNode(Integer(), insert_before='name2') + + class MySchema(Schema): + name2 = SchemaNode(Integer()) + name3 = SchemaNode(Integer()) + name1 = deferred(wrapped_func) + name4 = SchemaNode(Integer()) + + unbound = MySchema() + + unbound_names = [x.name for x in unbound.children] + + # Child not present before binding + assert unbound_names == ['name2', 'name3', 'name4'] + + bound = unbound.bind() + + bound_names = [x.name for x in bound.children] + + assert bound_names == ['name1', 'name2', 'name3', 'name4'] + + +@pytest.mark.parametrize("args, kw", [((), {}), (("foo",), {"bar": "Bar"})]) +def test_instantiate(args, kw): + from colander import instantiate + + class_ = mock.Mock(spec_set=()) + + decorator = instantiate(*args, **kw) + + result = decorator(class_) + + assert result is class_.return_value + class_.assert_called_once_with(*args, **kw) From 1f3f3f5d0ae68b98f5af8d15c0c8f5f8371152d4 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 15 Aug 2024 13:45:54 -0400 Subject: [PATCH 14/16] tests: split functests into a separate module --- tests/test_colander.py | 415 ----------------------------------- tests/test_functional.py | 461 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 461 insertions(+), 415 deletions(-) create mode 100644 tests/test_functional.py diff --git a/tests/test_colander.py b/tests/test_colander.py index 5cc8e72..d9bf052 100644 --- a/tests/test_colander.py +++ b/tests/test_colander.py @@ -1,8 +1,5 @@ import unittest -import colander -import tests - def invalid_exc(func, *arg, **kw): from colander import Invalid @@ -15,418 +12,6 @@ def invalid_exc(func, *arg, **kw): raise AssertionError('Invalid not raised') # pragma: no cover -class FunctionalBase: - def test_deserialize_ok(self): - - data = { - 'int': '10', - 'ob': 'tests', - 'seq': [('1', 's'), ('2', 's'), ('3', 's'), ('4', 's')], - 'seq2': [{'key': '1', 'key2': '2'}, {'key': '3', 'key2': '4'}], - 'tup': ('1', 's'), - } - schema = self._makeSchema() - result = schema.deserialize(data) - self.assertEqual(result['int'], 10) - self.assertEqual(result['ob'], tests) - self.assertEqual( - result['seq'], [(1, 's'), (2, 's'), (3, 's'), (4, 's')] - ) - self.assertEqual( - result['seq2'], [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}] - ) - self.assertEqual(result['tup'], (1, 's')) - - def test_flatten_ok(self): - - appstruct = { - 'int': 10, - 'ob': tests, - 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], - 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], - 'tup': (1, 's'), - } - schema = self._makeSchema() - result = schema.flatten(appstruct) - - expected = { - 'schema.seq.2.tupstring': 's', - 'schema.seq2.0.key2': 2, - 'schema.ob': tests, - 'schema.seq2.1.key2': 4, - 'schema.seq.1.tupstring': 's', - 'schema.seq2.0.key': 1, - 'schema.seq.1.tupint': 2, - 'schema.seq.0.tupstring': 's', - 'schema.seq.3.tupstring': 's', - 'schema.seq.3.tupint': 4, - 'schema.seq2.1.key': 3, - 'schema.int': 10, - 'schema.seq.0.tupint': 1, - 'schema.tup.tupint': 1, - 'schema.tup.tupstring': 's', - 'schema.seq.2.tupint': 3, - } - - for k, v in expected.items(): - self.assertEqual(result[k], v) - for k, v in result.items(): - self.assertEqual(expected[k], v) - - def test_flatten_mapping_has_no_name(self): - - appstruct = { - 'int': 10, - 'ob': tests, - 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], - 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], - 'tup': (1, 's'), - } - schema = self._makeSchema(name='') - result = schema.flatten(appstruct) - - expected = { - 'seq.2.tupstring': 's', - 'seq2.0.key2': 2, - 'ob': tests, - 'seq2.1.key2': 4, - 'seq.1.tupstring': 's', - 'seq2.0.key': 1, - 'seq.1.tupint': 2, - 'seq.0.tupstring': 's', - 'seq.3.tupstring': 's', - 'seq.3.tupint': 4, - 'seq2.1.key': 3, - 'int': 10, - 'seq.0.tupint': 1, - 'tup.tupint': 1, - 'tup.tupstring': 's', - 'seq.2.tupint': 3, - } - - for k, v in expected.items(): - self.assertEqual(result[k], v) - for k, v in result.items(): - self.assertEqual(expected[k], v) - - def test_unflatten_ok(self): - - fstruct = { - 'schema.seq.2.tupstring': 's', - 'schema.seq2.0.key2': 2, - 'schema.ob': tests, - 'schema.seq2.1.key2': 4, - 'schema.seq.1.tupstring': 's', - 'schema.seq2.0.key': 1, - 'schema.seq.1.tupint': 2, - 'schema.seq.0.tupstring': 's', - 'schema.seq.3.tupstring': 's', - 'schema.seq.3.tupint': 4, - 'schema.seq2.1.key': 3, - 'schema.int': 10, - 'schema.seq.0.tupint': 1, - 'schema.tup.tupint': 1, - 'schema.tup.tupstring': 's', - 'schema.seq.2.tupint': 3, - } - schema = self._makeSchema() - result = schema.unflatten(fstruct) - - expected = { - 'int': 10, - 'ob': tests, - 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], - 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], - 'tup': (1, 's'), - } - - for k, v in expected.items(): - self.assertEqual(result[k], v) - for k, v in result.items(): - self.assertEqual(expected[k], v) - - def test_unflatten_mapping_no_name(self): - - fstruct = { - 'seq.2.tupstring': 's', - 'seq2.0.key2': 2, - 'ob': tests, - 'seq2.1.key2': 4, - 'seq.1.tupstring': 's', - 'seq2.0.key': 1, - 'seq.1.tupint': 2, - 'seq.0.tupstring': 's', - 'seq.3.tupstring': 's', - 'seq.3.tupint': 4, - 'seq2.1.key': 3, - 'int': 10, - 'seq.0.tupint': 1, - 'tup.tupint': 1, - 'tup.tupstring': 's', - 'seq.2.tupint': 3, - } - schema = self._makeSchema(name='') - result = schema.unflatten(fstruct) - - expected = { - 'int': 10, - 'ob': tests, - 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], - 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], - 'tup': (1, 's'), - } - - for k, v in expected.items(): - self.assertEqual(result[k], v) - for k, v in result.items(): - self.assertEqual(expected[k], v) - - def test_flatten_unflatten_roundtrip(self): - - appstruct = { - 'int': 10, - 'ob': tests, - 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], - 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], - 'tup': (1, 's'), - } - schema = self._makeSchema(name='') - self.assertEqual( - schema.unflatten(schema.flatten(appstruct)), appstruct - ) - - def test_set_value(self): - - appstruct = { - 'int': 10, - 'ob': tests, - 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], - 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], - 'tup': (1, 's'), - } - schema = self._makeSchema() - schema.set_value(appstruct, 'seq2.1.key', 6) - self.assertEqual(appstruct['seq2'][1], {'key': 6, 'key2': 4}) - - def test_get_value(self): - - appstruct = { - 'int': 10, - 'ob': tests, - 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], - 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], - 'tup': (1, 's'), - } - schema = self._makeSchema() - self.assertEqual( - schema.get_value(appstruct, 'seq'), - [(1, 's'), (2, 's'), (3, 's'), (4, 's')], - ) - self.assertEqual(schema.get_value(appstruct, 'seq2.1.key'), 3) - - def test_invalid_asdict(self): - expected = { - 'schema.int': '20 is greater than maximum value 10', - 'schema.ob': 'The dotted name "no.way.this.exists" ' - 'cannot be imported', - 'schema.seq.0.0': '"q" is not a number', - 'schema.seq.1.0': '"w" is not a number', - 'schema.seq.2.0': '"e" is not a number', - 'schema.seq.3.0': '"r" is not a number', - 'schema.seq2.0.key': '"t" is not a number', - 'schema.seq2.0.key2': '"y" is not a number', - 'schema.seq2.1.key': '"u" is not a number', - 'schema.seq2.1.key2': '"i" is not a number', - 'schema.tup.0': '"s" is not a number', - } - data = { - 'int': '20', - 'ob': 'no.way.this.exists', - 'seq': [('q', 's'), ('w', 's'), ('e', 's'), ('r', 's')], - 'seq2': [{'key': 't', 'key2': 'y'}, {'key': 'u', 'key2': 'i'}], - 'tup': ('s', 's'), - } - schema = self._makeSchema() - e = invalid_exc(schema.deserialize, data) - errors = e.asdict() - self.assertEqual(errors, expected) - - def test_invalid_asdict_translation_callback(self): - from translationstring import TranslationString - - expected = { - 'schema.int': 'translated', - 'schema.ob': 'translated', - 'schema.seq.0.0': 'translated', - 'schema.seq.1.0': 'translated', - 'schema.seq.2.0': 'translated', - 'schema.seq.3.0': 'translated', - 'schema.seq2.0.key': 'translated', - 'schema.seq2.0.key2': 'translated', - 'schema.seq2.1.key': 'translated', - 'schema.seq2.1.key2': 'translated', - 'schema.tup.0': 'translated', - } - data = { - 'int': '20', - 'ob': 'no.way.this.exists', - 'seq': [('q', 's'), ('w', 's'), ('e', 's'), ('r', 's')], - 'seq2': [{'key': 't', 'key2': 'y'}, {'key': 'u', 'key2': 'i'}], - 'tup': ('s', 's'), - } - schema = self._makeSchema() - e = invalid_exc(schema.deserialize, data) - - def translation_function(string): - return TranslationString('translated') - - errors = e.asdict(translate=translation_function) - self.assertEqual(errors, expected) - - -class TestImperative(unittest.TestCase, FunctionalBase): - def _makeSchema(self, name='schema'): - - integer = colander.SchemaNode( - colander.Integer(), name='int', validator=colander.Range(0, 10) - ) - - ob = colander.SchemaNode( - colander.GlobalObject(package=colander), name='ob' - ) - - tup = colander.SchemaNode( - colander.Tuple(), - colander.SchemaNode(colander.Integer(), name='tupint'), - colander.SchemaNode(colander.String(), name='tupstring'), - name='tup', - ) - - seq = colander.SchemaNode(colander.Sequence(), tup, name='seq') - - seq2 = colander.SchemaNode( - colander.Sequence(), - colander.SchemaNode( - colander.Mapping(), - colander.SchemaNode(colander.Integer(), name='key'), - colander.SchemaNode(colander.Integer(), name='key2'), - name='mapping', - ), - name='seq2', - ) - - schema = colander.SchemaNode( - colander.Mapping(), integer, ob, tup, seq, seq2, name=name - ) - - return schema - - -class TestDeclarative(unittest.TestCase, FunctionalBase): - def _makeSchema(self, name='schema'): - class TupleSchema(colander.TupleSchema): - tupint = colander.SchemaNode(colander.Int()) - tupstring = colander.SchemaNode(colander.String()) - - class MappingSchema(colander.MappingSchema): - key = colander.SchemaNode(colander.Int()) - key2 = colander.SchemaNode(colander.Int()) - - class SequenceOne(colander.SequenceSchema): - tup = TupleSchema() - - class SequenceTwo(colander.SequenceSchema): - mapping = MappingSchema() - - class MainSchema(colander.MappingSchema): - int = colander.SchemaNode( - colander.Int(), validator=colander.Range(0, 10) - ) - ob = colander.SchemaNode(colander.GlobalObject(package=colander)) - seq = SequenceOne() - tup = TupleSchema() - seq2 = SequenceTwo() - - schema = MainSchema(name=name) - return schema - - -class TestUltraDeclarative(unittest.TestCase, FunctionalBase): - def _makeSchema(self, name='schema'): - class IntSchema(colander.SchemaNode): - schema_type = colander.Int - - class StringSchema(colander.SchemaNode): - schema_type = colander.String - - class TupleSchema(colander.TupleSchema): - tupint = IntSchema() - tupstring = StringSchema() - - class MappingSchema(colander.MappingSchema): - key = IntSchema() - key2 = IntSchema() - - class SequenceOne(colander.SequenceSchema): - tup = TupleSchema() - - class SequenceTwo(colander.SequenceSchema): - mapping = MappingSchema() - - class IntSchemaRanged(IntSchema): - validator = colander.Range(0, 10) - - class GlobalObjectSchema(colander.SchemaNode): - def schema_type(self): - return colander.GlobalObject(package=colander) - - class MainSchema(colander.MappingSchema): - int = IntSchemaRanged() - ob = GlobalObjectSchema() - seq = SequenceOne() - tup = TupleSchema() - seq2 = SequenceTwo() - - MainSchema.name = name - - schema = MainSchema() - return schema - - -class TestDeclarativeWithInstantiate(unittest.TestCase, FunctionalBase): - def _makeSchema(self, name='schema'): - - # an unlikely usage, but goes to test passing - # parameters to instantiation works - @colander.instantiate(name=name) - class schema(colander.MappingSchema): - int = colander.SchemaNode( - colander.Int(), validator=colander.Range(0, 10) - ) - ob = colander.SchemaNode(colander.GlobalObject(package=colander)) - - @colander.instantiate() - class seq(colander.SequenceSchema): - @colander.instantiate() - class tup(colander.TupleSchema): - tupint = colander.SchemaNode(colander.Int()) - tupstring = colander.SchemaNode(colander.String()) - - @colander.instantiate() - class tup(colander.TupleSchema): - tupint = colander.SchemaNode(colander.Int()) - tupstring = colander.SchemaNode(colander.String()) - - @colander.instantiate() - class seq2(colander.SequenceSchema): - @colander.instantiate() - class mapping(colander.MappingSchema): - key = colander.SchemaNode(colander.Int()) - key2 = colander.SchemaNode(colander.Int()) - - return schema - - class Test_null(unittest.TestCase): def test___nonzero__(self): from colander import null diff --git a/tests/test_functional.py b/tests/test_functional.py new file mode 100644 index 0000000..fa7b06a --- /dev/null +++ b/tests/test_functional.py @@ -0,0 +1,461 @@ +import pytest + + +def imperative(name='schema'): + import colander + + integer = colander.SchemaNode( + colander.Integer(), name='int', validator=colander.Range(0, 10) + ) + + ob = colander.SchemaNode( + colander.GlobalObject(package=colander), name='ob' + ) + + tup = colander.SchemaNode( + colander.Tuple(), + colander.SchemaNode(colander.Integer(), name='tupint'), + colander.SchemaNode(colander.String(), name='tupstring'), + name='tup', + ) + + seq = colander.SchemaNode(colander.Sequence(), tup, name='seq') + + seq2 = colander.SchemaNode( + colander.Sequence(), + colander.SchemaNode( + colander.Mapping(), + colander.SchemaNode(colander.Integer(), name='key'), + colander.SchemaNode(colander.Integer(), name='key2'), + name='mapping', + ), + name='seq2', + ) + + schema = colander.SchemaNode( + colander.Mapping(), integer, ob, tup, seq, seq2, name=name + ) + + return schema + + +def declarative(name='schema'): + import colander + + class TupleSchema(colander.TupleSchema): + tupint = colander.SchemaNode(colander.Int()) + tupstring = colander.SchemaNode(colander.String()) + + class MappingSchema(colander.MappingSchema): + key = colander.SchemaNode(colander.Int()) + key2 = colander.SchemaNode(colander.Int()) + + class SequenceOne(colander.SequenceSchema): + tup = TupleSchema() + + class SequenceTwo(colander.SequenceSchema): + mapping = MappingSchema() + + class MainSchema(colander.MappingSchema): + int = colander.SchemaNode( + colander.Int(), validator=colander.Range(0, 10) + ) + ob = colander.SchemaNode(colander.GlobalObject(package=colander)) + seq = SequenceOne() + tup = TupleSchema() + seq2 = SequenceTwo() + + schema = MainSchema(name=name) + return schema + + +def ultra_declarative(name='schema'): + import colander + + class IntSchema(colander.SchemaNode): + schema_type = colander.Int + + class StringSchema(colander.SchemaNode): + schema_type = colander.String + + class TupleSchema(colander.TupleSchema): + tupint = IntSchema() + tupstring = StringSchema() + + class MappingSchema(colander.MappingSchema): + key = IntSchema() + key2 = IntSchema() + + class SequenceOne(colander.SequenceSchema): + tup = TupleSchema() + + class SequenceTwo(colander.SequenceSchema): + mapping = MappingSchema() + + class IntSchemaRanged(IntSchema): + validator = colander.Range(0, 10) + + class GlobalObjectSchema(colander.SchemaNode): + def schema_type(self): + return colander.GlobalObject(package=colander) + + class MainSchema(colander.MappingSchema): + int = IntSchemaRanged() + ob = GlobalObjectSchema() + seq = SequenceOne() + tup = TupleSchema() + seq2 = SequenceTwo() + + MainSchema.name = name + + schema = MainSchema() + return schema + + +def with_instantiate(name='schema'): + import colander + + # an unlikely usage, but goes to test passing + # parameters to instantiation works + @colander.instantiate(name=name) + class schema(colander.MappingSchema): + int = colander.SchemaNode( + colander.Int(), validator=colander.Range(0, 10) + ) + ob = colander.SchemaNode(colander.GlobalObject(package=colander)) + + @colander.instantiate() + class seq(colander.SequenceSchema): + @colander.instantiate() + class tup(colander.TupleSchema): + tupint = colander.SchemaNode(colander.Int()) + tupstring = colander.SchemaNode(colander.String()) + + @colander.instantiate() + class tup(colander.TupleSchema): + tupint = colander.SchemaNode(colander.Int()) + tupstring = colander.SchemaNode(colander.String()) + + @colander.instantiate() + class seq2(colander.SequenceSchema): + @colander.instantiate() + class mapping(colander.MappingSchema): + key = colander.SchemaNode(colander.Int()) + key2 = colander.SchemaNode(colander.Int()) + + return schema + + +@pytest.fixture( + scope="module", + params=[ + imperative, + declarative, + ultra_declarative, + with_instantiate, + ], +) +def schema_maker(request): + return request.param + + +def test_deserialize_ok(schema_maker): + import tests + + data = { + 'int': '10', + 'ob': 'tests', + 'seq': [('1', 's'), ('2', 's'), ('3', 's'), ('4', 's')], + 'seq2': [{'key': '1', 'key2': '2'}, {'key': '3', 'key2': '4'}], + 'tup': ('1', 's'), + } + schema = schema_maker() + + result = schema.deserialize(data) + + assert result['int'] == 10 + assert result['ob'] == tests + assert result['seq'] == [(1, 's'), (2, 's'), (3, 's'), (4, 's')] + assert result['seq2'] == [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}] + assert result['tup'] == (1, 's') + + +def test_flatten_ok(schema_maker): + import tests + + appstruct = { + 'int': 10, + 'ob': tests, + 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], + 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], + 'tup': (1, 's'), + } + schema = schema_maker() + + result = schema.flatten(appstruct) + + expected = { + 'schema.seq.2.tupstring': 's', + 'schema.seq2.0.key2': 2, + 'schema.ob': tests, + 'schema.seq2.1.key2': 4, + 'schema.seq.1.tupstring': 's', + 'schema.seq2.0.key': 1, + 'schema.seq.1.tupint': 2, + 'schema.seq.0.tupstring': 's', + 'schema.seq.3.tupstring': 's', + 'schema.seq.3.tupint': 4, + 'schema.seq2.1.key': 3, + 'schema.int': 10, + 'schema.seq.0.tupint': 1, + 'schema.tup.tupint': 1, + 'schema.tup.tupstring': 's', + 'schema.seq.2.tupint': 3, + } + + for k, v in expected.items(): + assert result[k] == v + for k, v in result.items(): + assert expected[k] == v + + +def test_flatten_mapping_has_no_name(schema_maker): + import tests + + appstruct = { + 'int': 10, + 'ob': tests, + 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], + 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], + 'tup': (1, 's'), + } + schema = schema_maker(name='') + result = schema.flatten(appstruct) + + expected = { + 'seq.2.tupstring': 's', + 'seq2.0.key2': 2, + 'ob': tests, + 'seq2.1.key2': 4, + 'seq.1.tupstring': 's', + 'seq2.0.key': 1, + 'seq.1.tupint': 2, + 'seq.0.tupstring': 's', + 'seq.3.tupstring': 's', + 'seq.3.tupint': 4, + 'seq2.1.key': 3, + 'int': 10, + 'seq.0.tupint': 1, + 'tup.tupint': 1, + 'tup.tupstring': 's', + 'seq.2.tupint': 3, + } + + for k, v in expected.items(): + assert result[k] == v + for k, v in result.items(): + assert expected[k] == v + + +def test_unflatten_ok(schema_maker): + import tests + + fstruct = { + 'schema.seq.2.tupstring': 's', + 'schema.seq2.0.key2': 2, + 'schema.ob': tests, + 'schema.seq2.1.key2': 4, + 'schema.seq.1.tupstring': 's', + 'schema.seq2.0.key': 1, + 'schema.seq.1.tupint': 2, + 'schema.seq.0.tupstring': 's', + 'schema.seq.3.tupstring': 's', + 'schema.seq.3.tupint': 4, + 'schema.seq2.1.key': 3, + 'schema.int': 10, + 'schema.seq.0.tupint': 1, + 'schema.tup.tupint': 1, + 'schema.tup.tupstring': 's', + 'schema.seq.2.tupint': 3, + } + schema = schema_maker() + + result = schema.unflatten(fstruct) + + expected = { + 'int': 10, + 'ob': tests, + 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], + 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], + 'tup': (1, 's'), + } + + for k, v in expected.items(): + assert result[k] == v + + for k, v in result.items(): + assert expected[k] == v + + +def test_unflatten_mapping_no_name(schema_maker): + import tests + + fstruct = { + 'seq.2.tupstring': 's', + 'seq2.0.key2': 2, + 'ob': tests, + 'seq2.1.key2': 4, + 'seq.1.tupstring': 's', + 'seq2.0.key': 1, + 'seq.1.tupint': 2, + 'seq.0.tupstring': 's', + 'seq.3.tupstring': 's', + 'seq.3.tupint': 4, + 'seq2.1.key': 3, + 'int': 10, + 'seq.0.tupint': 1, + 'tup.tupint': 1, + 'tup.tupstring': 's', + 'seq.2.tupint': 3, + } + schema = schema_maker(name='') + + result = schema.unflatten(fstruct) + + expected = { + 'int': 10, + 'ob': tests, + 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], + 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], + 'tup': (1, 's'), + } + + for k, v in expected.items(): + assert result[k] == v + + for k, v in result.items(): + assert expected[k] == v + + +def test_flatten_unflatten_roundtrip(schema_maker): + import tests + + appstruct = { + 'int': 10, + 'ob': tests, + 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], + 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], + 'tup': (1, 's'), + } + schema = schema_maker(name='') + + assert schema.unflatten(schema.flatten(appstruct)) == appstruct + + +def test_set_value(schema_maker): + import tests + + appstruct = { + 'int': 10, + 'ob': tests, + 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], + 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], + 'tup': (1, 's'), + } + schema = schema_maker() + + schema.set_value(appstruct, 'seq2.1.key', 6) + + assert appstruct['seq2'][1] == {'key': 6, 'key2': 4} + + +def test_get_value(schema_maker): + import tests + + appstruct = { + 'int': 10, + 'ob': tests, + 'seq': [(1, 's'), (2, 's'), (3, 's'), (4, 's')], + 'seq2': [{'key': 1, 'key2': 2}, {'key': 3, 'key2': 4}], + 'tup': (1, 's'), + } + schema = schema_maker() + + assert schema.get_value(appstruct, 'seq') == [ + (1, 's'), + (2, 's'), + (3, 's'), + (4, 's'), + ] + assert schema.get_value(appstruct, 'seq2.1.key') == 3 + + +def test_invalid_asdict(schema_maker): + from colander import Invalid + + expected = { + 'schema.int': '20 is greater than maximum value 10', + 'schema.ob': 'The dotted name "no.way.this.exists" ' + 'cannot be imported', + 'schema.seq.0.0': '"q" is not a number', + 'schema.seq.1.0': '"w" is not a number', + 'schema.seq.2.0': '"e" is not a number', + 'schema.seq.3.0': '"r" is not a number', + 'schema.seq2.0.key': '"t" is not a number', + 'schema.seq2.0.key2': '"y" is not a number', + 'schema.seq2.1.key': '"u" is not a number', + 'schema.seq2.1.key2': '"i" is not a number', + 'schema.tup.0': '"s" is not a number', + } + data = { + 'int': '20', + 'ob': 'no.way.this.exists', + 'seq': [('q', 's'), ('w', 's'), ('e', 's'), ('r', 's')], + 'seq2': [{'key': 't', 'key2': 'y'}, {'key': 'u', 'key2': 'i'}], + 'tup': ('s', 's'), + } + schema = schema_maker() + + with pytest.raises(Invalid) as exc: + schema.deserialize(data) + + invalid = exc.value + errors = invalid.asdict() + assert errors == expected + + +def test_invalid_asdict_translation_callback(schema_maker): + from colander import Invalid + from translationstring import TranslationString + + expected = { + 'schema.int': 'translated', + 'schema.ob': 'translated', + 'schema.seq.0.0': 'translated', + 'schema.seq.1.0': 'translated', + 'schema.seq.2.0': 'translated', + 'schema.seq.3.0': 'translated', + 'schema.seq2.0.key': 'translated', + 'schema.seq2.0.key2': 'translated', + 'schema.seq2.1.key': 'translated', + 'schema.seq2.1.key2': 'translated', + 'schema.tup.0': 'translated', + } + data = { + 'int': '20', + 'ob': 'no.way.this.exists', + 'seq': [('q', 's'), ('w', 's'), ('e', 's'), ('r', 's')], + 'seq2': [{'key': 't', 'key2': 'y'}, {'key': 'u', 'key2': 'i'}], + 'tup': ('s', 's'), + } + schema = schema_maker() + + def translation_function(string): + return TranslationString('translated') + + with pytest.raises(Invalid) as exc: + schema.deserialize(data) + + invalid = exc.value + errors = invalid.asdict(translate=translation_function) + assert errors == expected From 94640514a5b8bf7605dcaeb112882349e883dd7c Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 15 Aug 2024 13:58:34 -0400 Subject: [PATCH 15/16] tests: remove superseded 'TestCase' classes --- tests/test_colander.py | 140 ----------------------------------------- 1 file changed, 140 deletions(-) delete mode 100644 tests/test_colander.py diff --git a/tests/test_colander.py b/tests/test_colander.py deleted file mode 100644 index d9bf052..0000000 --- a/tests/test_colander.py +++ /dev/null @@ -1,140 +0,0 @@ -import unittest - - -def invalid_exc(func, *arg, **kw): - from colander import Invalid - - try: - func(*arg, **kw) - except Invalid as e: - return e - else: - raise AssertionError('Invalid not raised') # pragma: no cover - - -class Test_null(unittest.TestCase): - def test___nonzero__(self): - from colander import null - - self.assertFalse(null) - - def test___repr__(self): - from colander import null - - self.assertEqual(repr(null), '') - - def test_pickling(self): - import pickle - - from colander import null - - self.assertTrue(pickle.loads(pickle.dumps(null)) is null) - - -class Test_required(unittest.TestCase): - def test___repr__(self): - from colander import required - - self.assertEqual(repr(required), '') - - def test_pickling(self): - import pickle - - from colander import required - - self.assertTrue(pickle.loads(pickle.dumps(required)) is required) - - -class Test_drop(unittest.TestCase): - def test___repr__(self): - from colander import drop - - self.assertEqual(repr(drop), '') - - def test_pickling(self): - import pickle - - from colander import drop - - self.assertTrue(pickle.loads(pickle.dumps(drop)) is drop) - - -class Dummy: - pass - - -class DummySchemaNode: - def __init__(self, typ, name='', exc=None, default=None): - self.typ = typ - self.name = name - self.exc = exc - self.required = default is None - self.default = default - self.children = [] - - def deserialize(self, val): - from colander import Invalid - - if self.exc: - raise Invalid(self, self.exc) - return val - - def serialize(self, val): - from colander import Invalid - - if self.exc: - raise Invalid(self, self.exc) - return val - - def __getitem__(self, name): - for child in self.children: - if child.name == name: - return child - - -class DummyValidator: - def __init__(self, msg=None, children=None): - self.msg = msg - self.children = children - - def __call__(self, node, value): - from colander import Invalid - - if self.msg: - e = Invalid(node, self.msg) - self.children and e.children.extend(self.children) - raise e - - -class DummyValidatorWithMsgNone: - def __call__(self, node, value): - from colander import Invalid - - e = Invalid(node) - raise e - - -class Uncooperative: - def __str__(self): - raise ValueError('I wont cooperate') - - __unicode__ = __str__ - - -class DummyType: - def serialize(self, node, value): - return value - - def deserialize(self, node, value): - return value - - def flatten(self, node, appstruct, prefix='', listitem=False): - if listitem: - key = prefix.rstrip('.') - else: - key = prefix + 'appstruct' - return {key: appstruct} - - def unflatten(self, node, paths, fstruct): - assert paths == [node.name] - return fstruct[node.name] From 6808a9d337ce037db82caca93afaf426734ca5a2 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Thu, 15 Aug 2024 14:24:03 -0400 Subject: [PATCH 16/16] ci: 3.7 support already dropped --- .github/workflows/ci-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index c8fffea..9de52d8 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -15,7 +15,6 @@ jobs: strategy: matrix: py: - - "3.7" - "3.8" - "3.9" - "3.10"