Using a custom class which inherits from click.Option
, you can intercept the option processing and display the desired menu, and then validate the response like:
Custom Class
import click
class EnumMenuPromptFromDict(click.Option):
def __init__(self, *args, **kwargs):
super(EnumMenuPromptFromDict, self).__init__(*args, **kwargs)
if 'prompt' not in kwargs:
raise TypeError(
"'prompt' keyword required for '{}' option".format(
args[0][0]))
self.choices_dict = self.prompt
self.prompt_menu = '\n'.join('[{}] {}'.format(i + 1, name)
for i, name in enumerate(self.prompt))
self.prompt = 'Choose from,\n{}\n{}'.format(
self.prompt_menu, self.name)
def prompt_for_value(self, ctx):
"""Get entered value and then validate"""
while True:
value = super(EnumMenuPromptFromDict, self).prompt_for_value(ctx)
try:
choice = int(value)
if choice > 0:
return list(self.choices_dict)[choice - 1]
except (ValueError, IndexError):
if value in self.choices_dict:
return value
click.echo('Error: {} is not a valid choice'.format(value))
def full_process_value(self, ctx, value):
"""Convert the entered value to the value from the choices dict"""
value = super(EnumMenuPromptFromDict, self).full_process_value(
ctx, value)
try:
return self.choices_dict[value]
except (KeyError, IndexError):
raise click.UsageError(
"'{}' is not a valid choice".format(value), ctx)
Using Custom Class:
To use the custom class, pass the cls
parameter to @click.option()
decorator like:
@click.option('--name', cls=EnumMenuPromptFromDict, prompt=titles)
where the prompt
is a dict of choices like:
titles = OrderedDict((
('Karen', 'CEO'),
('Bob', 'CFO'),
('Jo', 'COO'),
('Steve', 'CIO')
))
How does this work?
This works because click is a well designed OO framework. The @click.option()
decorator usually instantiates a click.Option
object but allows this behavior to be over ridden with the cls
parameter. So it is a relatively easy matter to inherit from click.Option
in our own class and over ride the desired methods.
In this case we over ride click.Option.prompt_for_value()
to intercept the command processing and allow the Menu number or Value to be entered. We also over ride click.Option.full_process_value()
to convert the name into the title.
Test Code:
from collections import OrderedDict
titles = OrderedDict((
('Karen', 'CEO'),
('Bob', 'CFO'),
('Jo', 'COO'),
('Steve', 'CIO')
))
@click.command()
@click.option('--name', cls=EnumMenuPromptFromDict, prompt=titles)
def cli(name):
"""The Great CLI APP"""
click.echo('a title: %s' % name)
if __name__ == "__main__":
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
cli([])