| # SPDX-License-Identifier: MIT |
| |
| """ |
| Tests for `attr.converters`. |
| """ |
| |
| |
| import pytest |
| |
| import attr |
| |
| from attr import Factory, attrib |
| from attr.converters import default_if_none, optional, pipe, to_bool |
| |
| |
| class TestOptional: |
| """ |
| Tests for `optional`. |
| """ |
| |
| def test_success_with_type(self): |
| """ |
| Wrapped converter is used as usual if value is not None. |
| """ |
| c = optional(int) |
| |
| assert c("42") == 42 |
| |
| def test_success_with_none(self): |
| """ |
| Nothing happens if None. |
| """ |
| c = optional(int) |
| |
| assert c(None) is None |
| |
| def test_fail(self): |
| """ |
| Propagates the underlying conversion error when conversion fails. |
| """ |
| c = optional(int) |
| |
| with pytest.raises(ValueError): |
| c("not_an_int") |
| |
| |
| class TestDefaultIfNone: |
| def test_missing_default(self): |
| """ |
| Raises TypeError if neither default nor factory have been passed. |
| """ |
| with pytest.raises(TypeError, match="Must pass either"): |
| default_if_none() |
| |
| def test_too_many_defaults(self): |
| """ |
| Raises TypeError if both default and factory are passed. |
| """ |
| with pytest.raises(TypeError, match="but not both"): |
| default_if_none(True, lambda: 42) |
| |
| def test_factory_takes_self(self): |
| """ |
| Raises ValueError if passed Factory has takes_self=True. |
| """ |
| with pytest.raises(ValueError, match="takes_self"): |
| default_if_none(Factory(list, takes_self=True)) |
| |
| @pytest.mark.parametrize("val", [1, 0, True, False, "foo", "", object()]) |
| def test_not_none(self, val): |
| """ |
| If a non-None value is passed, it's handed down. |
| """ |
| c = default_if_none("nope") |
| |
| assert val == c(val) |
| |
| c = default_if_none(factory=list) |
| |
| assert val == c(val) |
| |
| def test_none_value(self): |
| """ |
| Default values are returned when a None is passed. |
| """ |
| c = default_if_none(42) |
| |
| assert 42 == c(None) |
| |
| def test_none_factory(self): |
| """ |
| Factories are used if None is passed. |
| """ |
| c = default_if_none(factory=list) |
| |
| assert [] == c(None) |
| |
| c = default_if_none(default=Factory(list)) |
| |
| assert [] == c(None) |
| |
| |
| class TestPipe: |
| def test_success(self): |
| """ |
| Succeeds if all wrapped converters succeed. |
| """ |
| c = pipe(str, to_bool, bool) |
| |
| assert True is c("True") is c(True) |
| |
| def test_fail(self): |
| """ |
| Fails if any wrapped converter fails. |
| """ |
| c = pipe(str, to_bool) |
| |
| # First wrapped converter fails: |
| with pytest.raises(ValueError): |
| c(33) |
| |
| # Last wrapped converter fails: |
| with pytest.raises(ValueError): |
| c("33") |
| |
| def test_sugar(self): |
| """ |
| `pipe(c1, c2, c3)` and `[c1, c2, c3]` are equivalent. |
| """ |
| |
| @attr.s |
| class C: |
| a1 = attrib(default="True", converter=pipe(str, to_bool, bool)) |
| a2 = attrib(default=True, converter=[str, to_bool, bool]) |
| |
| c = C() |
| assert True is c.a1 is c.a2 |
| |
| def test_empty(self): |
| """ |
| Empty pipe returns same value. |
| """ |
| o = object() |
| |
| assert o is pipe()(o) |
| |
| |
| class TestToBool: |
| def test_unhashable(self): |
| """ |
| Fails if value is unhashable. |
| """ |
| with pytest.raises(ValueError, match="Cannot convert value to bool"): |
| to_bool([]) |
| |
| def test_truthy(self): |
| """ |
| Fails if truthy values are incorrectly converted. |
| """ |
| assert to_bool("t") |
| assert to_bool("yes") |
| assert to_bool("on") |
| |
| def test_falsy(self): |
| """ |
| Fails if falsy values are incorrectly converted. |
| """ |
| assert not to_bool("f") |
| assert not to_bool("no") |
| assert not to_bool("off") |