Skip to content

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'.