Skip to content

Migrating from Click

When this path fits

Use this path when the application already has a Click group, commands, and tests that should remain intact. base-cli is a lifecycle layer around the tree; it is not a replacement for Click decorators or parameter parsing.

Incremental change

Before, the application usually owns invocation and logging directly:

import click


@click.group()
def cli() -> None:
    """Example command tree."""


@cli.command()
def status() -> None:
    click.echo("ready")


if __name__ == "__main__":
    cli()

After, keep the decorators and callbacks, and attach the tree at the process boundary:

import base_cli
import click


@click.group()
def cli() -> None:
    """Example command tree."""


@cli.command()
def status() -> None:
    context = base_cli.get_current_context()
    context.log.info("status requested")
    click.echo("ready")


command = base_cli.attach(cli, sensitive_parameters=())

if __name__ == "__main__":
    raise SystemExit(base_cli.run_app(command))

Click still owns the group, options, parameters, help, completion, and Click exceptions. base-cli adds the context, lifecycle options, structured logging, redaction, runtime paths, cleanup, history hooks, and outcome handling. Put workspace/configuration policy in a consumer CliProfile; do not hard-code product assumptions in the framework.

Verification and rollback

  • [ ] Compare --help, option defaults, exit codes, and stdout with the inventory from the old entry point.
  • [ ] Add JSON/NDJSON fixtures if automation consumes command output.
  • [ ] Exercise --debug, --quiet, --keep-temp, and configured --json.
  • [ ] Run the installed-wheel smoke test on every supported platform.
  • [ ] Keep the old console-script entry point and last known-good wheel until the pilot passes; reverting the entry point is the rollback.

See output-contracts.md, json-contracts.md, and adopter-readiness.md for the operational details.