Skip to content

Commit 7a706be

Browse files
lebriceCopilot
andauthored
Fix #322 (Union[Literal, ...]) (#325)
* Add test to repro #322 Signed-off-by: Fabrice Normandin <fabrice.normandin@gmail.com> * Fix pre-commit issues Signed-off-by: Fabrice Normandin <fabrice.normandin@gmail.com> --------- Signed-off-by: Fabrice Normandin <fabrice.normandin@gmail.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 3b5bc23 commit 7a706be

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

simple_parsing/wrappers/field_parsing.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
is_forward_ref,
1717
is_homogeneous_tuple_type,
1818
is_list,
19+
is_literal,
1920
is_tuple,
2021
is_typevar,
2122
is_union,
@@ -127,6 +128,10 @@ def get_parsing_fn(t: type[T]) -> Callable[[Any], T]:
127128
args = get_type_arguments(t)
128129
return parse_union(*args)
129130

131+
elif is_literal(t):
132+
logger.debug(f"Parsing a Literal field of type {t}")
133+
return parse_literal(t)
134+
130135
elif is_enum(t):
131136
logger.debug(f"Parsing an Enum field of type {t}")
132137
return parse_enum(t)
@@ -205,6 +210,29 @@ def _parse_optional(val: Optional[Any]) -> Optional[T]:
205210
return _parse_optional
206211

207212

213+
def parse_literal(literal_type: type[T]) -> Callable[[str], T]:
214+
"""Returns a parsing function for a Literal type.
215+
216+
The function maps the string representation of each literal value back to the actual value
217+
(e.g. "1" -> 1, "BLUE" -> Color.BLUE).
218+
"""
219+
literal_values = get_type_arguments(literal_type)
220+
# Build a mapping from the string representation to the actual value.
221+
choice_dict: dict[str, Any] = {
222+
(v.name if isinstance(v, enum.Enum) else str(v)): v for v in literal_values
223+
}
224+
225+
def _parse_literal(val: str) -> T:
226+
if val in choice_dict:
227+
return choice_dict[val]
228+
raise ValueError(
229+
f"Invalid value {val!r} for {literal_type}. Expected one of: {list(choice_dict)}"
230+
)
231+
232+
_parse_literal.__name__ = str(literal_type)
233+
return _parse_literal
234+
235+
208236
def parse_tuple(tuple_item_types: tuple[type[T], ...]) -> Callable[[list[T]], tuple[T, ...]]:
209237
"""Makes a parsing function for creating tuples from the command-line args.
210238

test/test_literal.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import enum
22
import sys
33
from dataclasses import dataclass
4-
from typing import Any, NamedTuple, Optional
4+
from typing import Any, NamedTuple, Optional, Union
55

66
import pytest
77
from typing_extensions import Literal
@@ -118,3 +118,21 @@ def test_reproduce_issue_259_parsing_literal_py39():
118118
"argument --param: invalid typing.Literal['bar', 'biz'] value: 'biz'"
119119
):
120120
assert SomeFoo.setup("").param == "biz"
121+
122+
123+
@dataclass
124+
class Foo:
125+
bar: Union[Literal["a"], int] = "a"
126+
127+
128+
def test_issue_322():
129+
"""Test for https://github.com/lebrice/SimpleParsing/issues/322."""
130+
from simple_parsing import parse
131+
132+
assert parse(Foo, args="") == Foo()
133+
assert parse(Foo, args="--bar=a") == Foo(bar="a")
134+
# 'b' is neither in Literal["a"] nor a valid int, so it should be rejected.
135+
with exits_and_writes_to_stderr("invalid"):
136+
assert parse(Foo, args="--bar=b")
137+
138+
assert parse(Foo, args="--bar=123") == Foo(bar=123)

0 commit comments

Comments
 (0)