pythonargparsesubparsers

Argparse: Optional argument with choices that have variable arguments


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

Solution

  • 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.