How to skip a positional argument in Python Argparse

I use argparse to parse arguments for my scripts. Now I would like to write a program which runs a specific tool if the first argument (after the program name) is the name of the tool (which is closed in a class). For example if i want to run the "counter" tool I have to type:

python myscript.py counter filename

whereas if I want to run "fasta2bed" I have to type:

python myscript.py fasta2bed filename

I wrote this code but it seems like you can't skip positional arguments with Argparse:

import argparse

parser=argparse.ArgumentParser(
    usage="""python myscript.py {toolname} filename [-option]""",
    description='''Description.''',
    epilog="""Epilog.""")
parser.add_argument('counter', nargs='?', choices=['counter'],        help='count how many features are in the input file [-l][-s]')
parser.add_argument('fasta2bed', nargs='?', choices=['fasta2bed'],    help='read sequences in FASTA format and print out BED format')
parser.add_argument('filename', help='the input file name')
parser.add_argument('-l', '--long', action='store_true', help='retrive a    long summary file (default)')
parser.add_argument('-s', '--short', action='store_true', help='retrive a    short summary file')
args=parser.parse_args()

The problem is that when I try to run python myscript.py fasta2bed filename it doesn't work because it needs counter.

So I try to insert all in a single argument like this:

parser.add_argument('tool', nargs='?', choices=['counter', 'fasta2bed'], help='help')

with this aim:

data = open("inputfile", "r")

if args.tool == "counter":
    counter(data).summarize()      #summarize is a function present in the counter class
elif args.tool == "fasta2bed":
    fasta2bed(data)                #fasta2bed is just a function

but it doesn't work because it runs counter instead of fasta2bed...

How can I make this work?


To my knowledge, you can't skip an argument the way you seem to want to. That said, it seems you don't really want to skip one. The way to do this is change the way you're looking at the problem. What would work is to have a single argument that accepts either 'counter' or 'fasta2bed'. Changing the relevant lines in your code might produce something like:

import argparse
parser=argparse.ArgumentParser(
    usage="""python myscript.py {toolname} filename [-option]""",
    description='''Description.''',
    epilog="""Epilog.""")
parser.add_argument('toolname', choices=['counter', 'fasta2bed'], help='the name of the tool to be used')
parser.add_argument('filename', help='the input file name')
parser.add_argument('-l', '--long', action='store_true',     help='retrive a long summary file (default)')
parser.add_argument('-s', '--short', action='store_true', help='retrive a short summary file')
args=parser.parse_args()

Hopefully this will help get you started.

Edit for additional material:

You can check the choices for toolname by checking args.toolname:

if args.toolname == 'counter':
    print 'Running counter'
else:
    print 'Running fasta2bed'

Note that you'll need to use elif if you have more than 2 choices.


In a copy of your script with this line

parser.add_argument('tool', choices=['counter', 'fasta2bed'], help='help')

and

print(args)

I get the desired behavior

0827:~/mypy$ python stack33480471.py counter filename
Namespace(filename='filename', long=False, short=False, tool='counter')
0828:~/mypy$ python stack33480471.py fasta2bed filename
Namespace(filename='filename', long=False, short=False, tool='fasta2bed')
0828:~/mypy$ python stack33480471.py tool filename
usage: python myscript.py {toolname} filename [-option]
stack33480471.py: error: argument tool: invalid choice: 'tool' (choose from 'counter', 'fasta2bed')

Note that I dropped the nargs='?'. Parsing is clearer and more predictable that way. And I am just displaying the args. You want a clear idea of what the resulting namespace is like before getting lost testing specific values. What should happen if the user gives neither choice?

Someone else may suggest using subparsers. I think that's an advanced tool that often causes more confusion.

The use of store_true optionals is also a good idea. But it requires the use of '--counter' rather than 'counter'. That's not bad, just different.