Types¶
A collection of custom Click parameter types for common use-cases.
EnumChoice¶
click.Choice is supporting Enums, but naively: the Enum.name property of each members is used for choices. It was designed that way to simplify the implementation, because it is the part of Enum that is guaranteed to be unique strings.
But this is not always what we want, especially when the Enum’s names are not user-friendly (e.g. they contain underscores, uppercase letters, etc.). This custom EnumChoice type solve this issue by allowing you to select which part of the Enum members to use as choice strings.
Click.Choice limits¶
Let’s start with a simple example to demonstrate the limitations of click.Choice. Starting with this Format definition:
from enum import Enum
class Format(Enum):
TEXT = "text"
HTML = "html"
OTHER_FORMAT = "other-format"
This Format gets standard names for each member:
>>> Format.TEXT.name
'TEXT'
>>> Format.HTML.name
'HTML'
>>> Format.OTHER_FORMAT.name
'OTHER_FORMAT'
But we made its values more user-friendly:
>>> Format.TEXT.value
'text'
>>> Format.HTML.value
'html'
>>> Format.OTHER_FORMAT.value
'other-format'
Now let’s combine this Enum with click.Choice into a simple CLI:
from enum import Enum
from click import command, option, echo, Choice
class Format(Enum):
TEXT = "text"
HTML = "html"
OTHER_FORMAT = "other-format"
@command
@option(
"--format",
type=Choice(Format),
show_choices=True,
default=Format.HTML,
show_default=True,
help="Select format.",
)
def cli(format):
echo(f"Selected format: {format!r}")
All Enum’s members are properly registered and recognized when using their name:
$ cli --format TEXT
Selected format: <Format.TEXT: 'text'>
$ cli --format HTML
Selected format: <Format.HTML: 'html'>
$ cli --format OTHER_FORMAT
Selected format: <Format.OTHER_FORMAT: 'other-format'>
However, using the value fails:
$ cli --format text
Usage: cli [OPTIONS]
Try 'cli --help' for help.
Error: Invalid value for '--format': 'text' is not one of 'TEXT', 'HTML', 'OTHER_FORMAT'.
$ cli --format html
Usage: cli [OPTIONS]
Try 'cli --help' for help.
Error: Invalid value for '--format': 'html' is not one of 'TEXT', 'HTML', 'OTHER_FORMAT'.
$ cli --format other-format
Usage: cli [OPTIONS]
Try 'cli --help' for help.
Error: Invalid value for '--format': 'other-format' is not one of 'TEXT', 'HTML', 'OTHER_FORMAT'.
This preference for Enum.name is also reflected in the help message, both for choices and default value:
$ cli --help
Usage: cli [OPTIONS]
Options:
--format [TEXT|HTML|OTHER_FORMAT]
Select format. [default: HTML]
--help Show this message and exit.
To change this behavior, we need EnumChoice.
Usage¶
Let’s use click_extra.EnumChoice instead of click.Choice, and then override the __str__ method of our Enum:
from enum import Enum
from click import command, option, echo
from click_extra import EnumChoice
class Format(Enum):
TEXT = "text"
HTML = "html"
OTHER_FORMAT = "other-format"
def __str__(self):
return self.value
@command
@option(
"--format",
type=EnumChoice(Format),
show_choices=True,
help="Select format.",
)
def cli(format):
echo(f"Selected format: {format!r}")
This renders into much better help messages:
$ cli --help
Usage: cli [OPTIONS]
Options:
--format [text|html|other-format]
Select format.
--help Show this message and exit.
User inputs are now matched against the str() representation:
$ cli --format other-format
Selected format: <Format.OTHER_FORMAT: 'other-format'>
And not the Enum.name:
$ cli --format OTHER_FORMAT
Usage: cli [OPTIONS]
Try 'cli --help' for help.
Error: Invalid value for '--format': 'OTHER_FORMAT' is not one of 'text', 'html', 'other-format'.
By customizing the __str__ method of the Enum, you have full control over how choices are displayed and matched.
Case-sensitivity¶
EnumChoice is case-insensitive by default, unlike click.Choice, so random casing are recognized:
$ cli --format oThER-forMAt
Selected format: <Format.OTHER_FORMAT: 'other-format'>
If you want to restore case-sensitive matching, you can enable it by setting the case_sensitive parameter to True:
from enum import Enum
from click import command, option, echo
from click_extra import EnumChoice
class Format(Enum):
TEXT = "text"
HTML = "html"
OTHER_FORMAT = "other-format"
def __str__(self):
return self.value
@command
@option(
"--format",
type=EnumChoice(Format, case_sensitive=True),
show_choices=True,
help="Select format.",
)
def cli(format):
echo(f"Selected format: {format!r}")
$ cli --format oThER-forMAt
Usage: cli [OPTIONS]
Try 'cli --help' for help.
Error: Invalid value for '--format': 'oThER-forMAt' is not one of 'text', 'html', 'other-format'.
Choice source¶
EnumChoice use the str() representation of each member by default. But you can configure it to select which part of the members to use as choice strings.
That’s done by setting the choice_source parameter to one of:
ChoiceSource.KEYorChoiceSource.NAMEto use the key (i.e. theEnum.nameproperty),ChoiceSource.VALUEto use theEnum.value, orChoiceSource.STRto use thestr()string representation (which is the default behavior).
Here is an example using ChoiceSource.KEY, which is equivalent to click.Choice behavior:
from enum import Enum
from click import command, option, echo
from click_extra import EnumChoice, ChoiceSource
class Format(Enum):
TEXT = "text"
HTML = "html"
OTHER_FORMAT = "other-format"
def __str__(self):
return self.value
@command
@option(
"--format",
type=EnumChoice(Format, choice_source=ChoiceSource.KEY),
show_choices=True,
help="Select format.",
)
def cli(format):
echo(f"Selected format: {format!r}")
So even though we still override the __str__ method, user inputs are now matched against the name:
$ cli --format OTHER_FORMAT
Selected format: <Format.OTHER_FORMAT: 'other-format'>
And not the str() representation:
$ cli --format other-format
Usage: cli [OPTIONS]
Try 'cli --help' for help.
Error: Invalid value for '--format': 'other-format' is not one of 'text', 'html', 'other_format'.
Still, as you can see above, the choice strings are lower-cased, as per EnumChoice default. And this is also reflected in the help message:
$ cli --help
Usage: cli [OPTIONS]
Options:
--format [text|html|other_format]
Select format.
--help Show this message and exit.
Tip
If you don’t want to import ChoiceSource, you can also pass the string values "key", "name", "value", or "str" to the choice_source parameter:
>>> choice_type = EnumChoice(Format, choice_source="key")
>>> choice_type
EnumChoice('TEXT', 'HTML', 'OTHER_FORMAT')
Custom choice source¶
In addition to the built-in choice sources detailed above, you can also provide a custom callable to the choice_source parameter. This callable should accept an Enum member and return the corresponding string to use as choice.
This is practical when you want to use a specific attribute or method of the Enum members as choice strings. Here’s an example:
from enum import Enum
from click import command, option, echo
from click_extra import EnumChoice
class Format(Enum):
TEXT = "text"
HTML = "html"
OTHER_FORMAT = "other-format"
def display_name(self):
return f"custom-{self.value}"
@command
@option(
"--format",
type=EnumChoice(Format, choice_source=getattr(Format, "display_name")),
show_choices=True,
help="Select format.",
)
def cli(format):
echo(f"Selected format: {format!r}")
$ cli --help
Usage: cli [OPTIONS]
Options:
--format [custom-text|custom-html|custom-other-format]
Select format.
--help Show this message and exit.
Default value¶
Another limit of click.Choice is how the default value is displayed in help messages. Click is hard-coded to use the Enum.name in help messages for the default value.
To fix this limitation, you have to use EnumChoice with @click_extra.option or @click_extra.argument decorators, which override Click’s default help formatter to properly display the default value according to the choice strings.
For example, using @click_extra.option:
from enum import Enum
import click
import click_extra
class Format(Enum):
TEXT = "text"
HTML = "html"
OTHER_FORMAT = "other-format"
def __str__(self):
return self.value
@click.command
@click_extra.option(
"--format",
type=click_extra.EnumChoice(Format),
show_choices=True,
default=Format.HTML,
show_default=True,
help="Select format.",
)
def cli(format):
click.echo(f"Selected format: {format!r}")
This renders into much better help messages, where the default value is displayed using the choice strings:
$ cli --help
Usage: cli [OPTIONS]
Options:
--format [text|html|other-format]
Select format. [default: html]
--help Show this message and exit.
Warning
Without Click Extra’s @option or @argument, Click’s default help formatter is used, which always displays the default value using the Enum.name, even when using EnumChoice:
from enum import Enum
import click
import click_extra
class Format(Enum):
TEXT = "text"
HTML = "html"
OTHER_FORMAT = "other-format"
def __str__(self):
return self.value
@click.command
@click.option(
"--format",
type=click_extra.EnumChoice(Format),
show_choices=True,
default=Format.HTML,
show_default=True,
help="Select format.",
)
def cli(format):
click.echo(f"Selected format: {format!r}")
See the unmatched default value in the help message:
$ cli --help
Usage: cli [OPTIONS]
Options:
--format [text|html|other-format]
Select format. [default: HTML]
--help Show this message and exit.
You can still work around this limitation by forcing the default value:
from enum import Enum
import click
import click_extra
class Format(Enum):
TEXT = "text"
HTML = "html"
OTHER_FORMAT = "other-format"
def __str__(self):
return self.value
@click.command
@click.option(
"--format",
type=click_extra.EnumChoice(Format),
show_choices=True,
default=str(Format.HTML),
show_default=True,
help="Select format.",
)
def cli(format):
click.echo(f"Selected format: {format!r}")
$ cli --help
Usage: cli [OPTIONS]
Options:
--format [text|html|other-format]
Select format. [default: html]
--help Show this message and exit.
Aliases¶
EnumChoice also supports aliases on both names and values.
Here’s an example using aliases:
from enum import Enum
from click import command, option, echo
from click_extra import EnumChoice
class State(Enum):
NEW = "new"
IN_PROGRESS = "in_progress"
ONGOING = "in_progress" # Alias for IN_PROGRESS
COMPLETED = "completed"
# Dynamiccally add names and values aliases.
State.NEW._add_alias_("fresh") # Alias for NEW
State.COMPLETED._add_value_alias_("done") # Alias for COMPLETED
@command
@option(
"--state",
type=EnumChoice(State, choice_source="name"),
show_choices=True,
)
@option(
"--state-name",
type=EnumChoice(State, choice_source="name", show_aliases=True),
show_choices=True,
)
@option(
"--state-value",
type=EnumChoice(State, choice_source="value", show_aliases=True),
show_choices=True,
)
def cli(state, state_name, state_value):
echo(f"Selected state: {state!r}")
echo(f"Selected state-name: {state_name!r}")
echo(f"Selected state-value: {state_value!r}")
You can now see the name aliases ongoing and fresh are now featured in the help message if show_aliases=True, as well as the value alias done:
$ cli --help
Usage: cli [OPTIONS]
Options:
--state [new|in_progress|completed]
--state-name [new|in_progress|ongoing|completed|fresh]
--state-value [new|in_progress|completed|done]
--help Show this message and exit.
And both names and values aliases are properly recognized, and normalized to their corresponding canonocal Enum members:
$ cli --state in_progress --state-name ongoing --state-value done
Selected state: <State.IN_PROGRESS: 'in_progress'>
Selected state-name: <State.IN_PROGRESS: 'in_progress'>
Selected state-value: <State.COMPLETED: 'completed'>
click_extra.types API¶
classDiagram
Choice <|-- EnumChoice
Enum <|-- ChoiceSource
- class click_extra.types.ChoiceSource(*values)[source]¶
Bases:
EnumSource of choices for
EnumChoice.- KEY = 'key'¶
- NAME = 'name'¶
- VALUE = 'value'¶
- STR = 'str'¶
- class click_extra.types.EnumChoice(choices, case_sensitive=False, choice_source=ChoiceSource.STR, show_aliases=False)[source]¶
Bases:
ChoiceChoice type for
Enum.Allows to select which part of the members to use as choice strings, by setting the
choice_sourceparameter to one of:ChoiceSource.KEYorChoiceSource.NAMEto use the key (i.e. thenameproperty),ChoiceSource.VALUEto use thevalue,ChoiceSource.STRto use thestr()string representation, orA custom callable that takes an
Enummember and returns a string.
Default to
ChoiceSource.STR, which makes you to only have to define the__str__()method on yourEnumto produce beautiful choice strings.Same as
click.Choice, but takes anEnumaschoices.Also defaults to case-insensitive matching.
- choices: tuple[str, ...]¶
The strings available as choice.
Hint
Contrary to the parent
Choiceclass, we store choices directly as strings, not theEnummembers themselves. That way there is no surprises when displaying them to the user.This trick bypass
Enum-specific code path in the Click library. Because, after all, a terminal environment only deals with strings: arguments, parameters, parsing, help messages, environment variables, etc.
- get_choice_string(member)[source]¶
Derivate the choice string from the given
Enum’smember.- Return type: