From 503dd346a1be9ac016c43b902a6601ddeaa9bda8 Mon Sep 17 00:00:00 2001 From: Antoni Szych Date: Thu, 1 Feb 2024 21:17:46 +0100 Subject: [PATCH] fix: allow unsortable containers in In and NotIn validators (fixes #451) (#506) credits to: @spacegaier and @beastd --- voluptuous/tests/tests.py | 37 ++++++++++++++++++++++++++++++++++++- voluptuous/validators.py | 24 ++++++++++++++++++------ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py index 05b7e8e..7286b31 100644 --- a/voluptuous/tests/tests.py +++ b/voluptuous/tests/tests.py @@ -79,19 +79,54 @@ def test_in(): assert isinstance(ctx.value.errors[0], InInvalid) +def test_in_unsortable_container(): + """Verify that In works with unsortable container.""" + schema = Schema({"type": In((int, str, float))}) + schema({"type": float}) + with pytest.raises( + MultipleInvalid, + match=( + r"value must be one of \[, , \] for dictionary value " + r"@ data\['type'\]" + ), + ) as ctx: + schema({"type": 42}) + assert len(ctx.value.errors) == 1 + assert isinstance(ctx.value.errors[0], InInvalid) + + def test_not_in(): """Verify that NotIn works.""" schema = Schema({"color": NotIn(frozenset(["red", "blue", "yellow"]))}) schema({"color": "orange"}) with pytest.raises( MultipleInvalid, - match=r"value must not be one of \['blue', 'red', 'yellow'\] for dictionary value @ data\['color'\]", + match=( + r"value must not be one of \['blue', 'red', 'yellow'\] for dictionary " + r"value @ data\['color'\]" + ), ) as ctx: schema({"color": "blue"}) assert len(ctx.value.errors) == 1 assert isinstance(ctx.value.errors[0], NotInInvalid) +def test_not_in_unsortable_container(): + """Verify that NotIn works with unsortable container.""" + schema = Schema({"type": NotIn((int, str, float))}) + schema({"type": 42}) + with pytest.raises( + MultipleInvalid, + match=( + r"value must not be one of \[, , " + r"\] for dictionary value @ data\['type'\]" + ), + ) as ctx: + schema({"type": str}) + assert len(ctx.value.errors) == 1 + assert isinstance(ctx.value.errors[0], NotInInvalid) + + def test_contains(): """Verify contains validation method.""" schema = Schema({'color': Contains('red')}) diff --git a/voluptuous/validators.py b/voluptuous/validators.py index a372afe..9951738 100644 --- a/voluptuous/validators.py +++ b/voluptuous/validators.py @@ -821,9 +821,15 @@ def __call__(self, v): except TypeError: check = True if check: - raise InInvalid( - self.msg or 'value must be one of {}'.format(sorted(self.container)) - ) + try: + raise InInvalid( + self.msg or f'value must be one of {sorted(self.container)}' + ) + except TypeError: + raise InInvalid( + self.msg + or f'value must be one of {sorted(self.container, key=str)}' + ) return v def __repr__(self): @@ -845,9 +851,15 @@ def __call__(self, v): except TypeError: check = True if check: - raise NotInInvalid( - self.msg or 'value must not be one of {}'.format(sorted(self.container)) - ) + try: + raise NotInInvalid( + self.msg or f'value must not be one of {sorted(self.container)}' + ) + except TypeError: + raise NotInInvalid( + self.msg + or f'value must not be one of {sorted(self.container, key=str)}' + ) return v def __repr__(self):