Skip to content

Commit 6774f1d

Browse files
blackarydwalterspre-commit-ci[bot]tiangolosvlandeg
authored
✨ Support typing.Literal to define a set of predefined choices (#429)
Co-authored-by: Dan Walters <dan@walters.io> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com> Co-authored-by: svlandeg <svlandeg@github.com> Co-authored-by: Sofie Van Landeghem <svlandeg@users.noreply.github.com> Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
1 parent 2ece3e6 commit 6774f1d

File tree

7 files changed

+157
-2
lines changed

7 files changed

+157
-2
lines changed

docs/tutorial/parameter-types/enum.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,37 @@ Buying groceries: Eggs, Bacon
111111
```
112112

113113
</div>
114+
115+
### Literal choices
116+
117+
You can also use `Literal` to represent a set of possible predefined choices, without having to use an `Enum`:
118+
119+
{* docs_src/parameter_types/enum/tutorial004_an.py hl[6] *}
120+
121+
<div class="termy">
122+
123+
```console
124+
$ python main.py --help
125+
126+
// Notice the predefined values [simple|conv|lstm]
127+
Usage: main.py [OPTIONS]
128+
129+
Options:
130+
--network [simple|conv|lstm] [default: simple]
131+
--help Show this message and exit.
132+
133+
// Try it
134+
$ python main.py --network conv
135+
136+
Training neural network of type: conv
137+
138+
// Invalid value
139+
$ python main.py --network capsule
140+
141+
Usage: main.py [OPTIONS]
142+
Try "main.py --help" for help.
143+
144+
Error: Invalid value for '--network': 'capsule' is not one of 'simple', 'conv', 'lstm'.
145+
```
146+
147+
</div>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import typer
2+
from typing_extensions import Literal
3+
4+
5+
def main(network: Literal["simple", "conv", "lstm"] = typer.Option("simple")):
6+
print(f"Training neural network of type: {network}")
7+
8+
9+
if __name__ == "__main__":
10+
typer.run(main)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import typer
2+
from typing_extensions import Annotated, Literal
3+
4+
5+
def main(
6+
network: Annotated[Literal["simple", "conv", "lstm"], typer.Option()] = "simple",
7+
):
8+
print(f"Training neural network of type: {network}")
9+
10+
11+
if __name__ == "__main__":
12+
typer.run(main)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import subprocess
2+
import sys
3+
4+
import typer
5+
from typer.testing import CliRunner
6+
7+
from docs_src.parameter_types.enum import tutorial004 as mod
8+
9+
runner = CliRunner()
10+
11+
app = typer.Typer()
12+
app.command()(mod.main)
13+
14+
15+
def test_help():
16+
result = runner.invoke(app, ["--help"])
17+
assert result.exit_code == 0
18+
assert "--network [simple|conv|lstm]" in result.output.replace(" ", "")
19+
20+
21+
def test_main():
22+
result = runner.invoke(app, ["--network", "conv"])
23+
assert result.exit_code == 0
24+
assert "Training neural network of type: conv" in result.output
25+
26+
27+
def test_invalid():
28+
result = runner.invoke(app, ["--network", "capsule"])
29+
assert result.exit_code != 0
30+
assert "Invalid value for '--network'" in result.output
31+
assert (
32+
"invalid choice: capsule. (choose from" in result.output
33+
or "'capsule' is not one of" in result.output
34+
)
35+
assert "simple" in result.output
36+
assert "conv" in result.output
37+
assert "lstm" in result.output
38+
39+
40+
def test_script():
41+
result = subprocess.run(
42+
[sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
43+
capture_output=True,
44+
encoding="utf-8",
45+
)
46+
assert "Usage" in result.stdout
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import subprocess
2+
import sys
3+
4+
import typer
5+
from typer.testing import CliRunner
6+
7+
from docs_src.parameter_types.enum import tutorial004_an as mod
8+
9+
runner = CliRunner()
10+
11+
app = typer.Typer()
12+
app.command()(mod.main)
13+
14+
15+
def test_help():
16+
result = runner.invoke(app, ["--help"])
17+
assert result.exit_code == 0
18+
assert "--network [simple|conv|lstm]" in result.output.replace(" ", "")
19+
20+
21+
def test_main():
22+
result = runner.invoke(app, ["--network", "conv"])
23+
assert result.exit_code == 0
24+
assert "Training neural network of type: conv" in result.output
25+
26+
27+
def test_invalid():
28+
result = runner.invoke(app, ["--network", "capsule"])
29+
assert result.exit_code != 0
30+
assert "Invalid value for '--network'" in result.output
31+
assert (
32+
"invalid choice: capsule. (choose from" in result.output
33+
or "'capsule' is not one of" in result.output
34+
)
35+
assert "simple" in result.output
36+
assert "conv" in result.output
37+
assert "lstm" in result.output
38+
39+
40+
def test_script():
41+
result = subprocess.run(
42+
[sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
43+
capture_output=True,
44+
encoding="utf-8",
45+
)
46+
assert "Usage" in result.stdout

typer/_typing.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ def is_callable_type(type_: Type[Any]) -> bool:
9393

9494

9595
def is_literal_type(type_: Type[Any]) -> bool:
96-
return Literal is not None and get_origin(type_) is Literal
96+
import typing_extensions
97+
98+
return get_origin(type_) in (Literal, typing_extensions.Literal)
9799

98100

99101
def literal_values(type_: Type[Any]) -> Tuple[Any, ...]:

typer/main.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import click
1818
from typer._types import TyperChoice
1919

20-
from ._typing import get_args, get_origin, is_union
20+
from ._typing import get_args, get_origin, is_literal_type, is_union, literal_values
2121
from .completion import get_completion_inspect_parameters
2222
from .core import (
2323
DEFAULT_MARKUP_MODE,
@@ -783,6 +783,11 @@ def get_click_type(
783783
[item.value for item in annotation],
784784
case_sensitive=parameter_info.case_sensitive,
785785
)
786+
elif is_literal_type(annotation):
787+
return click.Choice(
788+
literal_values(annotation),
789+
case_sensitive=parameter_info.case_sensitive,
790+
)
786791
raise RuntimeError(f"Type not yet supported: {annotation}") # pragma: no cover
787792

788793

0 commit comments

Comments
 (0)