How to remove empty metavar square brackets from python argparse help command? - python

I dont want to show default metavar in the help page nor I need to provide anything, so hack is to add an empty string as a metavar value, but it produces an empty square bracket in the help page, can anyone help how to remove it?
required.add_argument(
'--configure',
type=str,
nargs='?',
metavar='',
const='default',
dest='configure',
default=argparse.SUPPRESS,
help='Configure a profile',
)
Output:
mycommand profile --help
usage: command profile [--help] [options]
optional arguments:
--list Shows list profiles
--configure [] Configure a profile
--delete [] Delete a profile
--show [] Show a profile

The use of [] is integral to using `nargs='?'. They mean "optional/not required".
In [626]: p=argparse.ArgumentParser()
In [627]: a = p.add_argument('--foo',nargs='?')
In [628]: p.print_help()
usage: ipython3 [-h] [--foo [FOO]]
optional arguments:
-h, --help show this help message and exit
--foo [FOO]
To change that you'll have to subclass the HelpFormatter, and change one of the nested methods.

Related

Python argparse: Store parameters not matching any subparser

I am using argparse library to parse arguments to my python script. This is my code:
parser = argparse.ArgumentParser(
prog="confgit",
description="Git overhead for version control of your config files",
formatter_class=argparse.RawTextHelpFormatter, )
parser.add_argument(
"-c", "--config",
type=str,
default=DEFAULT_CONFIG_PATH,
dest="CONFIG_PATH",
help="load alternative config")
subparsers = parser.add_subparsers(help="Commands:")
subparsers.add_parser("include", help="Include file or directory in to repository").add_argument(
"file_to_include",
type=str,
action="store",
nargs="?",
const="",
default=False,
help="include file or directory in to repository")
subparsers.add_parser("exclude", help="Exclude file or directory in to repository").add_argument(
"exclude",
type=str,
action="store",
help="exclude file or directory from repository")
print(parser.parse_args())
I would like to be able to store parameters not matching any subparser as a string. For example
running myprogram include test.txt --config .config/cfg.txt will result in:
Namespace(CONFIG_PATH='.config/cfg.txt', file_to_include='test.txt')
and running myprogram some text here will result in:
Namespace(CONFIG_PATH='.config/default.txt', input="some other text")
How can I achieve this ?
Thank you for help
The helps from your code:
1940:~/mypy$ python3 stack65119253.py -h
usage: confgit [-h] [-c CONFIG_PATH] {include,exclude} ...
Git overhead for version control of your config files
positional arguments:
{include,exclude} Commands:
include Include file or directory in to repository
exclude Exclude file or directory in to repository
optional arguments:
-h, --help show this help message and exit
-c CONFIG_PATH, --config CONFIG_PATH
load alternative config
So you can provide a optional '-c' with value.
The subparsers argument is a positional with 2 choices. It isn't required, but if you do provide a string it will be tested against those strings.
1941:~/mypy$ python3 stack65119253.py include -h
usage: confgit include [-h] [file_to_include]
positional arguments:
file_to_include include file or directory in to repository
optional arguments:
-h, --help show this help message and exit
1941:~/mypy$ python3 stack65119253.py exclude -h
usage: confgit exclude [-h] exclude
positional arguments:
exclude exclude file or directory from repository
optional arguments:
-h, --help show this help message and exit
For example:
1946:~/mypy$ python3 stack65119253.py -c foobar
Namespace(CONFIG_PATH='foobar')
1946:~/mypy$ python3 stack65119253.py -c foobar test
usage: confgit [-h] [-c CONFIG_PATH] {include,exclude} ...
confgit: error: invalid choice: 'test' (choose from 'include', 'exclude')
1947:~/mypy$ python3 stack65119253.py -c foobar include
Namespace(CONFIG_PATH='foobar', file_to_include=False)
argparse assigns strings to positionals by position. It does not assign by value. That is, it does not test for some value, and based on that decide whether it qualifies. The choices testing comes after assignment. Use optionals if you want to assign by value.
parser.add_argument('--include', nargs='?')
parser.add_argument('--exclude')
parse+known_args is a way of dealing with unrecognized arguments, but it doesn't get around the invalid choices error.
If the user doesn't quote the string "some other text" you will simply have to treat it as 3 different arguments ["some", "other", "text"]. But to handle it as closely as what you seem to desire you simply need to use the nargs option on an argument called input. The argparse page has an example at the very top with the "number accumulator".
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('integers', metavar='N', type=int, nargs='+',
help='an integer for the accumulator')
Use * for 0 or more arguments instead of + and replace integers with whatever argument name you want.
Upon further investigation what I wrote above won't work when you have subparsers. I would suggest making your include and exclude subcommands into options. Wouldn't it be sensible to want to do both anyway? In your current configuration you could only either include or exclude.

Python: how to add '-help' to argparse help command list?

Is there a way to include '-help' command to argparse help list?
I wish to have something like this on output, if i am typing '-help'.
optional arguments:
-h, -help, --help show this help message and exit
Thanks
As #Akaisteph7 suggested:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-help', action="help", help="second help :)")
parser.add_argument('-f', '--foo')
parser.print_help()
0945:~/mypy$ python3 stack57058526.py
usage: stack57058526.py [-h] [-help] [-f FOO]
optional arguments:
-h, --help show this help message and exit
-help second help :)
-f FOO, --foo FOO
Changing to:
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('-h','--help','-help', action="help", help="replacement help")
0946:~/mypy$ python3 stack57058526.py
usage: stack57058526.py [-h] [-f FOO]
optional arguments:
-h, --help, -help replacement help
-f FOO, --foo FOO
Adding the '-help' flag to the default help requires modifying a couple of 'private' attributes:
parser = argparse.ArgumentParser()
parser._actions[0].option_strings += ['-help']
parser._option_string_actions['-help'] = parser._option_string_actions['-h']
0947:~/mypy$ python3 stack57058526.py
usage: stack57058526.py [-h] [-f FOO]
optional arguments:
-h, --help, -help show this help message and exit
-f FOO, --foo FOO
If you want to build this change into your local version of argparse, you could modify this block of code in the ArgumentParser.__init__ method:
if self.add_help:
self.add_argument(
default_prefix+'h', default_prefix*2+'help',
action='help', default=SUPPRESS,
help=_('show this help message and exit'))
Whether you change a local copy of argparse.py, or subclass ArgumentParser is up to you.
While this is possible to do, it is not recommended. Single dashes are only meant to be used with single letters. In general, you should follow recommendations as they are there for a reason.
If you really want to add it however, you can do it with:
parser.add_argument("-help", action="help")
argparse can work with any prefix characters.
For instance to support POSIX, Cmd.exe and PowerShell-type queries you could use:
p = ArgumentParser(prefix_chars="-/", add_help=False)
p.add_argument("-help", "--help", "-h", "/?", action="help")
Remember to use add_help=False or you'll have two help commands!

how to set argparse to exclude optional argument by values of positional argument

the arg include an action field and optional switches that modify the behavior of the actions.
the argparse code is like the below:
parser=argparse.ArgumentParser()
parser.add-argument('action',metavar='action', choices=['analysis','report','update'],nargs='?', default='report')
parser.add-argument('-d',dest='delay',type=int, choices=range(1,10),default=1)
parser.add-argument('-v',dest='verbose',action='store-true',default=False)
parser.add-argument('-o',dest='offline',action='store-true',default=False)
parser.add-argument('-n',dest='names',required=False)
i want to make the switch option -o, -d, -v only available for action=report, while option -n only available for action=analysis.
i know there is a mutual group setting, but it is just set for the arguments not for the argument values!
btw, does argparse support combine options, like -vo...???
First: Yes, combined options like -vo are supported
Now to the action switching:
Adding a SubParser to your ArgumentParser is exactely what you want to do.
See Section 15.4.5.1. Sub-commands in the Documentation of argparse, everything is explained to some detail and with example there
EDIT:
I know refer to your comment below this post:
If you do not provide a subcommand parameter in the program call, the parser normally gives you a kind advice how to use the program and then exits with a "too few arguments error"
I rewrote your code to show this
test.py
import argparse
parser=argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='sub-command help')
parser_analysis = subparsers.add_parser('analysis', help='analysis help text')
parser_analysis.add_argument('-n',dest='names',required=False)
parser_report = subparsers.add_parser('report', help='report help text')
parser_report.add_argument('-d',dest='delay',type=int, choices=range(1,10),default=1)
parser_report.add_argument('-v',dest='verbose',action='store_true',default=False)
parser_report.add_argument('-o',dest='offline',action='store_true',default=False)
parser_update = subparsers.add_parser('update', help='update help text')
parser.parse_args()
Now some calls of this test.py with different arguments:
$python test.py
usage: test.py [-h] {analysis,report,update} ...
test.py: error: too few arguments
$python test.py -h
usage: test.py [-h] {analysis,report,update} ...
positional arguments:
{analysis,report,update}
sub-command help
analysis analysis help text
report report help text
update update help text
optional arguments:
-h, --help show this help message and exit
$python test.py report -h
usage: test.py report [-h] [-d {1,2,3,4,5,6,7,8,9}] [-v] [-o]
optional arguments:
-h, --help show this help message and exit
-d {1,2,3,4,5,6,7,8,9}
-v
-o
So as I see it the only problem is that the program throws an error after calling python test.py without any subcommand. So I would do s.th. like this
try:
args=parser.parse_args()
except:
exit(0)
to avoid that the user sees that there was an unhandled error. You have then the same behaviour as i.e. the svn command.
If you want to handle this in the way that a default subcommand is executed, you whould have to do s.th. like mentioned in this post answer:
Argparse - How to Specify a Default Subcommand
import sys
#...your parser definitions
if (len(sys.argv) < 2):
args = parser.parse_args(['update'])
else:
args = parser.parse_args()
This would parse a command update if the argument list in sys.argv is smaller than 2. Why 2? Because the first argument in the argument list is always the program which you called, i.e. test.py
The question is, do you really want this behaviour? Because there is no need for calling test.py update if i can always call test.py, so users will probably get lazy and never use test.py update command. Also if you later want a different default behaviour like test.py starting an interactive mode, users which are by then used to calling test.py for updating will get confused or their scripts which use your program get broken.

Don't show long options twice in print_help() from argparse

I have the following code:
parser = argparse.ArgumentParser(description='Postfix Queue Administration Tool',
prog='pqa',
usage='%(prog)s [-h] [-v,--version]')
parser.add_argument('-l', '--list', action='store_true',
help='Shows full overview of all queues')
parser.add_argument('-q', '--queue', action='store', metavar='<queue>', dest='queue',
help='Show information for <queue>')
parser.add_argument('-d', '--domain', action='store', metavar='<domain>', dest='domain',
help='Show information about a specific <domain>')
parser.add_argument('-v', '--version', action='version', version='%(prog)s 0.1')
args = parser.parse_args()
Which gives me output like this:
%./pqa
usage: pqa [-h] [-v,--version]
Postfix Queue Administration Tool
optional arguments:
-h, --help show this help message and exit
-l, --list Shows full overview of all queues
-q <queue>, --queue <queue>
Show information for <queue>
-d <domain>, --domain <domain>
Show information about a specific <domain>
-v, --version show program's version number and exit
I would very much like to know how I can 'group' commands that have two versions (ie. long options) which each also show a metavar.
This is mostly an aesthetic issue on my side, but I would still like to fix this. I have been reading manuals and texts on the internet, but either the information just isn't there or I am totally missing something here :)
Putting hpaulj's answer into actual code, something like this works:
class CustomHelpFormatter(argparse.HelpFormatter):
def _format_action_invocation(self, action):
if not action.option_strings or action.nargs == 0:
return super()._format_action_invocation(action)
default = self._get_default_metavar_for_optional(action)
args_string = self._format_args(action, default)
return ', '.join(action.option_strings) + ' ' + args_string
fmt = lambda prog: CustomHelpFormatter(prog)
parser = argparse.ArgumentParser(formatter_class=fmt)
To additionally extend the default column size for help variables, add constructor to CustomHelpFormatter:
def __init__(self, prog):
super().__init__(prog, max_help_position=40, width=80)
Seeing it in action:
usage: bk set [-h] [-p] [-s r] [-f] [-c] [-b c] [-t x y] [-bs s] [-bc c]
[--crop x1 y1 x2 y2] [-g u r d l]
monitor [path]
positional arguments:
monitor monitor number
path input image path
optional arguments:
-h, --help show this help message and exit
-p, --preview previews the changes without applying them
-s, --scale r scales image by given factor
-f, --fit fits the image within monitor work area
-c, --cover makes the image cover whole monitor work area
-b, --background c selects background color
-t, --translate x y places the image at given position
-bs, --border-size s selects border width
-bc, --border-color c selects border size
--crop x1 y1 x2 y2 selects crop area
-g, --gap, --gaps u r d l keeps "border" around work area
Another solution, using custom descriptions
if you set the metavar='', the help line becomes:
-q , --queue Show information for <queue>
Here I suppress the regular help lines, and replace them with the description lines for a group:
parser = argparse.ArgumentParser(description='Postfix Queue Administration Tool',
prog='pqa',
usage='%(prog)s [-h] [-v,--version]',
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument('-l', '--list', action='store_true',
help='Shows full overview of all queues')
g = parser.add_argument_group(title='information options',
description='''-q, --queue <queue> Show information for <queue>
-d, --domain <domain> Show information about a specific <domain>''')
g.add_argument('-q', '--queue', action='store', metavar='', dest='queue',
help=argparse.SUPPRESS)
g.add_argument('-d', '--domain', action='store', metavar='<domain>', dest='domain',
help=argparse.SUPPRESS)
parser.add_argument('-v', '--version', action='version', version='%(prog)s 0.1')
parser.print_help()
usage: pqa [-h] [-v,--version]
Postfix Queue Administration Tool
optional arguments:
-h, --help show this help message and exit
-l, --list Shows full overview of all queues
-v, --version show program's version number and exit
information options:
-q, --queue <queue> Show information for <queue>
-d, --domain <domain> Show information about a specific <domain>
Or you could put that information in the regular description. You already are using a custom usage line.
Is the problem that <domain> is repeated in the help line?:
-d <domain>, --domain <domain>
The argparse HelpFormatter does not give the user much control over this part of the display. As you have shown, you can set the usage line, the help text, and the metavar.
You would have to subclass the HelpFormatter, and change one of the functions to produce something like:
-d, --domain <domain>
It doesn't look like a complicated change, probably to the HelpFormatter._format_action_invocation method. But you need to be more explicit about what you want.

Writing a help for python script

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.

Categories

Resources