Only one command line option with argparse - python

I'm trying to create a CLI with the argparse module but I'd like to have different commands with different argument requirements, I tried this:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('foo', help='foo help')
parser.add_argument('test', nargs=1, help='test help')
args = parser.parse_args()
what I'd like is to be able to run python test.py foo and python test.py test somearg
but when I run python test.py foo I get error: too few arguments. Is there a way that the commands could behave like git status, git commit or pip install? or is there a better way to create a CLI in python?

#crodjer is correct;
to provide an example:
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title='subcommands',
description='valid subcommands',
help='additional help')
foo_parser = subparsers.add_parser('foo', help='foo help')
bar_parser = subparsers.add_parser('bar', help='bar help')
bar_parser.add_argument('somearg')
args = parser.parse_args()
Test of different args per subparser:
$ python subparsers_example.py bar somearg
Namespace(somearg='somearg')
$ python subparsers_example.py foo
Namespace()
$ python subparsers_example.py foo somearg
usage: argparse_subparsers.py foo [-h]
subparser_example.py foo: error: unrecognized arguments: somearg
Help output:
$ python subparsers_example.py foo -h
usage: argparse_subparsers.py foo [-h]
optional arguments:
-h, --help show this help message and exit
$ python subparsers_example.py bar -h
usage: argparse_subparsers.py bar [-h] somearg
positional arguments:
somearg
optional arguments:
-h, --help show this help message and exit

This is what you probably want:
http://docs.python.org/library/argparse.html#sub-commands
With this you can add sub arguments which have their own argument schemes.

By default, argparse arguments consume one value. If you want foo to have different behavior, you'll need to specify it. It looks like you think the default is nargs=0, but it's not. From the argparse documentation (at http://docs.python.org/dev/library/argparse.html#nargs): "If the nargs keyword argument is not provided, the number of args consumed is determined by the action. Generally this means a single command-line arg will be consumed and a single item (not a list) will be produced."
You can either use nargs='?' for foo and give it a default value in case nothing is provided from the command-line, or use a non-default action (perhaps 'store_true'?).

Related

argparse claims argument althoug default value specified [duplicate]

I have a script which is meant to be used like this:
usage: installer.py dir [-h] [-v]
dir is a positional argument which is defined like this:
parser.add_argument('dir', default=os.getcwd())
I want the dir to be optional: when it's not specified it should just be cwd.
Unfortunately when I don't specify the dir argument, I get Error: Too few arguments.
Use nargs='?' (or nargs='*' if you need more than one dir)
parser.add_argument('dir', nargs='?', default=os.getcwd())
extended example:
>>> import os, argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-v', action='store_true')
_StoreTrueAction(option_strings=['-v'], dest='v', nargs=0, const=True, default=False, type=None, choices=None, help=None, metavar=None)
>>> parser.add_argument('dir', nargs='?', default=os.getcwd())
_StoreAction(option_strings=[], dest='dir', nargs='?', const=None, default='/home/vinay', type=None, choices=None, help=None, metavar=None)
>>> parser.parse_args('somedir -v'.split())
Namespace(dir='somedir', v=True)
>>> parser.parse_args('-v'.split())
Namespace(dir='/home/vinay', v=True)
>>> parser.parse_args(''.split())
Namespace(dir='/home/vinay', v=False)
>>> parser.parse_args(['somedir'])
Namespace(dir='somedir', v=False)
>>> parser.parse_args('somedir -h -v'.split())
usage: [-h] [-v] [dir]
positional arguments:
dir
optional arguments:
-h, --help show this help message and exit
-v
As an extension to #VinaySajip answer. There are additional nargs worth mentioning.
parser.add_argument('dir', nargs=1, default=os.getcwd())
N (an integer). N arguments from the command line will be gathered together into a list
parser.add_argument('dir', nargs='*', default=os.getcwd())
'*'. All command-line arguments present are gathered into a list. Note that it generally doesn't make much sense to have more than one positional argument with nargs='*', but multiple optional arguments with nargs='*' is possible.
parser.add_argument('dir', nargs='+', default=os.getcwd())
'+'. Just like '*', all command-line args present are gathered into a list. Additionally, an error message will be generated if there wasn’t at least one command-line argument present.
parser.add_argument('dir', nargs=argparse.REMAINDER, default=os.getcwd())
argparse.REMAINDER. All the remaining command-line arguments are gathered into a list. This is commonly useful for command line utilities that dispatch to other command line utilities
If the nargs keyword argument is not provided, the number of arguments consumed is determined by the action. Generally this means a single command-line argument will be consumed and a single item (not a list) will be produced.
Edit (copied from a comment by #Acumenus) nargs='?' The docs say: '?'. 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.
Short Answer
As already shown in the previous two answers, you can accept an optional positional argument with nargs='?'. You could also turn the argument directly into a Path type and/or shorten the cwd to . if you wanted to:
myfile.py
import argparse
import pathlib
parser = argparse.ArgumentParser()
parser.add_argument("dir", nargs="?", default=".", type=pathlib.Path)
parsed_args = parser.parse_args()
print("Installing to", parsed_args.dir.resolve())
$ python myfile.py
Installing to /users/myname/myfolder
$ python myfile.py /usr/bin/
Installing to /usr/bin
Longer answer
Since you also mention the flag-style True/False options -h and -v in your question, these examples may be of use:
Flags (e.g. -v)
We might refer to optional options that take no arguments as "flags". With flags, we only care about whether they are given or not. -h is a flag that argparse adds automatically (along with the longer version --help) so we shouldn't really override that. If we consider -v then,
myfile.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
"-v",
"--version",
action="store_true")
parsed_args = parser.parse_args()
if parsed_args.version:
print("version flag given")
else:
print("version flag not given")
Note that the second argument to add_argument() is a longer name for the option. It is not mandatory but it does make your subsequent code more readable (parsed_args.version vs parsed_args.v) and makes calls to your installer more explicit.
$ python myfile.py -v
version flag given
$ python myfile.py --version
version flag given
$ python myfile.py
version flag not given
Optional arguments (e.g. --installdir /usr/bin/)
One could argue that, in your case, you would be better off with an optional argument rather than a positional one.
myfile.py
import argparse
import pathlib
parser = argparse.ArgumentParser()
parser.add_argument(
"-i",
"--installdir", # Optional (but recommended) long version
type=pathlib.Path,
default="/bin"
)
parsed_args = parser.parse_args()
print("Installing to", parsed_args.installdir)
$ python myfile.py -i /usr/bin/
Installing to /usr/bin
$ python myfile.py --installdir /usr/bin/
Installing to /usr/bin
$ python myfile.py
Installing to /bin
parser.add_argument also has a switch required. You can use required=False.
Here is a sample snippet with Python 2.7:
parser = argparse.ArgumentParser(description='get dir')
parser.add_argument('--dir', type=str, help='dir', default=os.getcwd(), required=False)
args = parser.parse_args()

Make a command-line-based application with python argparse only

I would want to make a command-line-based application using python and argparse library. But I am having some challenges with this library, This is the command looks like (the usage):
prog (foo|bar) [-v] (-h "key:value")* [-d inline-data] [-f file] URL
This is the prog help output:
> prog help
prog is a simple application
Usage:
prog command [arguments]
The commands are:
foo executes FOO
bar executes BAR
help prints this screen.
Use "prog help [command]" for more information about a command.
My challenge is in this part. I want to give separate help description when we execute the following commands:
prog help foo
prog help bar
import argparse
parser = argparse.ArgumentParser(prog='prog.py',description='some help', add_help=False)
#I turned off the default help, and defined -h separately.
parser.add_argument('-h', '--header', metavar='', help='headerHelpString')
subparsers = parser.add_subparsers(help='help sub-command')
subparserHelp = subparsers.add_parser('help', help='some help')
subparserFooBar = subparserHelp.add_argument('method', choices=['foo', 'bar'])
group = parser.add_mutually_exclusive_group()
group.add_argument('-q', '--quiet', action='store_true', help='print quiet')
group.add_argument('-v', '--verbose', action='store_true', help='print verbose')
args = parser.parse_args()
Without arguments, I get an error - it requires the subparsers command, 'help'. As defined there are a couple of flagged arguments for the toplevel. Arguments for the subparser are not displayed in the usage (or full help if it was enabled).
1327:~/mypy$ python stack46982125.py
usage: prog.py [-h] [-q | -v] {help} ...
prog.py: error: too few arguments
In Py3 subparsers are 'optional', showing what would be set:
1328:~/mypy$ python3 stack46982125.py
Namespace(header=None, quiet=False, verbose=False)
With the 'help' subparser, usage includes '[-h]', but this is the default help. The subparsers doesn't inherit the add_help parameter; you have to set that explicitly.
1328:~/mypy$ python3 stack46982125.py help
usage: prog.py help [-h] {foo,bar}
prog.py help: error: the following arguments are required: method
Taking advantage of that -h, I get the same usage with a fuller help.
1329:~/mypy$ python3 stack46982125.py help -h
usage: prog.py help [-h] {foo,bar}
positional arguments:
{foo,bar}
optional arguments:
-h, --help show this help message and exit
And if I also define the required 'foo/bar' it parses fine.
1329:~/mypy$ python3 stack46982125.py help foo
Namespace(header=None, method='foo', quiet=False, verbose=False)
If I add
parser.print_help()
subparserHelp.print_help()
I get the added output:
usage: prog.py [-h] [-q | -v] {help} ...
some help
positional arguments:
{help} help sub-command
help some help
optional arguments:
-h , --header headerHelpString
-q, --quiet print quiet
-v, --verbose print verbose
usage: prog.py help [-h] {foo,bar}
positional arguments:
{foo,bar}
optional arguments:
-h, --help show this help message and exit
If I add a dest parameter
subparsers = parser.add_subparsers(dest='cmd', help='help sub-command')
And make the prints conditional
if args.cmd == 'help':
parser.print_help()
subparserHelp.print_help()
No display of help (in py3) or error as above (py2)
1340:~/mypy$ python3 stack46982125.py
Namespace(cmd=None, header=None, quiet=False, verbose=False)
display of helps:
1341:~/mypy$ python3 stack46982125.py help foo
Namespace(cmd='help', header=None, method='foo', quiet=False, verbose=False)
....
If you turn off the -h help, then you have to somehow capture the 'help' string, and act on it with your own help or the print_help method. You can also define another flagged argument and give it an action='help' parameter.
Do you really need to turn turn off the default add_help. Isn't it simpler to use the default help approach? It's probably more familiar to your users.

Passing in multiple options for argparse in Python

I have been looking at argparse documentation but I am still confused how to use it.
I made a python script to get issues from either pmd, checkstyle, or findbugs after a code analysis. Theses issues are also categorized into severities such as major, blocker, and critical.
So I want to be able to pass in two arguments in the form python script.py arg1 arg2 where arg1 would be a combination of p,c,f which stands for pmd, checkstyle, or findbug and arg2 would be a combination of m,c,b which stands for major, critical, and blocker.
So for instance, if I write python script.py pf cb in the terminal, I would get pmd and findbugs issues of critical and blocker severity.
It would be awesome if someone can give me a general structure of how this should go.
Thanks.
Perhaps you'd rather let the user specify the flags more verbose, like this?
python script.py --checker p --checker f --level c --level b
If so, you can use append
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--checker', action='append')
>>> parser.add_argument('--level', action='append')
Then you get a parameter checker and level, both as lists to iterate over.
If you really want to use the combined flags:
for c in arg1:
run_checker(c, arg2)
Assuming that you just pass the severity levels to the checker in some way.
You could try setting boolean flags if each option is present in an argument:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("pcf", help="pmd, checkstyle, or findbug")
parser.add_argument("mcb", help="major, critical, and blocker")
args = parser.parse_args()
# Use boolean flags
pmd = 'p' in args.pcf
checkstyle = 'c' in args.pcf
findbug = 'f' in args.pcf
major = 'm' in args.mcb
critical = 'c' in args.mcb
blocker = 'b' in args.mcb
This would work using python script.py pf cb.
Also, just a helpful hint. If you place the following at the top of your python file and then make it executable with chmod +x script.py you can then call the file directly (using a *NIX operating system):
#!/usr/bin/env python
import argparse
...
Now run with ./script.py pf cb or even put it in your path and call script.py pf cb
https://stackoverflow.com/users/1401034/ewan is a good solution given your specification. But a slight variation gives users more options, and might be clearer.
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--pmd', action='store_true')
parser.add_argument('-c', '--checkstyle', action='store_true')
parser.add_argument('-f', '--findbug', action='store_true')
parser.add_argument('-m', '--major', action='store_true')
parser.add_argument('-d', '--critical', action='store_true')
parser.add_argument('-b', '--blocker', action='store_true')
args = parser.parse_args()
print args
Sample runs:
1840:~/mypy$ python2.7 stack24265375.py -cfmd
Namespace(blocker=False, checkstyle=True, critical=True, findbug=True, major=True, pmd=False)
1841:~/mypy$ python2.7 stack24265375.py -h
usage: stack24265375.py [-h] [-p] [-c] [-f] [-m] [-d] [-b]
optional arguments:
-h, --help show this help message and exit
-p, --pmd
-c, --checkstyle
-f, --findbug
-m, --major
-d, --critical
-b, --blocker
1842:~/mypy$ python2.7 stack24265375.py --critical --major
Namespace(blocker=False, checkstyle=False, critical=True, findbug=False, major=True, pmd=False)
In this case, there are 6 optional, boolean flags. The short, single letter versions can be strung together in any combination. Except the necessary '-', this form can look just like the one using 2 positional arguments. But they can be mixed and matched in any combination. And the long form is self documenting.
The help display might be clearer if these 6 arguments were divided into 2 argument groups.
parser = argparse.ArgumentParser()
group1 = parser.add_argument_group('issue')
group1.add_argument('-p', '--pmd', action='store_true')
group1.add_argument('-c', '--checkstyle', action='store_true')
group1.add_argument('-f', '--findbug', action='store_true')
group2 = parser.add_argument_group('severity')
group2.add_argument('-m', '--major', action='store_true')
group2.add_argument('-d', '--critical', action='store_true')
group2.add_argument('-b', '--blocker', action='store_true')
args = parser.parse_args()
print args
with the help:
usage: stack24265375.py [-h] [-p] [-c] [-f] [-m] [-d] [-b]
optional arguments:
-h, --help show this help message and exit
issue:
-p, --pmd
-c, --checkstyle
-f, --findbug
severity:
-m, --major
-d, --critical
-b, --blocker

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