Python – Click – Dynamic default value based on hints for additional options

Click – Dynamic default value based on hints for additional options… here is a solution to the problem.

Click – Dynamic default value based on hints for additional options

I’m building the CLI interface using Click. Click on the offerdynamic defaults for prompts, which is great. Also this example gives some insight into how to implement dynamic defaults using custom click classes, providing more flexible options when evaluating defaults.

What I want to do now is get dynamic defaults based on the click option provided by the other, eg

python mymodule --param1 something --param2 somethingelse

Now, if param2 is empty, I’d like to try to get the dynamic default based on the param1 input provided, for example:

@click.command()
@click.option('--param1', prompt=True)
@click.option('--param2', prompt=True, default=lambda: myfunct(param1))
def cmd(param1, param2):
    pass

myfunct(param1:str=None):
    param2 = None
    #get param2 based on param1 input
    return param2

Any ideas on what is the best way to get this done?
Is param1 guaranteed to be evaluated (and hinted) before param2?

Solution

Extensionexample you referencedThe required functionality can be done like this:

#

##自定义类:

import click

class OptionPromptNull(click. Option):
    _value_key = '_default_val'

def __init__(self, *args, **kwargs):
        self.default_option = kwargs.pop('default_option', None)
        super(OptionPromptNull, self).__init__(*args, **kwargs)

def get_default(self, ctx, **kwargs):
        if not hasattr(self, self._value_key):
            if self.default_option is None:
                default = super(OptionPromptNull, self).get_default(ctx, **kwargs)
            else:
                arg = ctx.params[self.default_option]
                default = self.type_cast_value(ctx, self.default(arg))
            setattr(self, self._value_key, default)
        return getattr(self, self._value_key)

def prompt_for_value(self, ctx):
        default = self.get_default(ctx)

# only prompt if the default value is None
        if default is None:
            return super(OptionPromptNull, self).prompt_for_value(ctx)

return default

#

##使用自定义类:

To use a custom class, you need to pass three parameters to the click.option decorator, for example:

@click.option('--param3', cls=OptionPromptNull, default_option='param1',
              default=lambda x: get_a_value(x), prompt="Enter Param3")
  • CLS needs to reference a custom class.

  • default_option needs to specify which option to pass to the default callable.

  • default

  • specifies the callable used to get the default value.

#

##这是如何工作的?

This works because click is a well-designed OO framework. @click.option() decorators usually instantiate a click. Option object, but allows this behavior to be overridden with the cls parameter. Therefore, inheriting click. Option and override the required method is a relatively easy thing to do.

In this case, we override click. Option.get_default() and click. Option.prompt_for_value() method. In prompt_for_value(), we only prompt when the default value is None. Then in get_default(), we call the default function to pass the required (previously entered) arguments.

To clarify part of the problem, evaluate options first: in the order in which they are passed on the command line

, and second: in the order in which they are declared for those options that are not passed on the command line.

#

##测试代码:

@click.command()
@click.option('--param1', prompt="Enter Param1")
@click.option('--param2', cls=OptionPromptNull,
              default=lambda: get_value_none(), prompt="Enter Param2")
@click.option('--param3', cls=OptionPromptNull, default_option='param1',
              default=lambda x: get_a_value(x), prompt="Enter Param3")
def cli(param1, param2, param3):
    click.echo("param1: '{}'".format(param1))
    click.echo("param2: '{}'".format(param2))
    click.echo("param3: '{}'".format(param3))

def get_value_none():
    return None

def get_a_value(val):
    return val

if __name__ == "__main__":
    commands = (
        r'',
        r'--param3 5',
        '--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

#

##结果:

Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> 
Enter Param1: 3
Enter Param2: 4
param1: '3'
param2: '4'
param3: '3'
-----------
> --param3 5
Enter Param1: 3
Enter Param2: 4
param1: '3'
param2: '4'
param3: '5'
-----------
> --help
Usage: test.py [OPTIONS]

Options:
  --param1 TEXT
  --param2 TEXT
  --param3 TEXT
  --help         Show this message and exit.

Related Problems and Solutions