We can use a --yes option to skip the confirmation in the eager callback by creating a custom click.Option class.
Custom Class
def confirm_eager_option(*args, confirm_skip_flag_name='yes'):
class ConfirmEagerOption(click.Option):
def __init__(self, param_decls, **kwargs):
assert not param_decls, 'pass options names into confirm_eager_option()'
self.original_callback = kwargs['callback']
kwargs['callback'] = self.callback_handler
kwargs['is_eager'] = True
kwargs['is_flag'] = True
kwargs['expose_value'] = False
super().__init__(args, **kwargs)
def handle_parse_result(self, ctx, opts, args):
# grab the parsed options for later reference
self.parse_opts = opts
return super().handle_parse_result(ctx, opts, args)
def callback_handler(self, ctx, option, value):
if value and not self.parse_opts.get('help'):
# If we have the confirm flag, then set value to None
if self.parse_opts.get(confirm_skip_flag_name):
value = None
self.original_callback(ctx, option, value)
# build a decorator with our skip flag
confirm_skip_option = click.option(
f'--{confirm_skip_flag_name}', is_flag=True, expose_value=False, is_eager=True,
help=f"Skip confirmation for {args[0]}"
)
return ConfirmEagerOption
Using the Custom Class:
To use the custom class, first build the class by calling confirm_eager_option() and pass
it the option names, and the skip flag name like:
confirm_eager_option_cls = confirm_eager_option(
'--reset', confirm_skip_flag_name='yes')
Then pass the class as the cls argument of the click.option decorator, and add another
decorator for the skip option like::
@click.command()
@click.option(cls=confirm_eager_option_cls, callback=config_reset,
help="Reset the user configuration to the default.")
@confirm_eager_option_cls.confirm_skip_option
def cli():
...
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 override the handle_parse_result() method. The overridden method allows us to
capture the other parsed options, and then look at those options in the eager callback hook to decide if
skipping the confirmation is allowed.
Test Code:
confirm_eager_option_cls = confirm_eager_option(
'--reset', confirm_skip_flag_name='yes')
def config_reset(ctx, option, value):
if value is None or value:
if value:
click.echo('Confirming reset...')
# click.confirm("Are you sure?", abort=True)
click.echo('Resetting...')
@click.command()
@click.option(cls=confirm_eager_option_cls, callback=config_reset,
help="Reset the user configuration to the default.")
@confirm_eager_option_cls.confirm_skip_option
def cli():
click.echo('running command')
if __name__ == "__main__":
commands = (
'',
'--reset',
'--reset --yes',
'--help',
)
import sys, time
time.sleep(1)
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
for cmd in commands:
try:
time.sleep(0.1)
print('-----------')
print('> ' + cmd)
time.sleep(0.1)
cli(cmd.split())
except BaseException as exc:
if str(exc) != '0' and \
not isinstance(exc, (click.ClickException, SystemExit)):
raise
Test Results:
Click Version: 7.1.2
Python Version: 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)]
-----------
>
running command
-----------
> --reset
Confirming reset...
Resetting...
running command
-----------
> --reset --yes
Resetting...
running command
-----------
> --help
Usage: test_code.py [OPTIONS]
Options:
--reset Reset the user configuration to the default.
--yes Skip confirmation for --reset
--help Show this message and exit.