Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Click password option only if argument equals something

In click, I'm defining this command:

@click.command('time', short_help='Timesheet Generator')
@click.argument('time_command', type=click.Choice(['this', 'last']))
@click.argument('data_mode', type=click.Choice(['excel', 'exchange']), default='exchange')
@click.option('--password', prompt=True, hide_input=True, confirmation_prompt=False)
@pass_context
def cli(ctx, time_command, data_mode, password):

The issue I have is that I only want the password to prompt if the data_mode argument equals exchange. How can I pull this off?

like image 855
scphantm Avatar asked Mar 01 '16 15:03

scphantm


1 Answers

We can remove the need for a prompt if another parameter does not match a specific value by building a custom class derived from click.Option, and in that class over riding the click.Option.handle_parse_result() method like:

Custom Class:

import click

def PromptIf(arg_name, arg_value):

    class Cls(click.Option):

        def __init__(self, *args, **kwargs):
            kwargs['prompt'] = kwargs.get('prompt', True)
            super(Cls, self).__init__(*args, **kwargs)

        def handle_parse_result(self, ctx, opts, args):
            assert any(c.name == arg_name for c in ctx.command.params), \
                "Param '{}' not found for option '{}'".format(
                    arg_name, self.name)

            if arg_name not in opts:
                raise click.UsageError(
                    "Illegal usage: `%s` is a required parameter with" % (
                        arg_name))

            # remove prompt from 
            if opts[arg_name] != arg_value:
                self.prompt = None

            return super(Cls, self).handle_parse_result(ctx, opts, args)

    return Cls

Using Custom Class:

To use the custom class, pass the cls parameter to click.option decorator like:

@click.option('--an_option', cls=PromptIf('an_argument', 'an_arg_value'))

pass in the name of the parameter to examine for the desired value, and the value to check for.

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 overridden 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.handle_parse_result() and disable the need to prompt if the other specified parameter does not match the desired.

Note: This answer was inspired by this answer.

Test Code:

@click.command()
@click.argument('an_argument', type=click.Choice(['excel', 'exchange']),
                default='exchange')
@click.option('--password', hide_input=True, confirmation_prompt=False,
              cls=PromptIf('an_argument', 'exchange'))
def cli(an_argument, password):
    click.echo(an_argument)
    click.echo(password)

cli('exchange'.split())
like image 194
Stephen Rauch Avatar answered Oct 19 '22 18:10

Stephen Rauch