diff --git a/confuse/templates.py b/confuse/templates.py index 0281d30..87e0c34 100644 --- a/confuse/templates.py +++ b/confuse/templates.py @@ -324,11 +324,11 @@ class Choice(Template[T], Generic[T, K]): see :meth:`__init__` for usage. """ - choices: abc.Sequence[T] | dict[K, T] | type[T] + choices: abc.Sequence[T] | abc.Set[T] | dict[K, T] | type[T] def __init__( self, - choices: abc.Sequence[T] | dict[K, T] | type[T], + choices: abc.Sequence[T] | abc.Set[T] | dict[K, T] | type[T], default: T | _Required = REQUIRED, ) -> None: """Create a template that validates any of the values from the @@ -345,7 +345,9 @@ def __init__( @singledispatchmethod def convert_choices( - self, choices: abc.Sequence[T] | dict[K, T] | type[T], value: str + self, + choices: abc.Sequence[T] | abc.Set[T] | dict[K, T] | type[T], + value: str, ) -> T: raise NotImplementedError @@ -361,8 +363,16 @@ def _(self, choices: dict[K, T], value: K) -> T: def _(self, choices: abc.Sequence[T], value: T) -> T: return choices[choices.index(value)] + @convert_choices.register(abc.Set) + def _(self, choices: abc.Set[T], value: T) -> T: + if value not in choices: + raise ValueError(value) + return value + @singledispatchmethod - def format_choices(self, choices: abc.Sequence[T] | enum.Enum) -> list[str]: + def format_choices( + self, choices: abc.Sequence[T] | abc.Set[T] | enum.Enum + ) -> list[str]: raise NotImplementedError @format_choices.register(type) @@ -370,6 +380,7 @@ def _(self, choices: type[enum.Enum]) -> list[str]: return [c.value for c in choices] @format_choices.register(abc.Sequence) + @format_choices.register(abc.Set) @format_choices.register(Mapping) def _(self, choices: Iterable[T]) -> list[str]: return list(map(str, choices)) diff --git a/docs/changelog.rst b/docs/changelog.rst index 47f3c4b..7e4b224 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,10 @@ Changelog Unreleased ---------- +- Fix `Choice` rejecting `set` and `frozenset` choices with `NotImplementedError`, + which regressed in v2.2.0. + [#192](https://github.com/beetbox/confuse/issues/192) + v2.2.1 ------ diff --git a/test/test_valid.py b/test/test_valid.py index 1b5be61..97465f6 100644 --- a/test/test_valid.py +++ b/test/test_valid.py @@ -257,6 +257,26 @@ def test_validate_bad_choice_in_dict(self): with pytest.raises(confuse.ConfigValueError): config["foo"].get(confuse.Choice({2: "two", 4: "four"})) + def test_validate_good_choice_in_set(self): + config = _root({"foo": 2}) + valid = config["foo"].get(confuse.Choice({1, 2, 4, 8, 16})) + assert valid == 2 + + def test_validate_bad_choice_in_set(self): + config = _root({"foo": 3}) + with pytest.raises(confuse.ConfigValueError): + config["foo"].get(confuse.Choice({1, 2, 4, 8, 16})) + + def test_validate_good_choice_in_frozenset(self): + config = _root({"foo": 2}) + valid = config["foo"].get(confuse.Choice(frozenset({1, 2, 4, 8, 16}))) + assert valid == 2 + + def test_validate_bad_choice_in_frozenset(self): + config = _root({"foo": 3}) + with pytest.raises(confuse.ConfigValueError): + config["foo"].get(confuse.Choice(frozenset({1, 2, 4, 8, 16}))) + class OneOfTest(unittest.TestCase): def test_default_value(self):