argparse: default for positional argument not working? - python

I have:
from argparse import ArgumentParser
parser = ArgumentParser(description='Test')
parser.add_argument("command",
help="the command to be executed",
choices=["dump", "delete", "update", "set"],
default="set")
parser.parse_args()
But when I run: python test.py I get:
usage: test.py [-h] {dump,delete,update,set}
test.py: error: too few arguments
Maybe I am just blind sighted today; but I can't figure what should be wrong about my input. Or is this simply not possible with argparse?

For default keyword argument to work, you have to add nargs='*' like below:
parser.add_argument("command",
help="the command to be executed",
choices=["dump", "delete", "update", "set"],
nargs='?',
default="set"
)
See https://docs.python.org/2/library/argparse.html#default for more information :)
Edit by OP: nargs='*' allows for multiple commands to be entered. Thus changed to nargs='?' as I am looking for exactly one command to be entered.

Related

argparse: Ignore positional arguments if a flag is set?

I want to provide the command with 3 arguments: <version>, <input file> and <output file> under normal usage. Except there's a specific flag --init, which will basically run the program without any input and output file specifications required.
I would therefore ideally have a command which usage is:
py program.py --init
OR
py program.py <version> <input file> <output file>
However, since positional arguments are always required (because all 3 are required in any other circumstance than --init), there seems to be no way to cleanly get this syntax and all I could think of would be to make the 3 positional arguments into an optional flag, raise an exception if the optional flag isn't there when --init is not called. And that all seems ugly.
My code so far:
def get_args():
parser = argparse.ArgumentParser(description="Tool for FOO-ing a BAR.")
parser.add_argument(dest="version", help="The version.")
parser.add_argument(dest="input", help="The input file.")
parser.add_argument(dest="output", help="The output file.")
parser.add_argument("-i", "--init", dest="init", action="store_true", help="Foo Init.")
return parser.parse_args()
To Clarify:
Either all 3 arguments (<version> <input> <output>) MUST be specified.
OR
The program is only ran with --init flag and 0 arguments should be specified.
The program should NOT accept with 0, 1 or 2 arguments specified (without the --init flag).
You can define your own action class:
class init_action(argparse.Action):
def __init__(self, option_strings, dest, **kwargs):
return super().__init__(option_strings, dest, nargs=0, default=argparse.SUPPRESS, **kwargs)
def __call__(self, parser, namespace, values, option_string, **kwargs):
# Do whatever should be done here
parser.exit()
def get_args():
parser = argparse.ArgumentParser(description="Tool for FOO-ing a BAR.")
parser.add_argument(dest="version", help="The version.")
parser.add_argument(dest="input", help="The input file.")
parser.add_argument(dest="output", help="The output file.")
parser.add_argument("-i", "--init", action=init_action, help="Foo Init.")
return parser.parse_args()
I had the exact same problem, and fixed it by adding nargs="?" to all my positional arguments
def get_args():
parser = argparse.ArgumentParser(description="Tool for FOO-ing a BAR.")
parser.add_argument(dest="version", nargs="?", help="The version.")
parser.add_argument(dest="input", nargs="?", help="The input file.")
parser.add_argument(dest="output", nargs="?", help="The output file.")
parser.add_argument("-i", "--init", dest="init", action="store_true", help="Foo Init.")
return parser.parse_args()
but I'm not completely satisfied with it since when you run
py program.py -h
the synopsys is printed as
usage: program.py [-h] [--init] [version] [input] [output]
while I would want it to be
usage: program.py [-h] [--init] version input output
since version, input and output are required, just that they are not required when you use the --init flag.
Another downside is that you do not get the help text printed if you do not provide the positional arguments correctly.
I think it is sad that argparse have no way to set ignore_positional_args=True or something on flags. Especially since this property already is true for the -h flag. At least I'm not able to find a way to do it.
EDIT:
I actually found a way to better way to do it, but put it in a separate answer because I think this can be a useful approach as well.
parser.add_argument has a default parameter (docs) which can be used here for version, input and output parameters. Now you won't need third init param
One possible solution is to use sys.argv to determine whether the flag is enabled.
Using your example,
import sys
if '--init' not in sys.argv:
parser.add_argument(dest="version", help="The version.")
parser.add_argument(dest="input", help="The input file.")
parser.add_argument(dest="output", help="The output file.")
Argparse will allow you to run the program with either --init flag without any arguments or force you to include other arguments if --init flag is not present.
One downside to this approach is that you will see the following output, when you run py program.py -h:
usage: program.py [--init] version input output
positional arguments:
version - The version.
input - The input file.
output - The output file.
optional arguments:
--init - ...
which makes it hard for the user to understand the logic unless you clarify it in the description in the help section.

python argparse choices with a default choice

I'm trying to use argparse in a Python 3 application where there's an explicit list of choices, but a default if none are specified.
The code I have is:
parser.add_argument('--list', default='all', choices=['servers', 'storage', 'all'], help='list servers, storage, or both (default: %(default)s)')
args = parser.parse_args()
print(vars(args))
However, when I run this I get the following with an option:
$ python3 ./myapp.py --list all
{'list': 'all'}
Or without an option:
$ python3 ./myapp.py --list
usage: myapp.py [-h] [--list {servers,storage,all}]
myapp.py: error: argument --list: expected one argument
Am I missing something here? Or can I not have a default with choices specified?
Pass the nargs and const arguments to add_argument:
parser.add_argument('--list',
default='all',
const='all',
nargs='?',
choices=['servers', 'storage', 'all'],
help='list servers, storage, or both (default: %(default)s)')
If you want to know if --list was passed without an argument, remove the const argument, and check if args.list is None.
Documention:
nargs with '?'
One argument will be consumed from the command line if possible, and produced as a single item. If no command-line argument is present, the value from default will be produced. Note that for optional arguments, there is an additional case - the option string is present but not followed by a command-line argument. In this case the value from const will be produced.
const
When add_argument() is called with option strings (like -f or --foo) and nargs='?'. This creates an optional argument that can be followed by zero or one command-line arguments. When parsing the command line, if the option string is encountered with no command-line argument following it, the value of const will be assumed instead. See the nargs description for examples.
Thanks #ShadowRanger. Subcommands is exactly what I need, combined with nargs and const. The following works:
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers()
parser_list = subparser.add_parser('list')
parser_list.add_argument('list_type', default='all', const='all', nargs='?', choices=['all', 'servers', 'storage'])
parser_create = subparser.add_parser('create')
parser_create.add_argument('create_type', default='server', const='server', nargs='?', choices=['server', 'storage'])
args = parser.parse_args()
pprint(vars(args))
$ python3 ./myapp.py -h
usage: dotool.py [-h] {list,create} ...
Digital Ocean tool
positional arguments:
{list,create}
optional arguments:
-h, --help show this help message and exit
list option alone:
$ python3 ./myapp.py list
{'list_type': 'all'}
List option with a parameter:
$ python3 ./myapp.py list servers
{'list_type': 'servers'}

argparse doesn't check for positional arguments

I'm creating a script that takes both positional and optional arguments with argparse. I have gone through Doug's tutorial and the python Docs but can't find an answer.
parser = argparse.ArgumentParser(description='script to run')
parser.add_argument('inputFile', nargs='?', type=argparse.FileType('rt'),
parser.add_argument('inputString', action='store', nargs='?')
parser.add_argument('-option1', metavar='percent', type=float, action='store')
parser.add_argument('-option2', metavar='outFile1', type=argparse.FileType('w'),
parser.add_argument('-option3', action='store', default='<10',
args = parser.parse_args()
# rest of script.... blah blah
As you can see, I want 2 positional and 3 optional arguments. However, when I try to run it in the terminal, it doesn't check for the positionals!
If I try: python script.py inputfile
it will run normally and output error halfway through the script when it cannot find a value for inputString.
If I try: python script.py xxx ; the output is:
usage script.py [-h] [-option1] [-option2] [-option3]
Can anyone explain why it doesn't check for the positional arguments?
Your problem is that you're specifying nargs='?'. From the documentation:
'?'. One argument will be consumed from the command line if possible, and produced as a single item. If no command-line argument is present, the value from default will be produced.
If you leave out the nargs='?' then the argument will be required, and argparse will display an error if it is not provided. A single argument is consumed if action='store' (the default).
You can also specify nargs=1; the difference is that this produces a list containing one item, as opposed to the item itself. See the documentation for more ways you can use nargs.
Works for me.
Code:
#!/usr/bin/python
import argparse
parser=argparse.ArgumentParser(description='script to run')
parser.add_argument('inputFile', nargs='?', type=argparse.FileType('rt'))
parser.add_argument('inputString', action='store', nargs='?')
parser.add_argument('-option1', metavar='percent', type=float, action='store')
parser.add_argument('-option2', metavar='outFile1', type=argparse.FileType('w'))
parser.add_argument('-option3', action='store', default='<10')
args = parser.parse_args()
Execution:
# ./blah.py -h
usage: blah.py [-h] [-option1 percent] [-option2 outFile1] [-option3 OPTION3]
[inputFile] [inputString]
script to run
positional arguments:
inputFile
inputString
optional arguments:
-h, --help show this help message and exit
-option1 percent
-option2 outFile1
-option3 OPTION3
Did you overlook the second line in the argument list?
It works as expected. There is no inputString if you run it as script.py inputfile (only one argument is given, but inputString is the second argument).
narg='?' means that the argument is optional (they are surrounded by [] in the help message).

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, how to get subparsers to read in parent parser's argument?

Here is an example code:
import argparse
parser=argparse.ArgumentParser()
parser.add_argument('-main_arg')
subparser=parser.add_subparser()
a=subparser.add_parser('run')
a.add_argument('required_sub_arg')
a.add_argument('arg_a')
b=subparser.add_parser('b')
parser.parse_args()
I want it to read in -main_arg if I enter program run required_sub_arg -main_arg -arg_a
Right now, it doesn't recognize -main_arg as a valid argument.
PSA to recent readers
As this question still has visits in 2018, before doing anything this complex with argparse, please consider using docopt or click instead. It will improve both your sanity and that of anyone who might read or modify your code. Thank you.
Original answer
As is, you have a few issues.
First, parser.parse_args is a method that returns a namespace of parser's arguments, so you should do something like
args = parser.parse_args()
Then args.main_args to get-main_arg from a call like
program -main_arg run required_sub_arg -arg_a
Your issue with main_arg is that you have created a argument to parser named main_arg, and you make a call like
program run required_sub_arg -main_arg -arg_a
that refers to an argument to a named main_arg. Since a doesn't have such an argument, it is invalid.
In order to refer to a parser's argument from one of its subparser, you have to make said subparser inherit the arguments of its parent. This is done with
a=parser.add_subparser('run', parents=[parser])
You have mistaken subparser for child parser. See http://docs.python.org/dev/py3k/library/argparse.html and https://code.google.com/p/argparse/issues/detail?id=54 for more informations.
For anyone else using argparse that arrives here looking for a way to display "common" sub-parser arguments in the "main" help screen, here's one approach:
import argparse
common = argparse.ArgumentParser(add_help=False)
common.add_argument('--shared', action='store_true', help='some shared arg')
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--parent', action='store_true', help='parent only arg')
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[common])
run.add_argument('--fast', action='store_true', help='run only arg')
parser.epilog = "--- Arguments common to all sub-parsers ---" \
+ common.format_help().replace(common.format_usage(), '')
args = parser.parse_args()
Main help:
$ program.py -h
usage: program.py [-h] {run} ...
positional arguments:
{run}
optional arguments:
-h, --help show this help message and exit
--parent parent only arg
--- Arguments common to all sub-parsers ---
optional arguments:
--shared some shared arg
run sub-parser help:
$ program.py run -h
usage: program.py run [-h] [--shared]
optional arguments:
-h, --help show this help message and exit
--shared some shared arg
--fast run only arg
To address the actual question, since the accepted answer doesn't run for me, here's some additional information on why it doesn't seem possible to truly share argparse arguments with the same name across both parent and child/sub-parser parsers.
First, the problem with the following code:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-main_arg')
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[parser])
args = parser.parse_args()
Is that it leads to the following error, because both parent parser and sub-parser run define the -h/--help argument (by default).
Argparse.ArgumentError: argument -h/--help: conflicting option strings: -h, --help
While this error can be avoided by suppressing the -h/--help option (with add_help=False) on either the parent or the child, it's nice to have the help option at both levels.
Another potential way to avoid conflicting help options is to move common arguments to a shared parser, common:
import argparse
common = argparse.ArgumentParser(add_help=False)
common.add_argument('-main_arg', action='store_true')
parser = argparse.ArgumentParser(parents=[common])
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[common])
args = parser.parse_args()
print(args)
While this appears to work on the surface, in practice, it doesn't work as intended:
$ program.py run # OK
Namespace(main_arg=False)
$ program.py run -main_arg # OK
Namespace(main_arg=True)
$ program.py -main_arg run # BAD: expected main_arg to be True
Namespace(main_arg=False)
The behavior observed when parsing program.py -main_arg run illustrates a key relationship: a parent argparser and its sub-parsers are independent parsers, where the parent parses all arguments up to the sub-parser "command" positional argument, and then the selected sub-parser parses the remaining arguments in the same Namespace as the parent with no regard for attributes that may have been set by the parent.

Categories