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

zenpoy
  • 19,490
  • 9
  • 60
  • 87
kovpas
  • 9,553
  • 6
  • 40
  • 44

4 Answers4

4

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
pyrospade
  • 7,870
  • 4
  • 36
  • 52
  • 1
    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! – pyrospade Dec 24 '12 at 17:30
4

Docopt does it just as I wanted. Terrific!

docopt('./myapp [-a [-b BVAL] | -c]')
kovpas
  • 9,553
  • 6
  • 40
  • 44
  • 2
    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. – Bruno Bronosky May 12 '16 at 05:50
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')
zenpoy
  • 19,490
  • 9
  • 60
  • 87
  • 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. – kovpas Dec 19 '12 at 16:20
  • 1
    Subparsers is what I have used before for this purpose. – ken.ganong Dec 19 '12 at 18:52
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

Mikhail Karavashkin
  • 1,365
  • 10
  • 16