Click: Composable Command Line Interface Toolkit

repository·main·Indexed 12 days ago

https://github.com/pallets/click

Click is a Python package for creating composable command line interfaces (CLIs) with minimal code and sensible defaults. It features arbitrary nesting of commands, automatic help page generation, and lazy loading of subcommands. The toolkit provides decorators for defining commands and options, utilities for terminal I/O (such as echo, prompt, and progressbar), and advanced context management for resource lifecycles via with_resource and call_on_close. Version 8.5.0.dev.

Tokens
50.5K
Snippets
186
Records
231
Agent score
94%

What's inside Click

  1. Overview of Click features

    main

    Click (Command Line Interface Creation Kit) is designed to create composable command line interfaces with minimal code. Key features include:

    • Arbitrary nesting of commands: Easily create complex command hierarchies using groups.
    • Automatic help page generation: Click automatically generates formatted help documentation based on your command and option definitions.
    • Lazy loading: Supports loading subcommands at runtime to improve performance and startup time.
    • Sensible defaults: Provides high configurability while working out of the box with minimal setup.
  2. What is Click

    main

    Click is a Python package designed for creating composable command line interfaces (CLIs) with minimal code. It provides sensible defaults and is highly configurable.

    Key features include:

    • Arbitrary nesting of commands: Easily create complex command hierarchies.
    • Automatic help page generation: Automatically generates documentation for your CLI based on your code.
    • Lazy loading of subcommands: Supports loading subcommands at runtime to improve performance and startup time.
  3. How Group callbacks work

    main

    In Click, a regular command's callback executes when the command runs. However, for a @click.group(), the callback fires whenever any subcommand is invoked. This means an outer group's logic (like setting up a debug mode) runs before the inner command's logic.

    When using groups, ensure you use the group as the decorator for subcommands (e.g., @cli.command() instead of @click.command()) so they are correctly attached to the group's lifecycle.

    @click.group()
    @click.option('--debug/--no-debug', default=False)
    def cli(debug):
        click.echo(f"Debug mode is {'on' if debug else 'off'}")
    
    @cli.command()
    def sync():
        click.echo('Syncing')
  4. Core features and design goals of Click

    main

    Click is designed to create highly composable and nestable command-line interfaces. Unlike many other libraries, it focuses on providing a consistent user experience across different subcommands and tools.

    Key capabilities include:

    • Lazy Composability: Commands can be nested and combined without restrictions.
    • POSIX Compliance: Supports standard Unix/POSIX command-line conventions.
    • Environment Variables: Built-in support for loading parameter values from environment variables.
    • Interactive Prompting: Supports prompting users for custom values.
    • File Handling: Out-of-the-box support for file-related operations.
    • CLI Helpers: Includes utilities for terminal dimensions, ANSI colors, keyboard input, screen clearing, finding config paths, and launching editors.
    • Automatic Dispatching: Click doesn't just parse arguments; it dispatches them to the appropriate code/functions.
    • Invocation Context: Uses a context object that allows subcommands to access and respond to data provided by parent commands.
  5. Understanding Click's design constraints

    main

    Click intentionally limits certain types of customizability (such as the formatting of help pages) to maintain a core promise: multiple Click instances will continue to function as intended when strung together.

    By enforcing certain hardcoded behaviors and standard paradigms, Click ensures:

    • Consistency: A predictable command-line experience across different tools.
    • Reliability: Avoiding 'magical' behaviors like automatic parameter correction, which can break backwards compatibility if new parameters are added later.
    • Predictability: Avoiding the syntactical ambiguities found in other libraries (like optparse) when handling variadic arguments.
  6. Combine short options and use counting

    main

    Click supports POSIX-style short option stacking. For example, -abc is equivalent to -a -b -c if a, b, and c are single-character flags.

    If you want to count how many times an option is provided (common for verbosity levels), set count=True. If the option is not provided, the value is 0.

    @click.command()
    @click.option('-v', '--verbose', count=True)
    def log(verbose):
        click.echo(f"Verbosity: {verbose}")
    
    # Usage:
    # log() -> 0
    # log('-vvv') -> 3
  7. How Click infers option argument names

    main

    If you do not provide a second positional argument to @click.option(), Click attempts to infer the name of the decorated function's argument.

    To ensure correct inference, name your CLI option by taking the function argument name, adding -- to the front, and replacing underscores with dashes. For example, a function argument foo_bar should have an option --foo-bar.

    Inference logic:

    1. If a positional argument is a valid Python identifier (no dashes), it is used.
    2. If multiple arguments are prefixed with --, the first one declared is used.
    3. Otherwise, the first argument prefixed with - is used.

    The name is processed by converting to lowercase, removing leading - or --, and replacing remaining - with _.

    @click.command()
    @click.option('--string-to-echo')  # Inferred as 'string_to_echo'
    def echo(string_to_echo):
        click.echo(string_to_echo)
  8. How to share data between commands using Context

    main

    Commands are isolated by default. To allow a parent command to pass data to a nested subcommand, use the Context object.

    1. Accessing Context: Decorate a command with @click.pass_context to receive the ctx object as the first argument.
    2. Storing Data: Use ctx.obj to store a shared object (typically a dict). Use ctx.ensure_object(dict) in the group callback to initialize it safely.
    3. Accessing Data: Subcommands decorated with @click.pass_context can then read from ctx.obj.
    @click.group()
    @click.option('--debug/--no-debug', default=False)
    @click.pass_context
    def cli(ctx, debug):
        ctx.ensure_object(dict)
        ctx.obj['DEBUG'] = debug
    
    @cli.command()
    @click.pass_context
    def sync(ctx):
        click.echo(f"Debug is {'on' if ctx.obj['DEBUG'] else 'off'}")
    
    if __name__ == '__main__':
        cli(obj={})
  9. Understand the interaction between `default` and `flag_value`

    main

    The behavior of the default parameter depends on whether the flag is boolean or non-boolean:

    Non-boolean flags (e.g., flag_value='upper')

    There is a special shorthand: if you set default=True, Click interprets this as "activate this flag by default" and passes the actual flag_value to your function instead of the Python True object.

    Recommendation: To avoid confusion, always use the explicit value instead of the shorthand.

    • Avoid: @click.option('--upper', flag_value='upper', default=True)
    • Prefer: @click.option('--upper', flag_value='upper', default='upper')

    Boolean flags (e.g., flag_value=True or False)

    There is no shorthand. default=True is passed to your function as the literal Python True object.

  10. How to implement custom parameter types

    main

    To create a custom type, subclass click.ParamType. You must override the convert method to transform the input string into your desired Python object.

    Implementation Details:

    • Generics (v8.4.0+): ParamType is now a generic base class. Parameterize it with the type returned by convert (e.g., click.ParamType[int]) to support type-checking.
    • convert(self, value, param, ctx):
      • value: The input value (usually a string from the CLI, but could be a default value).
      • param: The parameter object.
      • ctx: The current Click context.
    • Handling existing types: Always check if value is already the correct type at the start of convert to support default values that are already instantiated.
    • Error Handling: Use self.fail(message, param, ctx) to raise a validation error if conversion fails.
    • name attribute: An optional string used for documentation purposes.
    import click
    
    class BasedIntParamType(click.ParamType[int]):
        name = "integer"
    
        def convert(self, value, param, ctx) -> int:
            if isinstance(value, int):
                return value
    
            try:
                if value[:2].lower() == "0x":
                    return int(value[2:], 16)
                elif value[:1] == "0":
                    return int(value, 8)
                return int(value, 10)
            except ValueError:
                self.fail(f"{value!r} is not a valid integer", param, ctx)
    
    BASED_INT = BasedIntParamType()
  11. How Click handles exceptions and exit codes

    main

    Click uses exceptions to signal error conditions, primarily incorrect usage. When using Command.main, Click follows a specific error handling lifecycle:

    1. EOFError or KeyboardInterrupt: These are re-raised as Abort.
    2. ClickException: Click calls ClickException.show() to display the error to the user and exits with the value of ClickException.exit_code.
    3. Abort: Click prints Aborted! to stderr and exits with code 1.
    4. Success: If no exceptions occur, the program exits with code 0.

    Note that triggering a help page via --help returns exit code 0. However, if a help page is displayed automatically due to incorrect user input, the program returns exit code 2.

  12. Create feature switch groups with multiple flags

    main

    You can have multiple options target the same parameter name. Click uses arbitration rules to decide which value reaches your function when multiple flags are present.

    Non-boolean groups (Strings, Enums, etc.)

    To make one flag the default when none are provided, use default=True (which resolves to that flag's flag_value) or set the explicit flag_value as the default.

    To implement a three-state pattern (where the function receives None if no flag is passed), set default=None on all options in the group.

    Boolean groups (Enable/Disable)

    For boolean switches, you can use a single flag or a pair. For a pair that defaults to 'on', use the --with-xyz/--without-xyz syntax:

    @click.option('--with-xyz/--without-xyz', 'enable_xyz', default=True)

    Alternatively, you can use multiple flags targeting the same variable:

    @click.option("--without-xyz", "enable_xyz", flag_value=False)
    @click.option("--with-xyz", "enable_xyz", flag_value=True, default=True)