问题
I'm using Click to build a CLI interface. Click offers dynamic defaults for prompts, which is great. Also this example gives some insights on how to implement dynamic defaults using a custom click-class and thus provide more flexible options when evaluating the default value.
What I'm trying to do now is to have dynamic defaults based on another provided click option, e.g.
python mymodule --param1 something --param2 somethingelse
Now if param2 is empty I want to try to get a dynamic default based on the provided param1 input, e.g.:
@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 would be the best way to get that done?
And is it guaranteed that param1 is evaluated (and prompted for) before param2?
回答1:
Extending the example you referenced the desired functionality can be done like:
Custom Class:
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):
if not hasattr(self, self._value_key):
if self.default_option is None:
default = super(OptionPromptNull, self).get_default(ctx)
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
Using the Custom Class:
To use the custom class you need to pass three parameters to the click.option decorator like:
@click.option('--param3', cls=OptionPromptNull, default_option='param1',
default=lambda x: get_a_value(x), prompt="Enter Param3")
clsneed to reference the custom class.default_optionneeds to specify which option will be passed to thedefaultcallable.defaultspecifies the callable used to get the default.
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 desired methods.
In this case we over ride the click.Option.get_default() and the click.Option.prompt_for_value() methods. In prompt_for_value() we only prompt if the default is None. And then in get_default() we call the default function passing the desired (previously entered) param.
And to clarify one part of the question, the options are evaluated first: in the order they were passed on the command line, and second: in the order they were declared for those that were not passed on the command line.
Test Code:
@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
Results:
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.
来源:https://stackoverflow.com/questions/51846634/click-dynamic-defaults-for-prompts-based-on-other-options