264
votes

This might be a simple one. Assume I have a program that uses argparse to process command line arguments/options. The following will print the 'help' message:

./myprogram -h

or:

./myprogram --help

But, if I run the script without any arguments whatsoever, it doesn't do anything. What I want it to do is to display the usage message when it is called with no arguments. How is that done?

16

16 Answers

315
votes

This answer comes from Steven Bethard on Google groups. I'm reposting it here to make it easier for people without a Google account to access.

You can override the default behavior of the error method:

import argparse
import sys

class MyParser(argparse.ArgumentParser):
    def error(self, message):
        sys.stderr.write('error: %s\n' % message)
        self.print_help()
        sys.exit(2)

parser = MyParser()
parser.add_argument('foo', nargs='+')
args = parser.parse_args()

Note that the above solution will print the help message whenever the error method is triggered. For example, test.py --blah will print the help message too if --blah isn't a valid option.

If you want to print the help message only if no arguments are supplied on the command line, then perhaps this is still the easiest way:

import argparse
import sys

parser=argparse.ArgumentParser()
parser.add_argument('foo', nargs='+')
if len(sys.argv)==1:
    parser.print_help(sys.stderr)
    sys.exit(1)
args=parser.parse_args()

Note that parser.print_help() prints to stdout by default. As init_js suggests, use parser.print_help(sys.stderr) to print to stderr.

64
votes

Instead of writing a class, a try/except can be used instead

try:
    options = parser.parse_args()
except:
    parser.print_help()
    sys.exit(0)

The upside is that the workflow is clearer and you don't need a stub class. The downside is that the first 'usage' line is printed twice.

This will need at least one mandatory argument. With no mandatory arguments, providing zero args on the commandline is valid.

35
votes

With argparse you could do:

parser.argparse.ArgumentParser()
#parser.add_args here

#sys.argv includes a list of elements starting with the program
if len(sys.argv) < 2:
    parser.print_usage()
    sys.exit(1)
27
votes

If you associate default functions for (sub)parsers, as is mentioned under add_subparsers, you can simply add it as the default action:

parser = argparse.ArgumentParser()
parser.set_defaults(func=lambda x: parser.print_usage())
args = parser.parse_args()
args.func(args)

Add the try-except if you raise exceptions due to missing positional arguments.

20
votes

If you have arguments that must be specified for the script to run - use the required parameter for ArgumentParser as shown below:-

parser.add_argument('--foo', required=True)

parse_args() will report an error if the script is run without any arguments.

18
votes

The cleanest solution will be to manually pass default argument if none were given on the command line:

parser.parse_args(args=None if sys.argv[1:] else ['--help'])

Complete example:

import argparse, sys

parser = argparse.ArgumentParser()
parser.add_argument('--host', default='localhost', help='Host to connect to')
# parse arguments
args = parser.parse_args(args=None if sys.argv[1:] else ['--help'])

# use your args
print("connecting to {}".format(args.host))

This will print complete help (not short usage) if called w/o arguments.

10
votes

Throwing my version into the pile here:

import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()
if not vars(args):
    parser.print_help()
    parser.exit(1)

You may notice the parser.exit - I mainly do it like that because it saves an import line if that was the only reason for sys in the file...

6
votes

There are a pair of one-liners with sys.argv[1:] (a very common Python's idiom to refer the command line arguments, being sys.argv[0] the script's name) that can do the job.

The first one is self-explanatory, clean and pythonic:

args = parser.parse_args(None if sys.argv[1:] else ['-h'])

The second one is a little hackier. Combining the previously evaluated fact that an empty list is False with the True == 1 and False == 0 equivalences you get this:

args = parser.parse_args([None, ['-h']][not sys.argv[1:]])

Maybe too many brackets, but pretty clear if a previous argument selection was made.

_, *av = sys.argv
args = parser.parse_args([None, ['-h']][not av])
5
votes
parser.print_help()
parser.exit()

The parser.exit method also accept a status (returncode), and a message value (include a trailing newline yourself!).

an opinionated example, :)

#!/usr/bin/env python3

""" Example argparser based python file
"""

import argparse

ARGP = argparse.ArgumentParser(
    description=__doc__,
    formatter_class=argparse.RawTextHelpFormatter,
)
ARGP.add_argument('--example', action='store_true', help='Example Argument')


def main(argp=None):
    if argp is None:
        argp = ARGP.parse_args()  # pragma: no cover

    if 'soemthing_went_wrong' and not argp.example:
        ARGP.print_help()
        ARGP.exit(status=64, message="\nSomething went wrong, --example condition was not set\n")


if __name__ == '__main__':
    main()  # pragma: no cover

Example calls:

$ python3 ~/helloworld.py; echo $?
usage: helloworld.py [-h] [--example]

 Example argparser based python file

optional arguments:
  -h, --help  show this help message and exit
  --example   Example Argument

Something went wrong, --example condition was not set
64
$ python3 ~/helloworld.py --example; echo $?
0
1
votes

Here is another way to do it, if you need something flexible where you want to display help if specific params are passed, none at all or more than 1 conflicting arg:

import argparse
import sys

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--days', required=False,  help="Check mapped inventory that is x days old", default=None)
    parser.add_argument('-e', '--event', required=False, action="store", dest="event_id",
                        help="Check mapped inventory for a specific event", default=None)
    parser.add_argument('-b', '--broker', required=False, action="store", dest="broker_id",
                        help="Check mapped inventory for a broker", default=None)
    parser.add_argument('-k', '--keyword', required=False, action="store", dest="event_keyword",
                        help="Check mapped inventory for a specific event keyword", default=None)
    parser.add_argument('-p', '--product', required=False, action="store", dest="product_id",
                        help="Check mapped inventory for a specific product", default=None)
    parser.add_argument('-m', '--metadata', required=False, action="store", dest="metadata",
                        help="Check mapped inventory for specific metadata, good for debugging past tix", default=None)
    parser.add_argument('-u', '--update', required=False, action="store_true", dest="make_updates",
                        help="Update the event for a product if there is a difference, default No", default=False)
    args = parser.parse_args()

    days = args.days
    event_id = args.event_id
    broker_id = args.broker_id
    event_keyword = args.event_keyword
    product_id = args.product_id
    metadata = args.metadata
    make_updates = args.make_updates

    no_change_counter = 0
    change_counter = 0

    req_arg = bool(days) + bool(event_id) + bool(broker_id) + bool(product_id) + bool(event_keyword) + bool(metadata)
    if not req_arg:
        print("Need to specify days, broker id, event id, event keyword or past tickets full metadata")
        parser.print_help()
        sys.exit()
    elif req_arg != 1:
        print("More than one option specified. Need to specify only one required option")
        parser.print_help()
        sys.exit()

    # Processing logic here ...

Cheers!

1
votes

When call add_subparsers method save the first positional argument to dest= and check value after argparse has been initialized, like this:

subparsers = parser.add_subparsers(dest='command')

And just check this this variable:

if not args.command:
    parser.print_help()
    parser.exit(1)  # If exit() - exit code will be zero (no error)

Full example:

#!/usr/bin/env python

""" doc """

import argparse
import sys

parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest='command',
                                   help='List of commands')

list_parser = subparsers.add_parser('list',
                                    help='List contents')
list_parser.add_argument('dir', action='store',
                         help='Directory to list')

create_parser = subparsers.add_parser('create',
                                      help='Create a directory')
create_parser.add_argument('dirname', action='store',
                           help='New directory to create')
create_parser.add_argument('--read-only', default=False, action='store_true',
                           help='Set permissions to prevent writing to the directory')

args = parser.parse_args()

if not args.command:
    parser.print_help()
    parser.exit(1)

print(vars(args))  # For debug
0
votes

Set your positional arguments with nargs, and check if positional args are empty.

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('file', nargs='?')
args = parser.parse_args()
if not args.file:
    parser.print_help()

Reference Python nargs

0
votes

If your command is something where a user needs to choose some action, then use a mutually exclusive group with required=True.

This is kind of an extension to the answer given by pd321.

import argparse

parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--batch", action='store', type=int,  metavar='pay_id')
group.add_argument("--list", action='store_true')
group.add_argument("--all", action='store_true', help='check all payments')

args=parser.parse_args()

if args.batch:
    print('batch {}'.format(args.batch))

if args.list:
    print('list')

if args.all:
    print('all')

Output:

$ python3 a_test.py
usage: a_test.py [-h] (--batch pay_id | --list | --all)
a_test.py: error: one of the arguments --batch --list --all is required

This only give the basic help. And some of the other answers will give you the full help. But at least your users know they can do -h

0
votes

This isn't good (also, because intercepts all errors), but:

def _error(parser):
    def wrapper(interceptor):
        parser.print_help()

        sys.exit(-1)

    return wrapper

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error(parser)

    parser.add_argument(...)
    ...

Here is definition of the error function of the ArgumentParser class:

https://github.com/python/cpython/blob/276eb67c29d05a93fbc22eea5470282e73700d20/Lib/argparse.py#L2374

. As you see, following signature, it takes two arguments. However, functions outside the class nothing knows about first argument: self, because, roughly speaking, this is parameter for the class. (I know, that you know...) Thereby, just pass own self and message in _error(...) can't (

def _error(self, message):
    self.print_help()

    sys.exit(-1)

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error
    ...
...

will output:

...
"AttributeError: 'str' object has no attribute 'print_help'"

). You can pass parser (self) in _error function, by calling it:

def _error(self, message):
    self.print_help()

    sys.exit(-1)

def _args_get(args=sys.argv[1:]):
    parser = argparser.ArgumentParser()

    parser.error = _error(parser)
    ...
...

, but you don't want exit the program, right now. Then return it:

def _error(parser):
    def wrapper():
        parser.print_help()

        sys.exit(-1)

    return wrapper
...

. Nonetheless, parser doesn't know, that it has been modified, thus when an error occurs, it will send cause of it (by the way, its localized translation). Well, then intercept it:

def _error(parser):
    def wrapper(interceptor):
        parser.print_help()

        sys.exit(-1)

    return wrapper
...

. Now, when error occurs and parser will send cause of it, you'll intercept it, look at this, and... throw out.

0
votes

I like to keep things as simple as possible, this works great:

#!/usr/bin/env python3
Description = """Tool description"""
Epilog  = """toolname.py -a aflag -b bflag  with these combined it does blah"""
arg_parser = argparse.ArgumentParser(
    formatter_class=argparse.RawDescriptionHelpFormatter,
    description=Description, 
    epilog=Epilog,
)
    try:
        if len(sys.argv) == 1:
            arg_parser.print_help()
    except Exception as e:
        print(e)

This is how I start all my tools as its always good to include some examples

0
votes

So for a really simple answer. Most of the time with argparse you are checking to see if parameters are set anyway, to call a function that does something.

If no parameters, just else out at the end and print the help. Simple and works.

import argparse
import sys
parser = argparse.ArgumentParser()

group = parser.add_mutually_exclusive_group()
group.add_argument("--holidays", action='store_true')
group.add_argument("--people", action='store_true')

args=parser.parse_args()
if args.holidays:
    get_holidays()
elif args.people:
    get_people()
else:
    parser.print_help(sys.stderr)