4

I have a Python program that maintains a list of contacts and I want it to support following options through the command line:

  1. --show , takes a string argument
  2. --list , takes no argument
  3. --add , takes a string argument
  4. --number , takes an int argument
  5. --email , takes a string argument

What I need is:

prog [--show xyz | --list | --add xyz --number 123 --email [email protected] ]

I tried to implement it using subparsers as follows:

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

subparser1 = subparsers.add_parser('1')
subparser1.add_argument('--show', type=str, help="Shows the contact based on the given name provided as argument")
subparser1.add_argument('--list', action='store_true', help= "Prints all the contacts")

subparser2 = subparsers.add_parser('2')

subparser2.add_argument('--add', type=str, help="Adds a new contact by this name",required=True)
subparser2.add_argument('--number', type=int, help="The Phone Number for the new contact",required=True)
subparser2.add_argument('--email', type=str, help="Email address for the new contact",required=True)

The problem is that I don't to want to provide the number/name of the subparser I wanna use through the command-line.

E.g:

prog.py 1 --list
prog.py 2 --add xyz --number 1234 --email [email protected]

I tried to make it work with mutually_exclusive_group but couldn't. Is there a way around this usage?

1
  • 2
    Usually this sort of command line would be designed without the -- on the commands show, list, and add Commented Sep 16, 2018 at 17:22

3 Answers 3

5

Do you really need the double dash (--) before your command? If not, you could do:

import argparse

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')

show_subparser = subparsers.add_parser('show')
show_subparser.add_argument('name', type=str)

list_subparser = subparsers.add_parser('list')

add_subparser = subparsers.add_parser('add')
add_subparser.add_argument('phone', type=int)

args = parser.parse_args()

# Do something with your args
print args

This would limit you to the above defined arguments. Inasmuch you can do either prog show xyz or prog add 123 but you can't do prog show xzy add 123.

Sign up to request clarification or add additional context in comments.

Comments

3

You could use add_mutually_exclusive_group method like so:

from argparse import ArgumentParser

parser = ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--show', help='...', action='store_true')
group.add_argument('--list', help='...', action='store_true')
group.add_argument('--add',  type=str, help='...')

parser.add_argument("--number", type=int, required=False)
parser.add_argument("--email", type=str, required=False)

args = parser.parse_args()

if args.add:
    # proceed with args.number and args.email
else:
    # ignore args.number and args.email...

Output:

$ python test.py
usage: test.py [-h] (--show | --list | --add ADD) [--number NUMBER]
               [--email EMAIL]
test.py: error: one of the arguments --show --list --add is required

3 Comments

Ignoring arguments that you don't need is certainly a viable approach. It fits with my philosophy of using argparse primarily as a way of discovering what the user wants. Some people might want to impose more control, and raise an error when the user provides extra stuff.
@hpaulj I agree with you! I think that handling extra errors in the code logic is much more convenient than creating a very complex argument parser, especially in large projects with many arguments.
In "if args.add: " I just added the code to check that args.number & args.email must also be present and that solved my problem. Thanks @game0ver
1

I generally prefer the click library for this sort of thing. Your example (without the -- in front of the commands) could be done like:

Code:

import click

@click.group()
def cli():
    """My Cool Tool"""


@cli.command()
@click.argument('name')
def show(name):
    """Shows the contact based on the given name provided as argument"""
    click.echo('Show me some cool things about {}'.format(name))


@cli.command('list')
def list_cmd():
    """Prints all the contacts"""
    click.echo('Here are some contacts')


@cli.command()
@click.argument('name')
@click.option('--number', required=True, help='The Phone Number for the new contact')
@click.option('--email', required=True, help='Email address for the new contact')
def add(name, number, email):
    """Adds a new contact by this name"""
    click.echo('Name: {}'.format(name))
    click.echo('Number: {}'.format(number))
    click.echo('Email: {}'.format(email))

Test Code:

if __name__ == "__main__":
    commands = (
        'show a_name',
        'list',
        'add a_name --number 123 --email e@mail',
        '',
        'show --help',
        'list --help',
        'add --help',
    )

    import sys, time

    time.sleep(1)
    print('Click Version: {}'.format(click.__version__))
    print('Python Version: {}'.format(sys.version))
    for cmd in commands:
        try:
            time.sleep(0.1)
            print('-----------')
            print('> ' + cmd)
            time.sleep(0.1)
            cli(cmd.split())

        except BaseException as exc:
            if str(exc) != '0' and \
                    not isinstance(exc, (click.ClickException, SystemExit)):
                raise

Test Results:

Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> show a_name
Show me some cool things about a_name
-----------
> list
Here are some contacts
-----------
> add a_name --number 123 --email e@mail
Name: a_name
Number: 123
Email: e@mail
-----------
> 
Usage: test.py [OPTIONS] COMMAND [ARGS]...

  My Cool Tool

Options:
  --help  Show this message and exit.

Commands:
  add   Adds a new contact by this name
  list  Prints all the contacts
  show  Shows the contact based on the given name...
-----------
> show --help
Usage: test.py show [OPTIONS] NAME

  Shows the contact based on the given name provided as argument

Options:
  --help  Show this message and exit.
-----------
> list --help
Usage: test.py list [OPTIONS]

  Prints all the contacts

Options:
  --help  Show this message and exit.
-----------
> add --help
Usage: test.py add [OPTIONS] NAME

  Adds a new contact by this name

Options:
  --number TEXT  The Phone Number for the new contact  [required]
  --email TEXT   Email address for the new contact  [required]
  --help         Show this message and exit.

2 Comments

That looks like argparse with 3 subparsers, {'show', 'list', 'add'}. Just like @Leopold's answer.
@hpaulj, Yep that is true. Big difference using click is that the above is an entire program. The framework injects the options and arguments directly into the decorated functions. Also click is generally more composable than argparse/optparse. Over time I find the programs more easily maintainable. Cheers.

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.