Click Cheat Sheet
Overview
Click (Command Line Interface Creation Kit) is a Python package for creating command-line interfaces. It uses decorators to define commands, arguments, and options, providing automatic help generation, input validation, and composable command groups. Click is the foundation for many popular CLI tools including Flask, pip, and Typer.
Click emphasizes composability and minimal boilerplate. Commands can be nested into groups, options support various types with automatic validation, and the framework handles file I/O, environment variables, and shell completion out of the box.
Installation
pip install click
# Verify
python -c "import click; print(click.__version__)"
Basic Usage
Simple Command
import click
@click.command()
@click.option('--name', '-n', default='World', help='Name to greet')
@click.option('--count', '-c', default=1, type=int, help='Number of greetings')
def hello(name, count):
"""Simple program that greets NAME."""
for _ in range(count):
click.echo(f'Hello, {name}!')
if __name__ == '__main__':
hello()
python hello.py --name Alice --count 3
python hello.py -n Alice -c 3
python hello.py --help
Arguments vs Options
import click
@click.command()
@click.argument('filename')
@click.argument('output', default='-') # Default to stdout
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
@click.option('--format', 'fmt', type=click.Choice(['json', 'csv', 'yaml']),
default='json', help='Output format')
def process(filename, output, verbose, fmt):
"""Process FILENAME and write to OUTPUT."""
if verbose:
click.echo(f'Processing {filename} as {fmt}')
click.echo(f'Writing to {output}')
if __name__ == '__main__':
process()
Options
Common Option Types
import click
@click.command()
@click.option('--name', type=str, required=True, help='User name')
@click.option('--age', type=int, default=25, help='User age')
@click.option('--score', type=float, help='Score value')
@click.option('--active/--no-active', default=True, help='Active status')
@click.option('--verbose', '-v', is_flag=True, help='Verbose mode')
@click.option('--count', '-c', count=True, help='Verbosity level (-ccc)')
@click.option('--env', type=click.Choice(['dev', 'staging', 'prod']),
default='dev', help='Environment')
@click.option('--config', type=click.Path(exists=True), help='Config file')
@click.option('--password', prompt=True, hide_input=True, help='Password')
def main(name, age, score, active, verbose, count, env, config, password):
"""Demo of option types."""
click.echo(f'Name: {name}, Age: {age}, Env: {env}')
if __name__ == '__main__':
main()
Multiple Values
@click.command()
@click.option('--tag', '-t', multiple=True, help='Tags (can repeat)')
@click.option('--item', nargs=2, type=(str, int), help='Item name and count')
@click.option('--port', type=click.IntRange(1, 65535), default=8080)
def cmd(tag, item, port):
"""Handle multiple values."""
for t in tag:
click.echo(f'Tag: {t}')
if item:
click.echo(f'Item: {item[0]} x {item[1]}')
python cmd.py --tag web --tag api --tag v2 --item widget 5 --port 9000
Environment Variables
@click.command()
@click.option('--api-key', envvar='API_KEY', help='API key')
@click.option('--debug', envvar='DEBUG', is_flag=True, help='Debug mode')
@click.option('--db-url', envvar='DATABASE_URL', required=True)
def app(api_key, debug, db_url):
"""Uses environment variables as defaults."""
click.echo(f'DB: {db_url}')
Command Groups
Basic Group
import click
@click.group()
@click.option('--debug/--no-debug', default=False)
@click.pass_context
def cli(ctx, debug):
"""My CLI application."""
ctx.ensure_object(dict)
ctx.obj['DEBUG'] = debug
@cli.command()
@click.argument('name')
@click.pass_context
def create(ctx, name):
"""Create a resource."""
click.echo(f'Creating {name} (debug={ctx.obj["DEBUG"]})')
@cli.command()
@click.argument('name')
@click.option('--force', is_flag=True)
def delete(name, force):
"""Delete a resource."""
if force or click.confirm(f'Delete {name}?'):
click.echo(f'Deleted {name}')
@cli.command('list')
@click.option('--limit', default=10, type=int)
def list_resources(limit):
"""List resources."""
click.echo(f'Showing {limit} resources')
if __name__ == '__main__':
cli()
python app.py --debug create myresource
python app.py delete myresource --force
python app.py list --limit 20
Nested Groups
@click.group()
def cli():
pass
@cli.group()
def db():
"""Database commands."""
pass
@db.command()
def migrate():
"""Run database migrations."""
click.echo('Running migrations...')
@db.command()
@click.option('--count', default=100)
def seed(count):
"""Seed database with test data."""
click.echo(f'Seeding {count} records')
@cli.group()
def users():
"""User management."""
pass
@users.command()
@click.argument('email')
def create(email):
click.echo(f'Created user: {email}')
python app.py db migrate
python app.py db seed --count 500
python app.py users create alice@example.com
Configuration
File I/O
@click.command()
@click.argument('input', type=click.File('r'))
@click.argument('output', type=click.File('w'), default='-')
def process(input, output):
"""Process INPUT file, write to OUTPUT (default stdout)."""
for line in input:
output.write(line.upper())
python process.py data.txt output.txt
cat data.txt | python process.py - -
Custom Types
class DateType(click.ParamType):
name = "date"
def convert(self, value, param, ctx):
from datetime import datetime
try:
return datetime.strptime(value, '%Y-%m-%d').date()
except ValueError:
self.fail(f'{value} is not a valid date (YYYY-MM-DD)', param, ctx)
DATE = DateType()
@click.command()
@click.option('--since', type=DATE, help='Start date (YYYY-MM-DD)')
def report(since):
click.echo(f'Report since: {since}')
Advanced Usage
Click Context and Pass Decorators
@click.group()
@click.option('--config', type=click.Path(), default='config.yaml')
@click.pass_context
def cli(ctx, config):
ctx.ensure_object(dict)
ctx.obj['config'] = config
@cli.command()
@click.pass_context
def deploy(ctx):
click.echo(f'Using config: {ctx.obj["config"]}')
Callbacks and Eager Options
def print_version(ctx, param, value):
if not value or ctx.resilient_parsing:
return
click.echo('v1.0.0')
ctx.exit()
@click.command()
@click.option('--version', is_flag=True, callback=print_version,
expose_value=False, is_eager=True)
def cli():
click.echo('Running app...')
Output Helpers
# Colored output
click.secho('Error!', fg='red', bold=True)
click.secho('Success!', fg='green')
click.secho('Warning!', fg='yellow')
# Paging
click.echo_via_pager('\n'.join(f'Line {i}' for i in range(1000)))
# Progress bar
with click.progressbar(range(1000), label='Processing') as bar:
for item in bar:
pass # do work
# Confirmation
if click.confirm('Continue?'):
click.echo('Proceeding...')
# Prompt with default
name = click.prompt('Your name', default='World')
Testing Click Commands
from click.testing import CliRunner
def test_hello():
runner = CliRunner()
result = runner.invoke(hello, ['--name', 'Alice'])
assert result.exit_code == 0
assert 'Hello, Alice!' in result.output
def test_file_processing():
runner = CliRunner()
with runner.isolated_filesystem():
with open('test.txt', 'w') as f:
f.write('hello\n')
result = runner.invoke(process, ['test.txt'])
assert result.exit_code == 0
Troubleshooting
| Issue | Solution |
|---|---|
| Option/argument naming conflict | Use different Python parameter name with expose |
--help not showing | Ensure @click.command() decorator is present |
| Context not available | Use @click.pass_context and ctx.ensure_object() |
| Boolean flag defaults wrong | Use --flag/--no-flag pattern explicitly |
| Unicode errors | Use click.echo() instead of print() |
| Missing required option | Click auto-generates error; use required=True |
# Show help at any level
python app.py --help
python app.py db --help
python app.py db migrate --help
# Shell completion (bash)
eval "$(_APP_COMPLETE=bash_source python app.py)"
# Shell completion (zsh)
eval "$(_APP_COMPLETE=zsh_source python app.py)"