click - Documentation

What is Click?

Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It’s designed to make creating complex command-line tools easy and enjoyable, handling argument parsing, help page generation, and more, automatically. Click takes care of the tedious aspects of command-line interface development, allowing you to focus on the functionality of your application.

Why use Click?

Installation

Click is easily installed using pip:

pip install click

This will install the Click package and its dependencies. Ensure you have pip installed; if not, consult the instructions for your Python distribution (e.g., Python’s official website).

Basic Example

Here’s a simple example demonstrating a basic Click application:

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.argument('name')
def hello(count, name):
    """Simple program that greets NAME for a given COUNT number of times."""
    for x in range(count):
        click.echo(f"Hello {name}!")

if __name__ == '__main__':
    hello()

This code defines a command hello that takes a name as an argument and an optional --count option. Running this script from the command line (e.g., python your_script.py John --count 3) will print “Hello John!” three times. The @click.command() decorator transforms the hello function into a Click command, and @click.option() and @click.argument() decorators define the command’s options and arguments respectively. The docstring within the hello function becomes the help text for the command. You can access this help text using python your_script.py --help.

Core Concepts

Commands and Groups

Click’s fundamental building blocks are commands and groups. A command represents a single action your CLI can perform. A group is a container for multiple commands, allowing you to organize your CLI into a hierarchical structure.

A command is defined by a function decorated with @click.command(). This function’s parameters define the command’s options and arguments. A group is created using @click.group(). Groups can contain other groups and commands, forming a tree-like structure. This enables creating complex CLIs with subcommands.

For example:

import click

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

@cli.command()
def hello():
    click.echo("Hello, world!")

@cli.command()
@click.argument('name')
def greet(name):
    click.echo(f"Hello, {name}!")

if __name__ == '__main__':
    cli()

This defines a group cli containing two commands: hello and greet. Running python your_script.py hello executes the hello command, and python your_script.py greet John executes greet with the argument “John”.

Options and Arguments

Options and arguments are how users provide input to your commands. Options are typically specified using flags (e.g., --verbose, -v), while arguments are positional parameters (e.g., the filename in copy file1.txt file2.txt).

Example demonstrating both:

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.argument('name')
def greet(count, name):
    for x in range(count):
        click.echo(f"Hello, {name}!")

if __name__ == '__main__':
    greet()

Here, --count is an option, and name is an argument.

Contexts

A Click context is an object that holds information about the current command invocation. This includes the command being executed, its options and arguments, and the parent group (if any). Contexts are crucial for passing data between commands and for accessing Click’s features.

Contexts are automatically created and managed by Click. You can access the context using the ctx parameter in your command functions:

import click

@click.command()
@click.option('--verbose/--no-verbose', default=False)
def my_command(verbose, ctx):
    ctx.obj = {'verbose': verbose} # Store data in the context object
    click.echo(f"Verbose mode is {'on' if verbose else 'off'}")

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

cli.add_command(my_command)

if __name__ == '__main__':
    cli()

In this example, we store a verbose setting in the context object ctx.obj. This makes the setting available to other commands within the same invocation.

Callbacks

Callbacks are functions that are executed at specific points during the command execution lifecycle. Click provides several callback mechanisms to enhance your CLI’s functionality. Common uses include:

Example using @click.before_invoke:

import click

@click.command()
@click.before_invoke(lambda ctx: print("Before invoke"))
@click.after_invoke(lambda ctx: print("After invoke"))
def my_command():
    click.echo("Hello from my command")

if __name__ == '__main__':
    my_command()

This will print “Before invoke”, then “Hello from my command”, and finally “After invoke”.

These callbacks provide hooks to customize the command execution flow in a controlled and reusable way.

Defining Commands

The @click.command decorator

The @click.command() decorator is the cornerstone of creating Click commands. It transforms a Python function into a callable command-line interface element. The function’s parameters define the command’s options and arguments. The function’s docstring becomes the help text for the command.

import click

@click.command()
def hello():
    """Prints a greeting."""
    click.echo("Hello, world!")

if __name__ == '__main__':
    hello()

This simple example defines a command named hello that prints “Hello, world!”. The name of the command defaults to the function name (in this case hello), but can be overridden using the name parameter in @click.command().

Defining options with @click.option

The @click.option() decorator adds options to your commands. Options are keyword arguments passed to your command, usually prefixed with a hyphen (- for short options) or double hyphen (-- for long options).

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
def hello(count):
    """Prints a greeting COUNT times."""
    for x in range(count):
        click.echo("Hello, world!")

if __name__ == '__main__':
    hello()

This adds a --count option with a default value of 1. The help parameter provides a description that appears in the command’s help text. The count parameter in the hello function receives the option’s value. You can also specify short options using the short parameter (e.g., @click.option('-c', '--count', ...)). Numerous other parameters exist for fine-grained control over options (type checking, required flags, etc.).

Defining arguments with @click.argument

The @click.argument() decorator adds positional arguments to your commands. Positional arguments are required parameters passed to your command in the order they are defined.

import click

@click.command()
@click.argument('name')
def greet(name):
    """Greets NAME."""
    click.echo(f"Hello, {name}!")

if __name__ == '__main__':
    greet()

This adds a required positional argument name. The name parameter in the greet function receives the argument’s value. The required parameter in @click.argument() can be used to control whether an argument is required. You can also specify argument types for validation.

Command parameters

Parameters in your command function receive values from options and arguments. The order of the parameters is crucial - arguments must appear before options. The type of parameter usually is inferred but can be explicitly specified for better validation and error reporting.

Command help text

The docstring of your command function is used to generate help text. This text should concisely describe what your command does and any important parameters.

import click

@click.command()
def my_command():
    """This is the help text for my_command.  It should describe what the command does."""
    pass

if __name__ == '__main__':
    my_command()

Running python your_script.py my_command --help will display the docstring as help information.

Default values

Options and arguments can have default values. If a user doesn’t provide a value for an option or argument with a default value, Click will use the default instead.

import click

@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.argument('name', default='World')
def greet(count, name):
    for x in range(count):
        click.echo(f"Hello, {name}!")

if __name__ == '__main__':
    greet()

Here, --count defaults to 1, and name defaults to “World”. If called without arguments, python your_script.py will print “Hello, World!” once. Using python your_script.py --count 3 John will print “Hello, John!” three times.

Working with Options

Option types

Click provides built-in support for various option types, allowing you to specify the expected data type and perform basic validation. This improves user experience and prevents common errors. The type parameter in @click.option() is used to specify the type. Common types include int, float, bool, str, Choice, File, and Path.

import click

@click.command()
@click.option('--count', type=int, default=1, help='Number of greetings.')
@click.option('--value', type=float, help='A floating-point value.')
@click.option('--filename', type=click.File('r'), help='A filename to read from.')
@click.option('--choice', type=click.Choice(['a', 'b', 'c']), help='Choose from a, b, or c.')
@click.option('--path', type=click.Path(exists=True), help='Path to an existing file or directory.')
def my_command(count, value, filename, choice, path):
    # ... your command logic ...
    click.echo(f"Count: {count}, Value: {value}, Choice: {choice}, Path: {path}")
    if filename:
        click.echo(filename.read()) # Read from the file object

if __name__ == '__main__':
    my_command()

This example showcases different option types: int, float, click.File, click.Choice, and click.Path. click.File('r') opens the file in read mode, and click.Path(exists=True) ensures that the path exists. Improper input will result in user-friendly error messages.

Flag options

Flag options are boolean options that represent a simple on/off switch. They are typically specified with a single flag, such as --verbose or -v. Click handles the boolean logic automatically.

import click

@click.command()
@click.option('--verbose', is_flag=True, help='Enable verbose output.')
def my_command(verbose):
    if verbose:
        click.echo("Verbose output enabled.")
    else:
        click.echo("Verbose output disabled.")

if __name__ == '__main__':
    my_command()

The is_flag=True parameter indicates a boolean option; no value is required.

Count options

Count options are similar to flag options but keep track of how many times the option is specified. This is useful for increasing verbosity levels, for example.

import click

@click.command()
@click.option('--verbose', count=True, help='Increase verbosity level.')
def my_command(verbose):
    if verbose == 1:
        click.echo("Verbose level 1")
    elif verbose > 1:
        click.echo(f"Verbose level {verbose}")
    else:
        click.echo("Normal output")

if __name__ == '__main__':
    my_command()

Each occurrence of --verbose increments the verbose parameter.

Multiple options

Click allows you to specify multiple values for a single option. The multiple=True parameter enables this functionality; the option’s value will be a list.

import click

@click.command()
@click.option('--files', multiple=True, type=click.Path(exists=True), help='List of files.')
def my_command(files):
    for file in files:
        click.echo(f"Processing file: {file}")

if __name__ == '__main__':
    my_command()

The user can specify --files file1.txt --files file2.txt.

Hidden options

Options can be hidden from the help output using the hidden=True parameter. This is useful for options intended for internal use or advanced users.

import click

@click.command()
@click.option('--hidden-option', hidden=True, help='This option is hidden.')
def my_command(hidden_option):
    # ...
    pass

if __name__ == '__main__':
    my_command()

The --hidden-option won’t be visible when running my_command --help.

Environmental variables

Options can be set using environment variables. The envvar parameter specifies the name of the environment variable.

import click
import os

@click.command()
@click.option('--config', envvar='MY_CONFIG', help='Configuration file path.')
def my_command(config):
    click.echo(f"Config file: {config}")

if __name__ == '__main__':
    my_command()

If the MY_CONFIG environment variable is set, its value will be used for the --config option.

Option validation

You can perform custom validation on option values using the callback parameter in @click.option.

import click

def validate_port(ctx, param, value):
    if not 1024 <= value <= 65535:
        raise click.BadParameter('Port must be between 1024 and 65535.')
    return value

@click.command()
@click.option('--port', type=int, callback=validate_port, help='Server port.')
def my_command(port):
    click.echo(f"Using port: {port}")

if __name__ == '__main__':
    my_command()

The validate_port function checks if the port number is within the valid range. click.BadParameter raises a user-friendly error message if validation fails.

Working with Arguments

Positional arguments

Positional arguments are parameters that are provided to a Click command based on their position in the command line. They are defined using the @click.argument() decorator. Unlike options, positional arguments are not prefixed with hyphens or double hyphens.

import click

@click.command()
@click.argument('filename')
def process_file(filename):
    click.echo(f"Processing file: {filename}")

if __name__ == '__main__':
    process_file()

In this example, filename is a positional argument. To run this command, you would provide the filename as an argument: python your_script.py myfile.txt.

Required vs. optional arguments

By default, arguments defined with @click.argument() are required. To make an argument optional, set the required parameter to False. If an optional argument isn’t provided, its value will be None.

import click

@click.command()
@click.argument('filename', required=False)
def process_file(filename):
    if filename:
        click.echo(f"Processing file: {filename}")
    else:
        click.echo("No file specified.")

if __name__ == '__main__':
    process_file()

Now, python your_script.py will execute without error, printing “No file specified.”

Argument type conversion

Like options, arguments can have their types specified using the type parameter in @click.argument(). This allows for automatic type conversion and validation.

import click

@click.command()
@click.argument('number', type=int)
def show_number(number):
    click.echo(f"The number is: {number}")

if __name__ == '__main__':
    show_number()

Here, the number argument is converted to an integer. Providing a non-integer value will result in a user-friendly error message. Click supports various built-in types (e.g., int, float, str, Path, File) and custom type converters.

Argument validation

Custom validation for arguments can be implemented using the callback parameter in @click.argument(), similar to options. The callback function receives the context, parameter, and value as arguments. It should return the validated value or raise click.BadParameter if validation fails.

import click

def validate_filepath(ctx, param, value):
    if not value.endswith(".txt"):
        raise click.BadParameter("File must have a .txt extension.")
    return value

@click.command()
@click.argument('filepath', callback=validate_filepath)
def process_file(filepath):
    click.echo(f"Processing file: {filepath}")

if __name__ == '__main__':
    process_file()

This example validates that the filepath ends with “.txt”.

Multiple arguments

Click allows you to accept multiple arguments of the same type using the nargs parameter in @click.argument(). If nargs is set to -1, it will consume all remaining arguments.

import click

@click.command()
@click.argument('filenames', nargs=-1)
def process_files(filenames):
    for filename in filenames:
        click.echo(f"Processing file: {filename}")

if __name__ == '__main__':
    process_files()

Running python your_script.py file1.txt file2.txt file3.txt will process all three files. Note that if nargs is a positive integer, exactly that number of arguments must be provided. If nargs is set to a number using a range (e.g. nargs=2), then you will have to provide exactly that many arguments. If you need to accept an arbitrary amount of arguments (i.e. zero or more) and treat them as a list, set nargs=-1.

Command Groups

The @click.group decorator

The @click.group() decorator is used to create command groups, which are essentially containers for organizing multiple related commands. This helps create a structured and more manageable CLI. A command group itself isn’t directly executable; it serves to organize subcommands.

import click

@click.group()
def cli():
    """A simple CLI with subcommands."""
    pass

@cli.command()
def hello():
    click.echo("Hello, world!")

if __name__ == '__main__':
    cli()

This creates a group named cli (the name defaults to the function name) containing the subcommand hello. To run the hello command, you would use python your_script.py hello.

Nesting command groups

Command groups can be nested to create a hierarchical structure, reflecting the logical organization of your CLI.

import click

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

@cli.group()
def user():
    """Manage users."""
    pass

@user.command()
def add():
    """Add a new user."""
    click.echo("Adding a user...")

@user.command()
def remove():
    """Remove a user."""
    click.echo("Removing a user...")

@cli.command()
def project():
    """Manage projects."""
    click.echo("Managing projects...")


if __name__ == '__main__':
    cli()

This nests the user group within the cli group. You’d run subcommands like python your_script.py user add or python your_script.py project.

Managing subcommands

Subcommands are added to a group by decorating functions with @group.command(), where group is the group object returned by @click.group(). You can also use group.add_command(command) to add a command created elsewhere.

import click

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

def my_command():
  click.echo("Hello from my command")

cli.add_command(my_command, name="mycmd") # name is an optional parameter

if __name__ == '__main__':
  cli()

This adds my_command to the cli group under the name “mycmd”. It can then be invoked as python your_script.py mycmd.

Group help text

Like commands, the docstring of a group function generates help text for the group. This text should describe the group’s purpose and list its subcommands.

import click

@click.group()
def cli():
    """This is a top-level CLI group.  It manages users and projects."""
    pass

# ... subcommands ...

if __name__ == '__main__':
    cli()

Running python your_script.py --help will display the group’s help text. The help text also shows the available subcommands. You can also add context to your help texts through the context_settings parameter. For example, to add a custom epilogue to your help text you can write something like this:

import click

@click.group(context_settings=dict(help_option_names=['-h', '--help'], epilog="This is some extra information"))
def cli():
    pass

# ... subcommands ...

if __name__ == '__main__':
    cli()

The help_option_names parameter helps define the help options that users can use.

Contexts and Callbacks

Understanding contexts

A Click context object is a container holding information relevant to the current command invocation. This includes:

The context is automatically created and managed by Click. It’s crucial for accessing command-line parameters, passing data between commands, and executing callbacks. You can access the context object within your command or group functions by adding a ctx parameter.

Using callbacks

Callbacks are functions that are invoked at specific points during command execution. They allow you to perform actions before or after a command runs or to modify the behavior of options and arguments. Common callback decorators include:

Example using @click.before_invoke and @click.after_invoke:

import click

@click.command()
@click.before_invoke(lambda ctx: print("Setting up..."))
@click.after_invoke(lambda ctx: print("Cleaning up..."))
def my_command():
    click.echo("Command executed.")

if __name__ == '__main__':
    my_command()

Context-specific options and arguments

Callbacks can access and modify the context, allowing for context-sensitive behavior. For instance, you can use callbacks to validate options based on other options or arguments in the context.

import click

def validate_option(ctx, param, value):
    if ctx.params['verbose'] and value < 10:
        raise click.BadParameter("Value must be at least 10 in verbose mode.")
    return value

@click.command()
@click.option('--verbose', is_flag=True)
@click.option('--value', type=int, callback=validate_option)
def my_command(verbose, value):
    click.echo(f"Verbose: {verbose}, Value: {value}")

if __name__ == '__main__':
    my_command()

Here, the validate_option callback checks if value meets a condition based on the verbose flag.

Passing data between commands

The ctx.obj attribute of the context object provides a mechanism for sharing data between commands, particularly within the same group. You typically initialize ctx.obj in a group’s invocation function and then access it in subcommands.

import click

@click.group()
def cli():
    ctx.obj = {}

@cli.command()
@click.argument('name')
def set_name(ctx, name):
    ctx.obj['name'] = name
    click.echo(f"Name set to: {name}")

@cli.command()
def greet():
    name = ctx.obj.get('name', 'World')
    click.echo(f"Hello, {name}!")

if __name__ == '__main__':
    cli()

First, set_name stores the name in ctx.obj. Then, greet retrieves the name from ctx.obj to use in its greeting. If set_name hasn’t been run, it defaults to “World”. This allows you to share data across multiple commands without using global variables, improving code organization and maintainability.

Advanced Usage

Working with files

Click provides convenient ways to work with files, primarily through the click.File type. This type handles file opening and closing automatically, ensuring resources are properly managed, even in case of errors. You can specify the file mode ('r' for reading, 'w' for writing, 'a' for appending, etc.) when defining the option or argument.

import click

@click.command()
@click.argument('filename', type=click.File('r'))
def process_file(filename):
    for line in filename:
        click.echo(line.strip())

if __name__ == '__main__':
    process_file()

This reads and prints each line of the specified file. If the file doesn’t exist or cannot be opened, Click will handle the error gracefully, providing a user-friendly message.

Handling errors

Click provides mechanisms for handling exceptions and presenting user-friendly error messages. The click.BadParameter exception is specifically designed for invalid command-line arguments. You can also use standard Python exception handling (try...except) to catch other errors.

import click

@click.command()
@click.argument('filename', type=click.Path(exists=True))
def process_file(filename):
    try:
        with open(filename, 'r') as f:
            # Process the file
            pass
    except FileNotFoundError:
        click.echo(f"Error: File '{filename}' not found.", err=True)
        raise click.Abort() # Stop execution

if __name__ == '__main__':
    process_file()

This example demonstrates handling FileNotFoundError; click.Abort() terminates the program cleanly. err=True sends the error message to stderr.

Custom help messages

You can customize the help messages generated by Click using several techniques:

Input/Output redirection

Click doesn’t directly manage input/output redirection (e.g., > and >> in the shell), but you can use Python’s standard sys module to interact with standard input/output streams.

import click
import sys

@click.command()
def my_command():
    for line in sys.stdin:
        click.echo(line.upper())

if __name__ == '__main__':
    my_command()

This command reads from stdin and writes to stdout. The user could redirect input and output using shell commands.

Click with other libraries

Click integrates well with many other Python libraries. For example, you can use it with libraries for:

Testing Click applications

Testing Click applications involves verifying that commands behave as expected with different arguments and options. The click.testing module provides tools for creating test cases. It allows you to run commands with mocked inputs and check their outputs.

Extending Click

Click is extensible. You can create custom types, decorators, and other components to adapt it to your specific needs. This involves creating custom classes inheriting from Click classes or extending existing behavior. You might want to extend Click if you require additional functionalities or wish to enforce custom validation logic not supported by built-in types.

Best Practices and Style Guide

Writing clean and readable Click applications

Writing clean, readable Click applications involves focusing on several key aspects:

Structuring your code

Organize your Click application’s code into logical modules or packages to avoid creating monolithic files. A common approach is to:

  1. Have a main script that defines the top-level group.
  2. Create separate modules or packages for individual commands or groups of related commands.
  3. Use a well-defined structure that separates command logic from supporting functions or utilities.

For example, you might have a cli.py file for the main entry point and separate files (e.g., user_commands.py, project_commands.py) for different command sets.

Choosing appropriate option names

Option names should be clear, concise, and consistent. Favor long option names that clearly describe the option’s purpose over short options. Use the double-hyphen (--) prefix for long option names. For example, --verbose is preferable to -v, especially in more complex CLIs where short options can become ambiguous. Consider using standard option names where applicable (e.g., --help, --version). If the option takes a value, ensure the name clearly indicates the type of value expected.

Consistent error handling

Maintain consistency in how you handle errors throughout your application. Use click.BadParameter for invalid input, provide user-friendly error messages, and handle exceptions gracefully. Consider using a centralized logging system for more comprehensive error tracking and debugging. For critical errors, terminate the program using click.Abort() for a clean exit and provide the user with information on how to proceed.

Testing and documentation

Thoroughly test your Click application to ensure that commands work as expected with various inputs and conditions. Use a testing framework like pytest along with click.testing to write unit tests for individual commands. Provide comprehensive documentation explaining how to use your CLI, including a clear usage guide with examples. Tools like Sphinx can help generate professional-looking documentation from docstrings and other comments in your code. Include examples of how to use your CLI, detailing both common and advanced usage patterns. Make sure your documentation keeps pace with your code updates; a stale documentation is worse than no documentation.