Code of the Day
IntermediateClick and Subcommands

Groups in practice

Build a Click group with two subcommands that share config through the context object.

UtilitiesIntermediate10 min read
By the end of this lesson you will be able to:
  • Create a click.group with two subcommands
  • Pass shared configuration through the Click context object
  • Invoke subcommands via CliRunner for verification

The cleanest way to understand groups is to build one. This lesson constructs a small notes tool with two subcommands — add and list — that share a storage dict through the Click context.

The structure

notes
  add  TEXT   -- append a note
  list        -- show all notes

The shared state is a list of notes. In a real tool it might be a database connection or a loaded config file. Here a plain Python list serves the same purpose.

Building the group

The group initialises the shared list and attaches it to ctx.obj:

import click

@click.group()
@click.pass_context
def notes(ctx):
    """A minimal note-taking tool."""
    ctx.ensure_object(dict)
    ctx.obj.setdefault("notes", [])

The setdefault call means subsequent group invocations within the same process (as happens in tests) do not reset the list.

The subcommands

Each subcommand retrieves ctx.obj via @click.pass_obj, which passes ctx.obj directly instead of the full context:

@notes.command()
@click.argument("text")
@click.pass_obj
def add(obj, text):
    """Add a note."""
    obj["notes"].append(text)
    click.echo(f"Added: {text}")

@notes.command()
@click.pass_obj
def list_notes(obj):
    """List all notes."""
    if not obj["notes"]:
        click.echo("No notes yet.")
        return
    for i, note in enumerate(obj["notes"], 1):
        click.echo(f"{i}. {note}")

Note the function is named list_notes to avoid shadowing Python's built-in list. Click uses the function name as the subcommand name by default; you can override it with @notes.command(name="list").

Try it

Python — editable, runs in your browser

@click.pass_obj is shorthand for @click.pass_context followed by ctx.obj access. Use @click.pass_obj when the subcommand only needs the shared data; use @click.pass_context when it also needs context metadata like ctx.invoked_subcommand or ctx.parent.

Where to go next

Next: configuration files — the ~/.config/tool/config.toml pattern, Click's auto_envvar_prefix, and the correct precedence order for settings.

Finished reading? Mark it complete to track your progress.

On this page