Enum - Choices¶
To define a CLI parameter that can take a value from a predefined set of values you can use a standard Python enum.Enum:
from enum import Enum
import typer
class NeuralNetwork(str, Enum):
simple = "simple"
conv = "conv"
lstm = "lstm"
app = typer.Typer()
@app.command()
def main(network: NeuralNetwork = NeuralNetwork.simple):
print(f"Training neural network of type: {network.value}")
if __name__ == "__main__":
app()
Tip
Notice that the function parameter network will be an Enum, not a str.
To get the str value in your function's code use network.value.
Check it:
$ python main.py --help
// Notice the predefined values [simple|conv|lstm]
Usage: main.py [OPTIONS]
Options:
--network [simple|conv|lstm] [default: simple]
--help Show this message and exit.
// Try it
$ python main.py --network conv
Training neural network of type: conv
// Invalid value
$ python main.py --network capsule
Usage: main.py [OPTIONS]
Try "main.py --help" for help.
Error: Invalid value for '--network': 'capsule' is not one of 'simple', 'conv', 'lstm'.
// Note that enums are case sensitive by default
$ python main.py --network CONV
Usage: main.py [OPTIONS]
Try "main.py --help" for help.
Error: Invalid value for '--network': 'CONV' is not one of 'simple', 'conv', 'lstm'.
Case insensitive Enum choices¶
You can make an Enum (choice) CLI parameter be case-insensitive with the case_sensitive parameter:
from enum import Enum
from typing import Annotated
import typer
class NeuralNetwork(str, Enum):
simple = "simple"
conv = "conv"
lstm = "lstm"
app = typer.Typer()
@app.command()
def main(
network: Annotated[
NeuralNetwork, typer.Option(case_sensitive=False)
] = NeuralNetwork.simple,
):
print(f"Training neural network of type: {network.value}")
if __name__ == "__main__":
app()
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from enum import Enum
import typer
class NeuralNetwork(str, Enum):
simple = "simple"
conv = "conv"
lstm = "lstm"
app = typer.Typer()
@app.command()
def main(
network: NeuralNetwork = typer.Option(NeuralNetwork.simple, case_sensitive=False),
):
print(f"Training neural network of type: {network.value}")
if __name__ == "__main__":
app()
And then the values of the Enum will be checked no matter if lower case, upper case, or a mix:
// Notice the upper case CONV
$ python main.py --network CONV
Training neural network of type: conv
// A mix also works
$ python main.py --network LsTm
Training neural network of type: lstm
Using Enum names instead of values¶
Sometimes you want to accept Enum names from the command line and convert
that into Enum values in the command handler. You can enable this by setting
enum_by_name=True:
import enum
import logging
from typing import Annotated
import typer
class LogLevel(enum.Enum):
debug = logging.DEBUG
info = logging.INFO
warning = logging.WARNING
app = typer.Typer()
@app.command()
def main(log_level: Annotated[LogLevel, typer.Option(enum_by_name=True)] = "warning"):
typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}")
if __name__ == "__main__":
app()
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
import enum
import logging
import typer
class LogLevel(enum.Enum):
debug = logging.DEBUG
info = logging.INFO
warning = logging.WARNING
app = typer.Typer()
@app.command()
def main(log_level: LogLevel = typer.Option("warning", enum_by_name=True)):
typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}")
if __name__ == "__main__":
app()
And then the names of the Enum will be used instead of values:
$ python main.py --log-level debug
Log level set to DEBUG
This can be particularly useful if the enum values are not strings:
import enum
from typing import Annotated
import typer
class Access(enum.IntEnum):
private = 1
protected = 2
public = 3
open = 4
app = typer.Typer()
@app.command()
def main(access: Annotated[Access, typer.Option(enum_by_name=True)] = "private"):
typer.echo(f"Access level: {access.name} ({access.value})")
if __name__ == "__main__":
app()
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
import enum
import typer
class Access(enum.IntEnum):
private = 1
protected = 2
public = 3
open = 4
app = typer.Typer()
@app.command()
def main(access: Access = typer.Option("private", enum_by_name=True)):
typer.echo(f"Access level: {access.name} ({access.value})")
if __name__ == "__main__":
app()
$ python main.py --access protected
Access level: protected (2)
List of Enum values¶
A CLI parameter can also take a list of Enum values:
from enum import Enum
from typing import Annotated
import typer
class Food(str, Enum):
food_1 = "Eggs"
food_2 = "Bacon"
food_3 = "Cheese"
app = typer.Typer()
@app.command()
def main(groceries: Annotated[list[Food], typer.Option()] = [Food.food_1, Food.food_3]):
print(f"Buying groceries: {', '.join([f.value for f in groceries])}")
if __name__ == "__main__":
app()
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from enum import Enum
import typer
class Food(str, Enum):
food_1 = "Eggs"
food_2 = "Bacon"
food_3 = "Cheese"
app = typer.Typer()
@app.command()
def main(groceries: list[Food] = typer.Option([Food.food_1, Food.food_3])):
print(f"Buying groceries: {', '.join([f.value for f in groceries])}")
if __name__ == "__main__":
app()
This works just like any other parameter value taking a list of things:
$ python main.py --help
// Notice the default values being shown
Usage: main.py [OPTIONS]
Options:
--groceries [Eggs|Bacon|Cheese] [default: Eggs, Cheese]
--help Show this message and exit.
// Try it with the default values
$ python main.py
Buying groceries: Eggs, Cheese
// Try it with a single value
$ python main.py --groceries "Eggs"
Buying groceries: Eggs
// Try it with multiple values
$ python main.py --groceries "Eggs" --groceries "Bacon"
Buying groceries: Eggs, Bacon
You can also combine enum_by_name=True with a list of enums:
from enum import Enum
from typing import Annotated
import typer
class Food(str, Enum):
f1 = "Eggs"
f2 = "Bacon"
f3 = "Cheese"
app = typer.Typer()
@app.command()
def main(
groceries: Annotated[list[Food], typer.Option(enum_by_name=True)] = ["f1", "f3"],
):
print(f"Buying groceries: {', '.join([f.value for f in groceries])}")
if __name__ == "__main__":
app()
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from enum import Enum
import typer
class Food(str, Enum):
f1 = "Eggs"
f2 = "Bacon"
f3 = "Cheese"
app = typer.Typer()
@app.command()
def main(groceries: list[Food] = typer.Option(["f1", "f3"], enum_by_name=True)):
print(f"Buying groceries: {', '.join([f.value for f in groceries])}")
if __name__ == "__main__":
app()
This works exactly the same, but you're using the enum names instead of values:
// Try it with a single value
$ python main.py --groceries "f1"
Buying groceries: Eggs
// Try it with multiple values
$ python main.py --groceries "f1" --groceries "f2"
Buying groceries: Eggs, Bacon
Literal choices¶
You can also use Literal to represent a set of possible predefined choices, without having to use an Enum:
from typing import Annotated, Literal
import typer
app = typer.Typer()
@app.command()
def main(
network: Annotated[Literal["simple", "conv", "lstm"], typer.Option()] = "simple",
):
print(f"Training neural network of type: {network}")
if __name__ == "__main__":
app()
🤓 Other versions and variants
from typing import Annotated, Literal
import typer
app = typer.Typer()
@app.command()
def main(
network: Annotated[Literal["simple", "conv", "lstm"], typer.Option()] = "simple",
):
print(f"Training neural network of type: {network}")
if __name__ == "__main__":
app()
Tip
Prefer to use the Annotated version if possible.
from typing import Literal
import typer
app = typer.Typer()
@app.command()
def main(network: Literal["simple", "conv", "lstm"] = typer.Option("simple")):
print(f"Training neural network of type: {network}")
if __name__ == "__main__":
app()
Tip
Prefer to use the Annotated version if possible.
from typing import Literal
import typer
app = typer.Typer()
@app.command()
def main(network: Literal["simple", "conv", "lstm"] = typer.Option("simple")):
print(f"Training neural network of type: {network}")
if __name__ == "__main__":
app()
$ python main.py --help
// Notice the predefined values [simple|conv|lstm]
Usage: main.py [OPTIONS]
Options:
--network [simple|conv|lstm] [default: simple]
--help Show this message and exit.
// Try it
$ python main.py --network conv
Training neural network of type: conv
// Invalid value
$ python main.py --network capsule
Usage: main.py [OPTIONS]
Try "main.py --help" for help.
Error: Invalid value for '--network': 'capsule' is not one of 'simple', 'conv', 'lstm'.