40

I have a program that uses a default name and password. I'm using argparse to allow the user to specify command line options, and I would like to enable the user to provide the program with a different name and password to use. So I have the following:

parser.add_argument(
    '-n',
    '--name',
    help='the login name that you wish the program to use'
    )

parser.add_argument(
    '-p',
    '--password',
    help='the password to log in with.'
    )

But it doesn't make any sense to specify only the name or only the password, but it would make sense to specify neither one. I noticed that argparse does have the ability to specify that two arguments are mutually exclusive. But what I have are two arguments that must appear together. How do I get this behavior? (I found "argument groups" mentioned in the docs, but they don't appear to solve my problem http://docs.python.org/2/library/argparse.html#argument-groups)

2
  • And I assume that post-processing the arguments is out the the question? Commented Jan 16, 2013 at 2:14
  • 1
    Nothing is out of the question. I just want argparse to do the work for me and tell the user that the options must appear together. Commented Jan 16, 2013 at 2:20

3 Answers 3

27

I know this is more than two years late, but I found a nice and concise way to do it:

if bool(ns.username) ^ bool(ns.password):
    parser.error('--username and --password must be given together')

^ is the XOR operator in Python. To require both arguments given at the command line is essentially an XOR test.

Sign up to request clarification or add additional context in comments.

2 Comments

Shouldn't this be XNOR?
@KaustubhKarkare XOR evaluated as True if only username or password is given. Above describes the failure case in such condition, so it should be XOR.
25

I believe that the best way to handle this is to post-process the returned namespace. The reason that argparse doesn't support this is because it parses arguments 1 at a time. It's easy for argparse to check to see if something was already parsed (which is why mutually-exclusive arguments work), but it isn't easy to see if something will be parsed in the future.

A simple:

parser.add_argument('-n','--name',...,default=None)
parser.add_argument('-p','--password',...,default=None)
ns = parser.parse_args()

if len([x for x in (ns.name,ns.password) if x is not None]) == 1:
   parser.error('--name and --password must be given together')

name = ns.name if ns.name is not None else "default_name"
password = ns.password if ns.password is not None else "default_password"

seems like it would suffice.

3 Comments

This is correct, but 'name = ns.name or "default_name"' is simpler and in my opinion easier to read that 'ns.name if ns.name is not None else ...'. However, as mgilson points out elsewhere, if the user passes an empty string for either value, the empty string will evaluate false and the default will be used instead of the provided empty string.
sum(1 for x in (ns.name, ns.password) if x is not None) also works in the place of len([x for x in (ns.name,ns.password) if x is not None]).
Wouldn't ns.name is None ^ ns.password is None be enough? :D
7

This is probably how I'd do it. Since you have existing defaults with the option to change, define the defaults, but don't use them as your argument defaults:

default_name = "x"
default_pass = "y"
parser.add_argument(
    '-n',
    '--name',
    default=None,
    help='the login name that you wish the program to use'
    )

parser.add_argument(
    '-p',
    '--password',
    default=None,
    help='the password to log in with.'
    )
args = parser.parse_args()
if all(i is not None for i in [args.name, args.password]):
    name = args.name
    passwd = args.password
elif any(i is not None for i in [args.name, args.password]):
    parser.error("Both Name and Password are Required!")
else:
    name = default_name
    passwd = default_pass

5 Comments

Careful with this one. What if the user sets up their password to an empty string?
As a side note, you can always edit your deleted answer and un-delete it :)
Thanks, it is kind of annoying to have it hanging there.
Well, more to the point, if you were to get 10 votes and the checkmark, you might get an enlightened badge :)
thanks, and updated with the check for None, instead of Falseness.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.