1

I'm trying to build a CLI in Python and I have an argument (--arg) that I want to reuse across multiple subcommands (req and opt).

But one subcommand (req) will have to require --arg and the other (opt) doesn't. How do I resolve this without having to make two versions of the same argument?

import argparse
arg_1 = argparse.ArgumentParser(add_help=False)
arg_1.add_argument('-a', '--arg', required=True,
                    help='reusable argument')

parser = argparse.ArgumentParser()
subp = parser.add_subparsers()

cmd_require = subp.add_parser('req', parents=[arg_1],
                                help='this subcommand requires --arg')  
cmd_optional = subp.add_parser('opt', parents=[arg_1],
                                help='this subcommand doesn\'t require --arg')  
what the
  • 398
  • 3
  • 10

2 Answers2

3

I don't know any 'native' argparse feature that does that. However, I thought of 2 different approaches to solve your problem.

Validate args in a separate function - Sometimes CLI application get complicated and by adding a validator function you can 'complete' the missing argparse features you wish for.

import argparse
arg_1 = argparse.ArgumentParser(add_help=False)
arg_1.add_argument('-a', '--arg', required=False,
                    help='reusable argument')

parser = argparse.ArgumentParser()
subp = parser.add_subparsers(dest='sub_parser')

cmd_require = subp.add_parser('req', parents=[arg_1],
                                help='this subcommand requires --arg')  
cmd_optional = subp.add_parser('opt', parents=[arg_1],
                                help='this subcommand doesn\'t require --arg') 

def validate_args(args):
    print(args)
    if args.sub_parser == 'req' and not args.arg:
        print("Invalid usage! using 'req' requires 'arg'")
        exit(1)

if __name__ == '__main__':
    args = parser.parse_args()
    validate_args(args)

Note:

  1. I used dest for the subparser in order to later identify the chosen subparser.
  2. Using argparse, if an optional argument is not passed it will be 'None'

"prepared argument" - Although argparse doesn't support an argument object - you could 'prepare' an argument by unpacking a dict and a tuple (*args, **kwargs)

import argparse
arg_name = ('-a', '--arg')
arg_dict = {'help': 'reusable argument'}
parser = argparse.ArgumentParser()
subp = parser.add_subparsers()

cmd_require = subp.add_parser('req',
                                help='this subcommand requires --arg')  
cmd_optional = subp.add_parser('opt',
                                help='this subcommand doesn\'t require --arg') 

cmd_optional.add_argument(*arg_name, **arg_dict, required=False)
cmd_require.add_argument(*arg_name, **arg_dict, required=True)

if __name__ == '__main__':
    args = parser.parse_args()
    validate_args(args)

I like the first approach better.

Hope you find that useful

Y.R.
  • 371
  • 1
  • 7
1
import argparse
arg_1 = argparse.ArgumentParser(add_help=False)
foobar = arg_1.add_argument('-a', '--arg', required=True,
                    help='reusable argument')

arg_1 is a parser object. When you use the add_argument command, it creates an Action object and adds it to the args_1._actions list. I just saved a reference to that in the foobar variable.

parser = argparse.ArgumentParser()
subp = parser.add_subparsers()

The parents mechanism adds the args_1._actions list to the cmd_require._actions list. So foobar will appear in both subparsers. It's copy by reference, which is common in python.

cmd_require = subp.add_parser('req', parents=[arg_1],
                                help='this subcommand requires --arg')  
cmd_optional = subp.add_parser('opt', parents=[arg_1],
                                help='this subcommand doesn\'t require --arg') 

foobar.required=False will turn off at attribute, but will do so for both parsers. I've seen this issue come up when people wanted to assign different default attributes.

The parents mechanism just a shortcut, that occasionally is useful, but not always. It doesn't do anything special; just saves a bit of typing. There are plenty other ways of defining an Action with the same flags in both subparsers.

  • typing it twice (horror of horrors!)
  • copy-n-paste
  • writing a utility function to add arguments to subparsers (see Larry Wall's Three Virtues)
hpaulj
  • 221,503
  • 14
  • 230
  • 353