Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions confuse/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -361,15 +363,24 @@ 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)
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))
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------

Expand Down
20 changes: 20 additions & 0 deletions test/test_valid.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down