Click CLI Framework

Click is a Python package for creating beautiful command-line interfaces with minimal code. Uses decorators for a clean, composable API. Powers Flask, AWS CLI, and many other tools.

Use Case

Use Click when you need to:

  • Build CLI tools in Python
  • Create nested command groups
  • Handle complex option parsing
  • Generate automatic help pages

Installation

1pip install click

Code

Basic Command

 1import click
 2
 3@click.command()
 4@click.option('--name', default='World', help='Name to greet')
 5@click.option('--count', default=1, help='Number of greetings')
 6def hello(name, count):
 7    """Simple program that greets NAME COUNT times."""
 8    for _ in range(count):
 9        click.echo(f'Hello, {name}!')
10
11if __name__ == '__main__':
12    hello()

Examples

Example 1: Options and Arguments

 1import click
 2
 3@click.command()
 4@click.option('--verbose', '-v', is_flag=True, help='Verbose output')
 5@click.option('--output', '-o', type=click.Path(), help='Output file')
 6@click.option('--format', type=click.Choice(['json', 'yaml', 'text']), 
 7              default='text', help='Output format')
 8@click.argument('input_file', type=click.Path(exists=True))
 9def process(verbose, output, format, input_file):
10    """Process INPUT_FILE and optionally save to OUTPUT."""
11    if verbose:
12        click.echo(f'Processing {input_file}...')
13    
14    # Process file
15    result = f"Processed {input_file} as {format}"
16    
17    if output:
18        with open(output, 'w') as f:
19            f.write(result)
20        click.echo(f'Saved to {output}')
21    else:
22        click.echo(result)
23
24if __name__ == '__main__':
25    process()

Usage:

1$ python app.py input.txt
2Processed input.txt as text
3
4$ python app.py input.txt -v -o output.txt --format json
5Processing input.txt...
6Saved to output.txt

Example 2: Command Groups (Subcommands)

 1import click
 2
 3@click.group()
 4def cli():
 5    """My CLI application with subcommands."""
 6    pass
 7
 8@cli.command()
 9def init():
10    """Initialize the application."""
11    click.echo('Initializing...')
12
13@cli.command()
14@click.option('--port', default=8000, help='Port number')
15def serve(port):
16    """Start the server."""
17    click.echo(f'Starting server on port {port}...')
18
19@cli.group()
20def config():
21    """Manage configuration."""
22    pass
23
24@config.command('get')
25@click.argument('key')
26def config_get(key):
27    """Get configuration value."""
28    click.echo(f'Getting config: {key}')
29
30@config.command('set')
31@click.argument('key')
32@click.argument('value')
33def config_set(key, value):
34    """Set configuration value."""
35    click.echo(f'Setting {key} = {value}')
36
37if __name__ == '__main__':
38    cli()

Usage:

 1$ python app.py init
 2Initializing...
 3
 4$ python app.py serve --port 3000
 5Starting server on port 3000...
 6
 7$ python app.py config get database.host
 8Getting config: database.host
 9
10$ python app.py config set database.host localhost
11Setting database.host = localhost

Example 3: Prompts and Confirmation

 1import click
 2
 3@click.command()
 4@click.option('--name', prompt='Your name', help='Name to greet')
 5@click.option('--password', prompt=True, hide_input=True, 
 6              confirmation_prompt=True, help='Password')
 7@click.confirmation_option(prompt='Are you sure you want to continue?')
 8def secure_hello(name, password):
 9    """Secure greeting with prompts."""
10    click.echo(f'Hello, {name}!')
11    click.echo('Password accepted')
12
13if __name__ == '__main__':
14    secure_hello()

Usage:

1$ python app.py
2Your name: John
3Password: 
4Repeat for confirmation: 
5Are you sure you want to continue? [y/N]: y
6Hello, John!
7Password accepted

Example 4: Progress Bars and Styling

 1import click
 2import time
 3
 4@click.command()
 5@click.option('--items', default=100, help='Number of items')
 6def process_items(items):
 7    """Process items with progress bar."""
 8    
 9    # Progress bar
10    with click.progressbar(range(items), label='Processing') as bar:
11        for item in bar:
12            time.sleep(0.01)  # Simulate work
13    
14    # Styled output
15    click.secho('Success!', fg='green', bold=True)
16    click.secho('Warning: Some items skipped', fg='yellow')
17    click.secho('Error details', fg='red', err=True)
18    
19    # Formatted output
20    click.echo(click.style('Processed: ', fg='blue') + 
21               click.style(str(items), fg='cyan', bold=True))
22
23if __name__ == '__main__':
24    process_items()

Example 5: Context and Shared State

 1import click
 2
 3@click.group()
 4@click.option('--debug/--no-debug', default=False)
 5@click.pass_context
 6def cli(ctx, debug):
 7    """CLI with shared context."""
 8    ctx.ensure_object(dict)
 9    ctx.obj['DEBUG'] = debug
10
11@cli.command()
12@click.pass_context
13def status(ctx):
14    """Show status."""
15    if ctx.obj['DEBUG']:
16        click.echo('Debug mode is ON')
17    click.echo('Status: OK')
18
19@cli.command()
20@click.option('--config', type=click.File('r'))
21@click.pass_context
22def run(ctx, config):
23    """Run with configuration."""
24    if ctx.obj['DEBUG']:
25        click.echo('Debug: Loading config...')
26    
27    if config:
28        content = config.read()
29        click.echo(f'Config loaded: {len(content)} bytes')
30
31if __name__ == '__main__':
32    cli(obj={})

Example 6: Custom Types and Validation

 1import click
 2
 3class EmailType(click.ParamType):
 4    name = "email"
 5    
 6    def convert(self, value, param, ctx):
 7        if '@' not in value:
 8            self.fail(f'{value} is not a valid email', param, ctx)
 9        return value
10
11EMAIL = EmailType()
12
13@click.command()
14@click.option('--email', type=EMAIL, required=True)
15@click.option('--age', type=click.IntRange(0, 120), required=True)
16@click.option('--score', type=click.FloatRange(0.0, 100.0))
17def register(email, age, score):
18    """Register user with validation."""
19    click.echo(f'Email: {email}')
20    click.echo(f'Age: {age}')
21    if score:
22        click.echo(f'Score: {score}')
23
24if __name__ == '__main__':
25    register()

Example 7: File Handling

 1import click
 2
 3@click.command()
 4@click.argument('input', type=click.File('r'))
 5@click.argument('output', type=click.File('w'))
 6@click.option('--uppercase', is_flag=True)
 7def convert(input, output, uppercase):
 8    """Convert INPUT file to OUTPUT file."""
 9    content = input.read()
10    
11    if uppercase:
12        content = content.upper()
13    
14    output.write(content)
15    click.echo(f'Converted {input.name} -> {output.name}')
16
17if __name__ == '__main__':
18    convert()

Usage:

1$ python app.py input.txt output.txt --uppercase
2Converted input.txt -> output.txt
3
4# Can use stdin/stdout
5$ echo "hello" | python app.py - -
6HELLO

Common Patterns

Environment Variables

1@click.command()
2@click.option('--api-key', envvar='API_KEY', required=True)
3def api_call(api_key):
4    """Call API with key from env or flag."""
5    click.echo(f'Using API key: {api_key[:4]}...')

Multiple Values

1@click.command()
2@click.option('--tag', multiple=True)
3def tag_items(tag):
4    """Add multiple tags."""
5    for t in tag:
6        click.echo(f'Tag: {t}')
7
8# Usage: python app.py --tag python --tag cli --tag tool

Callbacks

1def validate_port(ctx, param, value):
2    if value < 1024:
3        raise click.BadParameter('Port must be >= 1024')
4    return value
5
6@click.command()
7@click.option('--port', callback=validate_port, type=int)
8def serve(port):
9    click.echo(f'Port: {port}')

Notes

  • Arguments are required by default, options are optional
  • Use @click.pass_context to share state between commands
  • click.echo() is better than print() (handles encoding)
  • Progress bars work automatically with iterables
  • Click handles --help automatically

Gotchas/Warnings

  • ⚠️ Arguments vs Options: Arguments are positional, options have flags
  • ⚠️ Order matters: Decorators are applied bottom-to-top
  • ⚠️ File objects: Click handles opening/closing automatically
  • ⚠️ Context: Use @click.pass_context to access shared state
comments powered by Disqus