4

I want to create argument parser with following signature:

./myapp [-a [-b BVAL] | -c]

In other words, user could provide argument -b BVAL only in case if he provided argument -a.

It's quite easy to create mutually exclusive group of -a and -c, but I can't figure out how to create relationship allow -b only if -a provided

4 Answers 4

4
+50

You could inherit from ArgumentParser to add some custom functionality. Here I am raising an exception, but you could modify this to implement whatever you would like. Just change the on_dependency_error() method to suit your needs.

from argparse import ArgumentParser

class FancyParser(ArgumentParser):
    # {'b': 'a'} Where b depends on a
    dependencies = {}

    def on_dependency_error(self, arg, depends_on):
        raise FancyParser.DependencyError(
                    'Argument %s depends on %s' % (arg, depends_on))

    def add_argument(self, *args, **kwargs):
        depends_on = kwargs.get('depends_on')
        if depends_on:
            self.dependencies[kwargs.get('dest') or args[0]] = depends_on
            del kwargs['depends_on']
        return super(FancyParser, self).add_argument(*args, **kwargs)

    def parse_args(self, *args, **kwargs):
        args = super(FancyParser, self).parse_args(*args, **kwargs)
        for arg, depends_on in self.dependencies.iteritems():
            if getattr(args, arg) and not getattr(args, depends_on):
                self.on_dependency_error(arg, depends_on)
        return args

    class DependencyError(Exception):
        def __init__(self, *args, **kwargs):
            return super(FancyParser.DependencyError,
                         self).__init__(*args, **kwargs)

You can then use it like this -

args = ['-a', '-b', 'BVAL', '-c']
parser = FancyParser()
parser.add_argument('-a', dest='a', action='store_true')
parser.add_argument('-b', dest='b', depends_on='a')
parser.add_argument('-c', dest='c', action='store_true')
try:
    parser.parse_args(args)
except FancyParser.DependencyError as e:
    # Whatever here...
    pass
Sign up to request clarification or add additional context in comments.

1 Comment

No problem! Just made an update to the parse_args() method to better test for dependency. You'll want to use the updated code. Thanks!
4

Docopt does it just as I wanted. Terrific!

docopt('./myapp [-a [-b BVAL] | -c]')

1 Comment

This needs to be in the STANDARD LIBRARY! How many questions have you seen where someone posts the usage doc they want and then can't get argparse to create it for them? That's just backwards and wrong. We know how to interpret a usage doc. Why can't our code? Docopt does.
2

It's not exactly what you're looking for but maybe what you could use add_subparsers() (doc)?

Do something like:

import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(help='sub-command help')
a = subparsers.add_parser('a')
c = subparsers.add_parser('c')
a.add_argument('b')

2 Comments

Yes, this sounds reasonable. Let's wait if anyone could offer any solution a bit closer to what I'm looking for. If no better answer given, I'll go with subparsers, thank you.
Subparsers is what I have used before for this purpose.
2

If you don't want to use subparsers, you can handle your argument values yourself using parser.error.

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-a', dest='a', default='')  # you can use other defaults surely
parser.add_argument('-b', dest='b', default='')
parser.add_argument('-c', dest='c', default='')

args = parser.parse_args()

if args.b and not args.a:
    parser.error("Option 'b' can't be specified without 'a'")

But still consider using subparsers in case you might extend the logic

Comments

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.