How to set the default option as -h for Python click?
By default, my script, shows nothing when no arguments is given to the duh.py:
import click
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
#click.command(context_settings=CONTEXT_SETTINGS)
#click.option('--toduhornot', is_flag=True, help='prints "duh..."')
def duh(toduhornot):
if toduhornot:
click.echo('duh...')
if __name__ == '__main__':
duh()
[out]:
$ python3 test_click.py -h
Usage: test_click.py [OPTIONS]
Options:
--toduhornot prints "duh..."
-h, --help Show this message and exit.
$ python3 test_click.py --toduhornot
duh...
$ python3 test_click.py
Question:
As shown above, the default prints no information python3 test_click.py.
Is there a way such that, the default option is set to -h if no arguments is given, e.g.
$ python3 test_click.py
Usage: test_click.py [OPTIONS]
Options:
--toduhornot prints "duh..."
-h, --help Show this message and exit.
As of version 7.1, one can simply specify #click.command(no_args_is_help=True).
Your structure is not the recommended one, you should use:
import click
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
#click.group(context_settings=CONTEXT_SETTINGS)
def cli():
pass
#cli.command(help='prints "duh..."')
def duh():
click.echo('duh...')
if __name__ == '__main__':
cli()
And then python test_click.py will print help message:
Usage: test_click.py [OPTIONS] COMMAND [ARGS]...
Options:
-h, --help Show this message and exit.
Commands:
duh prints "duh..."
So you can use python test_click.py duh to call duh.
Update
import click
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
#click.command(context_settings=CONTEXT_SETTINGS)
#click.option('--toduhornot', is_flag=True, help='prints "duh..."')
def duh(toduhornot):
if toduhornot:
click.echo('duh...')
else:
with click.Context(duh) as ctx:
click.echo(ctx.get_help())
if __name__ == '__main__':
duh()
If you inherit from click.Command and override the parse_args() method, you can create a custom class to default to help like:
Custom Class
import click
class DefaultHelp(click.Command):
def __init__(self, *args, **kwargs):
context_settings = kwargs.setdefault('context_settings', {})
if 'help_option_names' not in context_settings:
context_settings['help_option_names'] = ['-h', '--help']
self.help_flag = context_settings['help_option_names'][0]
super(DefaultHelp, self).__init__(*args, **kwargs)
def parse_args(self, ctx, args):
if not args:
args = [self.help_flag]
return super(DefaultHelp, self).parse_args(ctx, args)
Using Custom Class:
To use the custom class, pass the cls parameter to #click.command() decorator like:
#click.command(cls=DefaultHelp)
How does this work?
This works because click is a well designed OO framework. The #click.command() decorator usually instantiates a
click.Command object but allows this behavior to be over ridden with the cls parameter. So it is a relatively
easy matter to inherit from click.Command in our own class and over ride the desired methods.
In this case we over-ride click.Command.parse_args() and check for an empty argument list. If it is empty then we invoke the help. In addition this class will default the help to ['-h', '--help'] if it is not otherwise set.
Test Code:
#click.command(cls=DefaultHelp)
#click.option('--toduhornot', is_flag=True, help='prints "duh..."')
def duh(toduhornot):
if toduhornot:
click.echo('duh...')
if __name__ == "__main__":
commands = (
'--toduhornot',
'',
'--help',
'-h',
)
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)
duh(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)]
-----------
> --toduhornot
duh...
-----------
>
Usage: test.py [OPTIONS]
Options:
--toduhornot prints "duh..."
-h, --help Show this message and exit.
-----------
> --help
Usage: test.py [OPTIONS]
Options:
--toduhornot prints "duh..."
-h, --help Show this message and exit.
-----------
> -h
Usage: test.py [OPTIONS]
Options:
--toduhornot prints "duh..."
-h, --help Show this message and exit.
Easiest approach I've found
import click
#click.command()
#click.option('--option')
#click.pass_context
def run(ctx, option):
if not option:
click.echo(ctx.get_help())
ctx.exit()
Related
I'm trying to figure out how to get help for global options shown in the help messages of sub-commands.
I created a simplistic cli:
#!/usr/bin/env python
import click
#click.group()
#click.option("-l", "--log-level", help="Set log level.")
def cli(log_level):
"CLI toolbox"
print("root")
#cli.group()
def admin():
print("admin")
#admin.command()
def invite():
print("invite")
if __name__ == "__main__":
cli()
Unfortunately the global options are not shown on the help screens of sub commands:
./cli.py --help
Usage: cli.py [OPTIONS] COMMAND [ARGS]...
CLI toolbox
Options:
-l, --log-level TEXT Set log level. <-- Option listed on global command
--help Show this message and exit.
Commands:
admin
./cli.py admin --help
root
Usage: cli.py admin [OPTIONS] COMMAND [ARGS]...
Options:
<- Option missing on command.
--help Show this message and exit.
Commands:
invite
This is by no means pretty, but it gets the options by defining a custom group so we can override the help message. It then also iterates through all subcommands, but that has been hardcoded to only reference that particular group, rather than programatically infer it.
import click
#click.group()
#click.option("-l", "--log-level", help="Set log level.")
def cli(log_level):
"CLI toolbox"
print("root")
class CustomHelpGroup(click.Group):
def format_help(self, ctx, formatter):
parent = ctx.parent
help_text = ['Greetings! Options:']
for param in parent.command.get_params(ctx):
help_text.append(' '.join(param.get_help_record(parent)))
help_text.append("\n" + ctx.get_usage() + "\n")
help_text.append('Commands:\n')
help_text.extend([f'{command_name}' for command_name, command in admin.commands.items()])
formatter.write('\n'.join(help_text))
#cli.group(cls=CustomHelpGroup)
def admin():
print("admin")
#admin.command()
def invite():
print("invite")
if __name__ == "__main__":
cli()
In my python-click-CLI script, I'm building some commands for features that should not be visible for users (to not confuse them), but visible for e.g. developers.
Is it possible to use feature flags for Python-click commands?
I would like to be able to configure (via a config file, etc) if a command is available or not. If a command-feature is disabled, the command should not be callable and the help should not show it.
Like this:
FLAG_ENABLED = False
# This command should not be shown and not be callable as long as the flag is disabled
#cli.command(name='specialfeature', active=FLAG_ENABLED)
def special_feature_command()
....
Obviously, I could change the body of my function:
#cli.command(name='specialfeature', active=FLAG_ENABLED)
def special_feature_command()
if FLAG_ENABLED:
...
else:
...
But then my command would still show up in help, which I would like to avoid.
You can add a feature flag functionality with a custom class like:
Custom Class
This class over rides the click.Group.command() method which is used to decorate command functions. It adds the ability to pass an active flag, which when False will skip adding the command to the group.
import click
class FeatureFlagCommand(click.Group):
def command(self, *args, active=True, **kwargs):
"""Behaves the same as `click.Group.command()` except added an
`active` flag which can be used to disable to command.
"""
if active:
return super(FeatureFlagCommand, self).command(*args, **kwargs)
else:
return lambda f: f
Using the Custom Class
By passing the cls parameter to the click.group() decorator, any commands added to the group via the the group.command() will be gated with the active flag.
#click.group(cls=FeatureFlagCommand)
def cli():
...
#cli.command(name='specialfeature', active=FLAG_ENABLED)
def special_feature_command()
...
How does this work?
This works because click is a well designed OO framework. It is easy to inherit from click.Group and build a new command() decorator. In the new command() decorator if the active flag is False we return the undecorated function instead of adding the function to the group.
Test Code:
#click.group(cls=FeatureFlagCommand)
def cli():
"""My Awesome Click Program"""
#cli.command(active=False)
def command1():
click.echo('Command 1')
#cli.command(active=True)
def command2():
click.echo('Command 2')
#cli.command()
def command3():
click.echo('Command 3')
if __name__ == "__main__":
commands = (
'command1',
'command2',
'command3',
'--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)]
-----------
> command1
Usage: test.py [OPTIONS] COMMAND [ARGS]...
Error: No such command "command1".
-----------
> command2
Command 2
-----------
> command3
Command 3
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...
My Awesome Click Program
Options:
--help Show this message and exit.
Commands:
command2
command3
-----------
>
Usage: test.py [OPTIONS] COMMAND [ARGS]...
My Awesome Click Program
Options:
--help Show this message and exit.
Commands:
command2
command3
I'm trying to make a verbose flag for my Python program.
Currently, I'm doing this:
import click
#global variable
verboseFlag = False
#parse arguments
#click.command()
#click.option('--verbose', '-v', is_flag=True, help="Print more output.")
def log(verbose):
global verboseFlag
verboseFlag = True
def main():
log()
if verboseFlag:
print("Verbose on!")
if __name__ == "__main__":
main()
It'll never print "Verbose on!" even when I set the '-v' argument. My thoughts are that the log function needs a parameter, but what do I give it? Also, is there a way to check whether the verbose flag is on without global variables?
So click is not simply a command line parser. It also dispatches and processes the commands. So in your example, the log() function never returns to main(). The intention of the framework is that the decorated function, ie: log(), will do the needed work.
Code:
import click
#click.command()
#click.option('--verbose', '-v', is_flag=True, help="Print more output.")
def log(verbose):
click.echo("Verbose {}!".format('on' if verbose else 'off'))
def main(*args):
log(*args)
Test Code:
if __name__ == "__main__":
commands = (
'--verbose',
'-v',
'',
'--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)
main(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)]
-----------
> --verbose
Verbose on!
-----------
> -v
Verbose on!
-----------
>
Verbose off!
-----------
> --help
Usage: test.py [OPTIONS]
Options:
-v, --verbose Print more output.
--help Show this message and exit.
The above answer was helpful, but this is what I ended up using. I thought I'd share since so many people are looking at this question:
#click.command()
#click.option('--verbose', '-v', is_flag=True, help="Print more output.")
def main(verbose):
if verbose:
# do something
if __name__ == "__main__":
# pylint: disable=no-value-for-parameter
main()
I'm using Click to build a Python CLI and am running into an issue with how exceptions are handles in Click.
I'm not sure about the wording ("subcommand", "parentcommand") here but from my example you'll get the idea I hope. Let's assume this code:
#click.group()
#click.option("--something")
def mycli(something):
try:
#do something with "something" and set ctx
ctx.obj = {}
ctx.obj["somevar"] = some_result
except:
print("Something went wrong")
raise
#only if everything went fine call mycommand
#click.group()
#click.pass_context
def mygroup(ctx):
pass
#mygroup.command(name="mycommand")
#click.pass_context
def mycommand(ctx):
#this only works if somevar is set in ctx so don't call this if setting went wrong in mycli
When the application starts this is called:
if __name__ == "__main__":
mycli.add_command(mygroup)
mycli()
I then start the program like this:
python myapp --something somevalue mycommand
Expected behaviour: first mycli is called and the code in it is executed. If an exception is thrown it's caught by the except block, a message is printed and the exception is raised. Because we have no other try/except block this will result in termination of the script. The "sub"-command mycommand is never called because the program already terminated when running the "parent"-command mycli.
Actual behaviour: the exception is caughtand the message is printed, but mycommand is still called. It then fails with another exception message because the required context variable was not set.
How would I handle something like that? Basically I only want to call the subcommand mycommand only to be executed if everything in mycli went fine.
To handle the exception, but not continue onto the subcommands, you can simply call exit() like:
Code:
import click
#click.group()
#click.option("--something")
#click.pass_context
def mycli(ctx, something):
ctx.obj = dict(a_var=something)
try:
if something != '1':
raise IndexError('An Error')
except Exception as exc:
click.echo('Exception: {}'.format(exc))
exit()
Test Code:
#mycli.group()
#click.pass_context
def mygroup(ctx):
click.echo('mygroup: {}'.format(ctx.obj['a_var']))
pass
#mygroup.command()
#click.pass_context
def mycommand(ctx):
click.echo('mycommand: {}'.format(ctx.obj['a_var']))
if __name__ == "__main__":
commands = (
'mygroup mycommand',
'--something 1 mygroup mycommand',
'--something 2 mygroup mycommand',
'--help',
'--something 1 mygroup --help',
'--something 1 mygroup mycommand --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)
mycli(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)]
-----------
> mygroup mycommand
Exception: An Error
-----------
> --something 1 mygroup mycommand
mygroup: 1
mycommand: 1
-----------
> --something 2 mygroup mycommand
Exception: An Error
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...
Options:
--something TEXT
--help Show this message and exit.
Commands:
mygroup
-----------
> --something 1 mygroup --help
Usage: test.py mygroup [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
mycommand
-----------
> --something 1 mygroup mycommand --help
mygroup: 1
Usage: test.py mygroup mycommand [OPTIONS]
Options:
--help Show this message and exit.
I am going to write something very basic so as to explain what I'm looking to make happen. I have written some code to do some interesting WordPress administration. The program will create instances but also create https settings for apache.
What I would like it to do and where I'm having a problem:
(If you run a help on the wp cli you will see exactly what I want to have happen...but I'm not a developer so I'd like some help)
python3 testcommands.py --help
Usage: testcommands.py [OPTIONS] COMMAND [ARGS]...
This is the help
Options:
--help Show this message and exit.
Commands:
https Commands for HTTPS
wp Commands for WP
python3 testcommands.py https --help
Usage: testcommands.py [OPTIONS] COMMAND [ARGS]...
This is the help
Options:
--help Show this message and exit.
Commands:
create Creates config for HTTPS
sync Syncs config for Apache
My basic code:
import click
#click.group()
def cli1():
pass
"""First Command"""
#cli1.command('wp')
def cmd1():
"""Commands for WP"""
pass
#cli1.command('install')
#click.option("--test", help="This is the test option")
def install():
"""Test Install for WP"""
print('Installing...')
#click.group()
def cli2():
pass
"""Second Command"""
#cli2.command('https')
def cmd2():
click.echo('Test 2 called')
wp = click.CommandCollection(sources=[cli1, cli2], help="This is the help")
if __name__ == '__main__':
wp()
Returns:
python3 testcommands.py --help
Usage: testcommands.py [OPTIONS] COMMAND [ARGS]...
This is the help
Options:
--help Show this message and exit.
Commands:
https
install Test Install for WP
wp Commands for WP
I can't figure this out. I don't want install to show here, as it should be underneath wp so as not to show.
Thank you if you can help me out...I'm sure it is simple...or maybe not possible...but thanks anyways.
I was able to figure it out once I found a site that was trying to do the same thing.
https://github.com/chancez/igor-cli
I couldn't figure out the name...but I was looking for a way to do HIERARCHICAL commands.
Here is the base code:
import click
#click.group()
#click.pass_context
def main(ctx):
"""Demo WP Control Help"""
#main.group()
#click.pass_context
def wp(ctx):
"""Commands for WP"""
#wp.command('install')
#click.pass_context
def wp_install(ctx):
"""Install WP instance"""
#wp.command('duplicate')
#click.pass_context
def wp_dup(ctx):
"""Duplicate WP instance"""
#main.group()
#click.pass_context
def https(ctx):
"""Commands for HTTPS"""
#https.command('create')
#click.pass_context
def https_create(ctx):
"""Create HTTPS configuration"""
#https.command('sync')
#click.pass_context
def https_sync(ctx):
"""Sync HTTPS configuration with Apache"""
if __name__ == '__main__':
main(obj={})