How would I structure a kubectl-like CLI with python-click?

Viewed 290

I would like to create a CLI that receives an action and resource parameters to work, in a kubectl fashion. Eg.

myctl init config
myctl create issue|pr|branch
myctl delete issue|pr|branch|config

myctl command should always receive 2 arguments, so that the user wouldn't try something like: myctl init config delete issue. Also the should not be able to execute impossible combinations such as myctl create config.

I though of some code like:

import click

@click.command()
@click.group()
@click.option(
    "init,create,delete",
    type=str,
    help="Action name.",
)
@click.option(
    "config,issue,pr,branch",
    type=str,
    help="Resource name.",
)
def main(action: str, resource: str) -> None:
    pass

if __name__ == "__main__":
    main(prog_name="myctl")

I am not really sure what click elements should I use in order to structure that (arguments, options, groups, etc) and how to put that together.

1 Answers

I've achieved this in the past by creating multiple nested groups.

import click


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


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


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


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


@init.command('config')
def config():
    print('Configuration complete')


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


@create.command('pr')
@click.argument('base_branch', required=False, default='master')
def pr(base_branch):
    print(f'Created PR against {base_branch}')


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


@delete.command('issue')
@click.argument('issue_number')
def delete_issue(issue_number):
    print(f'Deleting issue {issue_number}')


@delete.command('pr')
@click.argument('pr_number')
def delete_pr(pr_number):
    print(f'Deleting PR {pr_number}')


@delete.command('branch')
@click.argument('branch_name')
def delete_branch(branch_name):
    print(f'Deleting branch {branch_name}')


@delete.command('config')
@click.argument('config_name')
def delete_config(config_name):
    print(f'Deleting config {config_name}')


cli.add_command(init)
cli.add_command(create)
cli.add_command(delete)


def run_cli():
    cli()


if __name__ == "__main__":
    run_cli()

You can then expand this however you want, with invocations looking like the following. I've called my CLI play.

❯ play
Usage: play [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  create
  delete
  init

❯ play init
Usage: play init [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  config

❯ play init config
Configuration complete

❯ play create
Usage: play create [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  branch
  issue
  pr

❯ play create branch feature/the-coolest
Create branch feature/the-coolest

You can then proceed to add short help messages and customise it to your application.

Related