Python click and --help option callback isn't eager - python

I have the following click code:
#click.group(invoke_without_command=True)
def cli():
click.echo("Starting CallFlow....")
setup_logging()
# ##################----GEN---##################
#cli.command(help="a sub command")
#click.option(
"--folder", help="Tests folder path", type=str, nargs=1,
)
def sub1(folder):
# run some code here
Running my prog name that uses the above cli like this:
prog-name --help
shows me the correct help text:
Usage: prog-name [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
sub1 Help text
But running
prog-name sub1 --help --folder
I get an error that folder requires an argument like so:
Error: --folder option requires an argument
I thought that --help was an eager parameter and gets evaluated first. shouldn't that produce a help text?

From the documentation, the concept of eagerness refers only to the order of execution. Usually, the command line options will be processed in the order they are defined; making options like --help and --version eager means that they will be evaluated first.
If --help were not eager, your example would require --folder to always be passed first, like:
prog-name sub1 --folder test_folder --help

Related

argparse sub-commands and groups: Setting help-dialog in sub-command on its own group without being hidden in top-level help-dialog

I'm trying to achieve a program that makes both use of sub-commands (e.g.: program sub-command [options]) and groups (which makes for a fancy help dialog).
I have achieved this goal with one minor exception: In order to get the help dialog in its own group I must add the flag add_help=False when creating the sub-command parser, which removes the help message when running the top-level help dialog (e.g.: program -h).
Here's the code I've developed:
# imports
import argparse
# create the top-level parser
parser = argparse.ArgumentParser(prog="example", add_help=False, epilog="A very cool program")
# add top-level groups
toplevel = parser.add_argument_group("Global arguments")
toplevel.add_argument("-g", "--global", action="store_true", help="A global argument.")
help = parser.add_argument_group("Help dialog")
help.add_argument("-h", "--help", action="help", default=argparse.SUPPRESS, help="Show this help message and exit.")
# create subparser
subparsers = parser.add_subparsers(title="Available subcommands", dest="subcommand")
# create the parser for the "a" subcommand
parser_a = subparsers.add_parser("a", add_help=False)
# add groups for subcommand "a"
required_a = parser_a.add_argument_group("Required arguments")
required_a.add_argument("--bar", type=int, help="Flag bar help", required=True)
help_a = parser_a.add_argument_group("Help dialog")
help_a.add_argument("-h", "--help", action="help", default=argparse.SUPPRESS, help="Show this help message and exit.")
# create the parser for the "b" command
parser_b = subparsers.add_parser("b", add_help=False)
# add groups for subcommand "b"
required_b = parser_b.add_argument_group("Required arguments")
required_b.add_argument("--baz", help="Flag baz help", required=True)
optional_b = parser_b.add_argument_group("Optional arguments")
optional_b.add_argument("--tas", help="Flag tas help")
help_b = parser_b.add_argument_group("Help dialog")
help_b.add_argument("-h", "--help", action="help", default=argparse.SUPPRESS, help="Show this help message and exit.")
# parse arguments
args = parser.parse_args()
# provide args to main
print(args)
The top-level help is as follows:
$ example -h
usage: example [-g] [-h] {a,b} ...
Global arguments:
-g, --global A global argument.
Help dialog:
-h, --help Show this help message and exit.
Available sub-commands:
{a,b}
A very cool program
Which as you can see doesn't show the help message on the sub-commands.
To have them show up I would have to get rid of add_help=False when creating parser_a and parser_b but then, as expected, it would raise an issue when I would define my own help flag.
Essentially I would like to have the best of both worlds, where my main dialog would be:
$ example -h
usage: example [-g] [-h] {a,b} ...
Global arguments:
-g, --global A global argument.
Help dialog:
-h, --help Show this help message and exit.
Available sub-commands:
{a,b}
a Help for sub-command a.
b Help for sub-command b.
A very cool program
And the sub-commands would be:
$ example a -h
usage: example a --bar BAR [-h]
Required arguments:
--bar BAR Flag bar help
Help dialog:
-h, --help Show this help message and exit.
After going through the argparse source code, would the option conflict_handler be a possible solution? Would it be possible to tell it to ignore the original help dialog, which shows under positional arguments which I do not want, and have it instead showing in my own group but without disabling it completely?
TL;DR: Looking for modifications required to my Python script such that argparse outputs the two previous code blocks.
Note: The reason why "help-dialog" is written in the title is because stack overflow does not allow me to write the world "help" on the title, regardless of where it is written in the sentence.
Your code produces:
1138:~/mypy$ python3 stack69633930.py -h
usage: example [-g] [-h] {a,b} ...
Global arguments:
-g, --global A global argument.
Help dialog:
-h, --help Show this help message and exit.
Available subcommands:
{a,b}
A very cool program
1140:~/mypy$ python3 stack69633930.py a -h
usage: example a --bar BAR [-h]
Required arguments:
--bar BAR Flag bar help
Help dialog:
-h, --help Show this help message and exit.
With the conventional help
1140:~/mypy$ python3 stack69633930-1.py -h
usage: example [-h] [-g] {a,b} ...
optional arguments:
-h, --help show this help message and exit
Global arguments:
-g, --global A global argument.
Available subcommands:
{a,b}
A very cool program
1142:~/mypy$ python3 stack69633930-1.py a -h
usage: example a [-h] --bar BAR
optional arguments:
-h, --help show this help message and exit
Required arguments:
--bar BAR Flag bar help
The only differences I see are "Help dialog" group instead of "optional arguments", and the place of the [-h] in the usage.
To get 'help' for the subcommands:
1155:~/mypy$ python3 stack69633930-1.py -h
usage: example [-h] [-g] {a,b} ...
optional arguments:
-h, --help show this help message and exit
Global arguments:
-g, --global A global argument.
Available subcommands:
{a,b}
a help for subcommand a
b help for subcommand b
A very cool program
add
parser_a = subparsers.add_parser("a", help='help for subcommand a')
That works with your help-group as well.
===
The add_parser method handles the "help" keyword in a special way. "add_help" is passed on the ArgumentParser creation function. They have to very different functions.
def add_parser(self, name, **kwargs):
# set prog from the existing prefix
if kwargs.get('prog') is None:
kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
aliases = kwargs.pop('aliases', ())
# create a pseudo-action to hold the choice help
if 'help' in kwargs:
help = kwargs.pop('help')
choice_action = self._ChoicesPseudoAction(name, aliases, help)
self._choices_actions.append(choice_action)
# create the parser and add it to the map
parser = self._parser_class(**kwargs)
self._name_parser_map[name] = parser
# make parser available under aliases also
for alias in aliases:
self._name_parser_map[alias] = parser
return parser

Create help option for a python script [duplicate]

I am trying to make my python script very user friendly, so I like to write some sort of help for it. What is your advise for this? I could just put in some logic that if the user passed help as a paramater to the script, they get help. Is there a best practise or convention for this?
Use argparse.
For example, with test.py:
import argparse
parser=argparse.ArgumentParser(
description='''My Description. And what a lovely description it is. ''',
epilog="""All is well that ends well.""")
parser.add_argument('--foo', type=int, default=42, help='FOO!')
parser.add_argument('bar', nargs='*', default=[1, 2, 3], help='BAR!')
args=parser.parse_args()
Running
% test.py -h
yields
usage: test.py [-h] [--foo FOO] [bar [bar ...]]
My Description. And what a lovely description it is.
positional arguments:
bar BAR!
optional arguments:
-h, --help show this help message and exit
--foo FOO FOO!
All is well that ends well.
Best practice is to use argparse to handle all your commandline arguments. It includes a default --help which you can customize to your likings.
Here's the simplest example:
import argparse
parser = argparse.ArgumentParser(description='This is my help')
args = parser.parse_args()
Which results in:
% python argparse_test.py -h
usage: argparse_test.py [-h]
This is my help
optional arguments:
-h, --help show this help message and exit
You can define all your arguments with argparse and set a help message for each one of them. The resulting filtered/validated arguments are returned by parser.parse_args().
An alternative to the built-in argparse is a 3rd-party package called Click which features "automatic help page generation" and "arbitrary nesting of commands" (which also produces nested help pages). Internally, it's based on argparse, but, for me, makes the creation of complex CLI more convenient using decorators.
Here's a sample code:
import click
#click.command()
#click.argument("things", nargs=-1)
#click.option("-t", show_default=True, default="int", help="Data type")
#click.option("-o", help="Output format")
def combine(things, t):
"""Combines things into a single element"""
pass
if __name__ == "__main__":
combine()
And the generated help page:
$ python myapp.py --help
Usage: myapp.py [OPTIONS] [THINGS]...
Combines things into a single element
Options:
-t TEXT Data type [default: int]
-o TEXT Output format
--help Show this message and exit.
One of the nice things about it is that it uses the method docstrings as part of the help page, which is convenient because the docstring can now be used both for developer documentation and for script usage help.
You can also have nested command groups:
import click
#click.command()
#click.argument("numbers", nargs=-1)
#click.option("-e", help="Extra option for add")
def add(numbers, e):
"""Adds numbers"""
print(f"This method should add {numbers}")
#click.command()
#click.argument("numbers", nargs=-1)
#click.option("-e", help="Extra option for mul")
def mul(numbers, e):
"""Multiplies numbers"""
print(f"This method should multiply {numbers}")
#click.group()
def calc():
pass
calc.add_command(add)
calc.add_command(mul)
if __name__ == "__main__":
calc()
And it will produce nested help pages:
$ python myapp.py --help
Usage: myapp.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
add Adds numbers
mul Multiplies numbers
$ python myapp.py add --help
Usage: myapp.py add [OPTIONS] [NUMBERS]...
Adds numbers
Options:
-e TEXT Extra option for add
--help Show this message and exit.
$ python myapp.py mul --help
Usage: myapp.py mul [OPTIONS] [NUMBERS]...
Multiplies numbers
Options:
-e TEXT Extra option for mul
--help Show this message and exit.
For more information, see the Documenting Scripts section of the docs.

HelpFormatter in Click

I am using click within a local module and I would like to adjust how the help is displayed:
Currently output with --help:
Usage: __main__.py [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
foo Foo is a program very nice and pretty...
By default the prog name is __main__.py and the text is trimmed to 78 chars.
I discovered that this can be adjusted using the HelpFormatter class. But I don't know how to use it in this context.
Current Code:
import click
#click.group()
def main(ctx):
pass
#main.command()
def foo():
pass
click.CommandCollection(sources=[main])()
Expected output:
Usage: my_module_name [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
foo Foo is a program very nice and pretty and this sentence is very long.
If you are trying to to avoid the truncation of the help string, this can be accomplished via the short_help parameter. short_help is generally derived from help but truncated. If passed explicitly, the entire string will be displayed.
To display the string my_module_name, that can be passed under the parameter prog_name
Test Code:
import click
#click.group()
def main(ctx):
pass
#main.command(short_help='Foo is a program very nice and pretty and '
'this sentence is very long.')
def foo():
pass
main(['--help'], prog_name='my_module_name')
Results of short_help:
Usage: my_module_name [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
foo Foo is a program very nice and pretty and this sentence is very long.

Print program usage example with argparse module

I am trying to learn how to use python's argparse module. Currently my python script is:
parser = argparse.ArgumentParser(description='My first argparse attempt',
add_help=True)
parser.add_argument("-q", action ="store", dest='argument',
help="First argument")
output = parser.parse_args()
And it gives the output as :
usage: test.py [-h] [-q ARGUMENT]
My first argparse attempt
optional arguments:
-h, --help show this help message and exit
-q ARGUMENT First argument
Now, lets suppose I want my -h or --help argument to print a usage example also. Like,
Usage: python test.py -q "First Argument for test.py"
My purpose is to print the above usage example along with the default content of -h argument so that the user can get a basic idea of how to use the test.py python script.
So, is this functionality inbuilt in the argparse module. If no than what is the correct way to approach this problem.
Use parser.epilog to display something after the generated -h text.
parser = argparse.ArgumentParser(
description='My first argparse attempt',
epilog='Example of use')
output = parser.parse_args()
prints:
My first argparse attempt
optional arguments:
-h, --help show this help message and exit
Example of use

In python's argparse module, how can I disable printing subcommand choices between curly brackets?

How can I disable printing subcommand choices, the ones between curly brackets? Using an example at http://docs.python.org/dev/library/argparse.html#sub-commands, the normal output is:
usage: [-h] {foo,bar} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
{foo,bar} additional help
What I want is to print this:
usage: [-h] {foo,bar} ...
optional arguments:
-h, --help show this help message and exit
subcommands:
Removing just the last line.
To avoid spamming my users with the huge ugly curly-braced list of dozens of sub-commands, I simply set the metavar attribute of the subcommand object. My code looks like:
import argparse
parser = argparse.ArgumentParser(description='Stack Overflow example')
subs = parser.add_subparsers()
subs.metavar = 'subcommand'
sub = subs.add_parser('one', help='does something once')
sub = subs.add_parser('two', help='does something twice')
parser.parse_args()
And the output of running this script with a single -h argument is:
usage: tmp.py [-h] subcommand ...
Stack Overflow example
positional arguments:
subcommand
one does something once
two does something twice
optional arguments:
-h, --help show this help message and exit
The result is not exactly what you illustrate as your best desired case, but I think that it may be the closest you can get without subclassing argparse.ArgumentParser and overriding the things you need adjusted, which would be messy work.
Override ArgumentParser.print_usage() with your own method to print whatever, however you want. If all you want to do is eliminate the last line, call the original version, capture the results (by sending it to a file) and print just the part(s) you want.

Categories