2

I have a CLI that I'm trying to improve. What I would like to do is have an optional argument with 3 choices, and depending on your choice you are required to enter certain arguments to that choice.

For example:

--create dog DOG_NAME DOG_BREED
OR
--create cat CAT_NAME
OR
--create fish FISH_BREED FISH_TANK

etc.

So this would look something along the lines of:

parser.add_argument("--create", help="Create an animal", choices=["dog", "cat", "fish"])

But how do I have different required arguments for each of the choices? Do I have to use subparser?

EDIT: Went with a slightly different schema and got it to work.

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(title="actions", dest="subcmd")
subp_create = subparsers.add_parser("create", description="Create a Dog, Cat, or Fish")
subp_delete = subparsers.add_parser("delete", description="Delete a Dog, Cat, or Fish")

subp_create.add_argument("--dog", help="Create a Dog", nargs=2, metavar=(DOG_NAME, DOG_BREED))
#etc.

args = parser.parse_args()
handle_args(args)

def handle_args(args):
    if args.subcmd == "create":
        if args.dog:
            dog_name = args.dog[0]
            dog_breed = args.dog[1]
            #Do whatever you need
dzzl
  • 105
  • 1
  • 1
  • 11
  • this might be useful: https://stackoverflow.com/questions/19414060/argparse-required-argument-y-if-x-is-present – Corley Brigman Nov 20 '19 at 15:53
  • Similar but not exactly. If I were to apply that to the above example I'd need positional arguments for all the possible arguments i.e ```parser.add_argument("dog_name")``` no? – dzzl Nov 20 '19 at 16:15
  • Yes, subparser is the way to go. The subparsers argument is effectively a positional (not flagged argument as you have) with 'choices', and for each choice a parser with its own set of arguments (required or not). – hpaulj Nov 20 '19 at 17:07
  • If you stick with '--create', you'll need to test the other arguments after parsing. – hpaulj Nov 21 '19 at 00:16

2 Answers2

2

It looks like this might be possible with subparsers, but you could also try using click. An example which worked on my system:

#!/usr/bin/env python3

import click

@click.group('parent')
def parent():
    pass

@parent.group('create')
def create():
    pass

@create.command()
@click.argument('name')
@click.argument('breed')
def dog(name, breed):
    print(f'Dog: {name}: {breed}')

@create.command()
@click.argument('name')
def cat(name):
    print(f'Cat: {name}')

@create.command()
@click.argument('breed')
@click.argument('tank')
def fish(breed, tank):
    print(f'Fish of {breed} in {tank}')

if __name__ == '__main__':
    parent()

And when run:

$ ./click_test.py --help
Usage: click_test.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  create

and

$ ./click_test.py create --help
Usage: click_test.py create [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  cat
  dog
  fish

and then farther down

$ ./click_test.py create dog
Usage: click_test.py create dog [OPTIONS] NAME BREED
Try "click_test.py create dog --help" for help.

Error: Missing argument "NAME".
$ ./click_test.py create dog Fido Labrador
Dog: Fido: Labrador

I wasn't actually that interested in click before - i thought it would be too limiting for some complex cases that I wanted - but it actually is pretty nice for this case.

Corley Brigman
  • 11,633
  • 5
  • 33
  • 40
  • 1
    I can second the case for Click. I've used it in production environments a couple of times and a very solid solution. – Adam Smith Nov 21 '19 at 06:06
  • Thanks for the solution! I did try using Click but actually went with a bit of a different schema for the cli and was able to come up with something that fit my needs. I'll update my post with what I went with. – dzzl Nov 21 '19 at 16:31
0

Try passing your multiple arguments list surrounded by quotes like:

--create dog "DOG_NAME DOG_BREED"

You can also separate arguments with a coma like

--create dog DOG_NAME,DOG_BREED

use split() to get an argument list.

Tim
  • 2,052
  • 21
  • 30
  • Thanks for the response. I guess I'm asking how to have different required arguments based on what arguments are already present. i.e the root command is the same - in this example ```--create``` - but depending on what choice you select there will be additional required arguments. – dzzl Nov 20 '19 at 16:13
  • I think you'll have to implement it in your program's logic. Why not just call `--create dog` or `--create cat` and call appropriate logic for each then? – Tim Nov 20 '19 at 16:18
  • I could do that but if it's possible I'd like to do it this way. Even if it's for no other reason rather than to learn. – dzzl Nov 20 '19 at 16:20
  • I'm afraid Argparse will not help you to achieve that. I am pretty sure that there is no other way than putting the algorithm in your code. Creating a subparser is a good way to start. – Tim Nov 20 '19 at 20:45