sebastianwth
sebastianwth

Reputation: 187

Python argparse, handle either several positional arguments OR optional argument

I have the following code:

parser = argparse.ArgumentParser(description='')

parser.add_argument('-l', '--login', action='store_true')
parser.add_argument('FILTER1')
parser.add_argument('FILTER2')
parser.add_argument('FILTER3')

I'd like "--login" to be mutually exclusive from FILTER1, FILTER2, FILTER3. Also, FILTER1, FILTER2, FILTER3 are all required when any of the 3 are mentioned.

So either:

cli FILTER1 FILTER2 FILTER3

OR

cli --login

Upvotes: 0

Views: 1169

Answers (2)

Jasmijn
Jasmijn

Reputation: 10452

You could use ArgumentParser.add_mutually_exclusive_group, but it requires you to use optional arguments.

parser = argparse.ArgumentParser(description='')

group = parser.add_mutually_exclusive_group() 

group.add_argument('-l', '--login', action='store_true')
group.add_argument('-f', '--filter', nargs=3)

Pass required=True to add_mutually_exclusive_group if you want either --login or --filter to be required.

Upvotes: 0

wjandrea
wjandrea

Reputation: 32964

The most straightforward way to do this with argparse is to make the filters into an optional argument followed by nargs=3 in a mutually exclusive group with "login". Otherwise, you could do your own parsing and make each filter an optional positional.

Filters as optional

import argparse

parser = argparse.ArgumentParser()

meg = parser.add_mutually_exclusive_group()
meg.add_argument('-l', '--login', action='store_true')
meg.add_argument('-f', '--filters', nargs=3, metavar=('FILTER1', 'FILTER2', 'FILTER3'))

for arg_string in '-l', '-f 1 2 3', '-l -f 4 5 6', '-f 8 9':
    args = arg_string.split()
    try:  # This try-block for demo
        ns = parser.parse_args(args)
    except SystemExit:
        pass
    else:
        print(ns)
    print()

Output:

Namespace(login=True, filters=None)

Namespace(login=False, filters=['1', '2', '3'])

usage: tmp.py [-h] [-l | -f FILTER1 FILTER2 FILTER3]
tmp.py: error: argument -f/--filters: not allowed with argument -l/--login

usage: tmp.py [-h] [-l | -f FILTER1 FILTER2 FILTER3]
tmp.py: error: argument -f/--filters: expected 3 arguments

Own parsing

import argparse

parser = argparse.ArgumentParser()

parser.add_argument('-l', '--login', action='store_true')
parser.add_argument('FILTER1', nargs='?')
parser.add_argument('FILTER2', nargs='?')
parser.add_argument('FILTER3', nargs='?')

for arg_string in '-l', '1 2 3', '-l 4 5 6', '8 9':
    args = arg_string.split()
    ns = parser.parse_args(args)
    print(ns)
    try:  # This try-block for demo
        if ns.login and ns.FILTER1 is not None:
            parser.error("filters not allowed with argument -l/--login")
        elif not ns.login and any(a is None for a in [ns.FILTER1, ns.FILTER2, ns.FILTER3]):
            parser.error("Expected three filters")
    except SystemExit:
        pass
    print()

Output:

Namespace(login=True, FILTER1=None, FILTER2=None, FILTER3=None)

Namespace(login=False, FILTER1='1', FILTER2='2', FILTER3='3')

Namespace(login=True, FILTER1='4', FILTER2='5', FILTER3='6')
usage: tmp.py [-h] [-l] [FILTER1] [FILTER2] [FILTER3]
tmp.py: error: filters not allowed with argument -l/--login

Namespace(login=False, FILTER1='8', FILTER2='9', FILTER3=None)
usage: tmp.py [-h] [-l] [FILTER1] [FILTER2] [FILTER3]
tmp.py: error: Expected three filters

You should also document this behaviour since argparse won't do it for you. You might also want to change the parser.usage. This can be a pain, so that's why I recommend the first option personally.

Upvotes: 2

Related Questions