GregH
GregH

Reputation: 12858

Using click to create a complex command line interface

I am trying to do a CLI that accepts multiple arguments that I guess you would say are nested and are predefined. For example, say I am trying to create a utility that manages a relational database. I want commands like the following:

dbmgr.py create table --name="mytab"
dbmgr.py create view  --name="myview" --other-opt=...
dbmgr.py drop table --name=...
dbmgr.py drop user  --username=...

In this case there are a pre-defined set of operations ("create", "drop", etc) and each operation has a specific pre-defined set of objects that each operation can operate upon. In this case, the "create" operation can only accept the "table" or "view" object.

In the case of the "drop" operation, a user can only specify the "table", and "user" objects. In the case of click are "create", "table", "view", and "drop" just arguments? If so, how do I restrict what can be specified to specific values? I'm not sure if this is a case where groups, commands, etc are used and if so, how?

Upvotes: 2

Views: 576

Answers (1)

Stephen Rauch
Stephen Rauch

Reputation: 49794

What you are trying to do is the exact use case for click groups. In your example create and drop are groups, and table, view, etc... are commands. This type of structure is (in my opinion) what makes Click better then the other Python command line parsing libraries. It can describe these structures quite nicely.

Sample Code:

import click

@click.group()
def cli():
    """DB Manager CLI"""

@cli.group()
def create():
    """create objects"""

@create.command()
def table():
    click.echo('create table command')

@create.command()
def view():
    click.echo('create view command')

@cli.group()
def drop():
    """create objects"""

@drop.command()
def table():
    click.echo('drop table command')

@drop.command()
@click.option('--username')
def user(username):
    click.echo('drop user command: {}'.format(username))

if __name__ == "__main__":
    cli()

Test Code:

if __name__ == "__main__":
    commands = (
        'create table',
        'create view',
        'drop table',
        'drop user --username a-user',
        '--help',
        'drop --help',
        'drop user --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

Results:

Click Version: 7.0
Python Version: 3.8.1 (tags/v3.8.1:1b293b6, Dec 18 2019, 22:39:24) [MSC v.1916 32 bit (Intel)]
-----------
> create table
create table command
-----------
> create view
create view command
-----------
> drop table
drop table command
-----------
> drop user --username a-user
drop user command: a-user
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...

  DB Manager CLI

Options:
  --help  Show this message and exit.

Commands:
  create  create objects
  drop    create objects
-----------
> drop --help
Usage: test.py drop [OPTIONS] COMMAND [ARGS]...

  create objects

Options:
  --help  Show this message and exit.

Commands:
  table
  user
-----------
> drop user --help
Usage: test.py drop user [OPTIONS]

Options:
  --username TEXT
  --help           Show this message and exit.

Upvotes: 5

Related Questions