Reputation: 4495
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
?
Upvotes: 6
Views: 3114
Reputation: 598
I'm not sure if this is considered best practice, but another way to achieve the same effect is to use click.get_current_context()
as part of a function that computes the default value:
@click.command()
@click.option(
"--param1",
type=str,
prompt="First parameter",
)
@click.option(
"--param2",
type=str,
prompt="Second parameter",
default=lambda: myfunct(click.get_current_context().params.get("param1", None)),
)
def cmd(param1, param2):
pass
Upvotes: 1
Reputation: 49794
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, **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
###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")
cls
need to reference the custom class.
default_option
needs to specify which option will be passed to the default
callable.
default
specifies 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.
Upvotes: 3