From 1620afc72f91bce42693872caac96ec7cf8d98f9 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Fri, 28 Aug 2026 12:27:16 +0530 Subject: [PATCH] docs: add framework migration guides --- CHANGELOG.md | 2 + docs/migration-argparse.md | 70 +++++++++++++++++++++++++++++++++ docs/migration-cement.md | 80 ++++++++++++++++++++++++++++++++++++++ docs/migration-click.md | 75 +++++++++++++++++++++++++++++++++++ docs/migration-typer.md | 72 ++++++++++++++++++++++++++++++++++ docs/migrations.md | 46 ++++++++++++++++++++-- mkdocs.yml | 4 ++ scripts/validate_docs.py | 4 ++ 8 files changed, 350 insertions(+), 3 deletions(-) create mode 100644 docs/migration-argparse.md create mode 100644 docs/migration-cement.md create mode 100644 docs/migration-click.md create mode 100644 docs/migration-typer.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d06f446..bec2d7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ and versions are tracked in the repo-root `VERSION` file. ### Added +- Add framework-specific migration guides for Click, Typer, Cement, and + `argparse`, with rollout and rollback checklists. - Add golden success, error, inspection, log, NDJSON, and command-protocol fixtures with Python and Node.js validators for cross-language consumers. - Publish versioned JSON Schema artifacts for output, error, inspection, log, diff --git a/docs/migration-argparse.md b/docs/migration-argparse.md new file mode 100644 index 0000000..d559ce9 --- /dev/null +++ b/docs/migration-argparse.md @@ -0,0 +1,70 @@ +# Migrating from argparse + +## When this path fits + +Use this path when a standard-library `argparse` CLI needs consistent runtime +state, diagnostics, cleanup, and machine-readable output. Keep the parser +stable first; replacing parsing and lifecycle in the same change makes +behavioral regressions difficult to diagnose. + +## Incremental change + +An `argparse` entry point commonly combines parsing and application work: + +```python +import argparse + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", action="store_true") + args = parser.parse_args(argv) + print("ready (verbose)" if args.verbose else "ready") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) +``` + +The lowest-risk `base-cli` adoption is to move the command tree to Click while +preserving the option and callback contract: + +```python +import base_cli +import click + + +@click.command(name="example") +@click.option("--verbose", is_flag=True) +def cli(verbose: bool) -> None: + base_cli.get_current_context().log.info("status requested") + click.echo("ready (verbose)" if verbose else "ready") + + +command = base_cli.attach(cli) + +if __name__ == "__main__": + raise SystemExit(base_cli.run_app(command)) +``` + +`argparse`/Click remains responsible for parsing, help, completion, parameter +types, and usage errors. `base-cli` owns lifecycle options and hooks, context, +structured logging, redaction, runtime paths, cleanup, history, and output +contracts. If retaining `argparse` is a hard requirement, integrate the +consumer's parser at its own boundary and adopt the `base-cli` contracts +incrementally; `attach()` expects a Click command. + +## Verification and rollback + +- [ ] Snapshot option spelling, defaults, help text, exit codes, and parser + errors before changing the command tree. +- [ ] Test both human output and JSON/NDJSON fixtures, with diagnostics on + stderr and command output on stdout. +- [ ] Run the installed-wheel and supported-platform checks before removing + the old parser entry point. +- [ ] Keep the `argparse` entry point and previous wheel available for rollback + until every consumer has migrated. + +See [`output-contracts.md`](output-contracts.md) and +[`json-contracts.md`](json-contracts.md) for the stable output boundary. diff --git a/docs/migration-cement.md b/docs/migration-cement.md new file mode 100644 index 0000000..3377316 --- /dev/null +++ b/docs/migration-cement.md @@ -0,0 +1,80 @@ +# Migrating from Cement + +## When this path fits + +Use this path when Cement provides an established controller tree, hooks, and +configuration system, but the team wants the shared lifecycle and contracts of +`base-cli`. Cement can remain in place during an evaluation; migrate one +controller at a time rather than rewriting the whole application. + +## Incremental change + +A small Cement application commonly owns both parsing and process lifecycle: + +```python +from cement import App, Controller, ex + + +class BaseController(Controller): + class Meta: + label = "base" + + @ex(help="report status") + def status(self) -> None: + print("ready") + + +class Example(App): + class Meta: + label = "example" + handlers = [BaseController] + + +with Example() as app: + app.run() +``` + +The incremental target is a Click command with the same user-visible command +and options: + +```python +import base_cli +import click + + +@click.group(name="example") +def cli() -> None: + """Report status.""" + + +@cli.command() +def status() -> None: + base_cli.get_current_context().log.info("status requested") + click.echo("ready") + + +command = base_cli.attach(cli) + +if __name__ == "__main__": + raise SystemExit(base_cli.run_app(command)) +``` + +The parser-owned boundary is the Cement/Click command tree, parameters, help, +completion, and parser errors. Cement hooks and extensions need an explicit +consumer-owned replacement or an adapter; `base-cli` does not emulate Cement's +plugin or configuration conventions. Move lifecycle concerns—logging, +redaction, runtime paths, cleanup, history, and versioned output—into the +`base-cli` boundary and keep domain services in the consumer. + +## Verification and rollback + +- [ ] Inventory Cement hooks, extensions, config precedence, and controller + exit behavior before moving each command. +- [ ] Run old and new commands with identical arguments and compare stdout, + stderr, exit codes, and machine output fixtures. +- [ ] Pilot a single controller and retain the Cement entry point as the + rollback until diagnostics and cleanup are equivalent. +- [ ] Remove Cement only after all required hooks have consumer-owned tests. + +See [`migrations.md`](migrations.md) for the common contract and rollback +checklist. diff --git a/docs/migration-click.md b/docs/migration-click.md new file mode 100644 index 0000000..cbbdc15 --- /dev/null +++ b/docs/migration-click.md @@ -0,0 +1,75 @@ +# 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: + +```python +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: + +```python +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`](output-contracts.md), +[`json-contracts.md`](json-contracts.md), and +[`adopter-readiness.md`](adopter-readiness.md) for the operational details. diff --git a/docs/migration-typer.md b/docs/migration-typer.md new file mode 100644 index 0000000..db1d883 --- /dev/null +++ b/docs/migration-typer.md @@ -0,0 +1,72 @@ +# Migrating from Typer + +## When this path fits + +Use this path when typed Typer commands and dependency injection are already +valuable. Install the optional adapter so Typer remains optional for Click-only +consumers: + +```bash +python -m pip install 'base-cli[typer]' +``` + +## Incremental change + +Before, Typer owns the process entry point: + +```python +import typer + +app = typer.Typer() + + +@app.command() +def status(verbose: bool = typer.Option(False, "--verbose")) -> None: + typer.echo("ready" if not verbose else "ready (verbose)") + + +if __name__ == "__main__": + app() +``` + +After, retain the Typer declarations and attach the materialized command: + +```python +import base_cli +import typer + +app = typer.Typer() + + +@app.command() +def status(verbose: bool = typer.Option(False, "--verbose")) -> None: + context = base_cli.get_current_context() + context.log.info("status requested") + typer.echo("ready" if not verbose else "ready (verbose)") + + +command = base_cli.attach_typer(app, name="example") + +if __name__ == "__main__": + raise SystemExit(base_cli.run_app(command)) +``` + +Typer remains responsible for decorators, type-driven parameters, nested apps, +help, completion, dependency injection, and Typer/Click exceptions. The +adapter returns Typer's generated Click command; `base-cli` owns the lifecycle, +context, logging, redaction, runtime state, cleanup, history hooks, and output +contracts. For custom configuration use `base_cli.attach_typer(app, +app=base_cli.App(...))`. + +## Verification and rollback + +- [ ] Test the supported Typer and Python matrix, including the vendored Click + boundary used by newer Typer releases. +- [ ] Compare typed defaults, callback injection, help, completion, and exit + codes with the original app. +- [ ] Add contract fixtures and exercise debug, quiet, JSON, and retained + diagnostics. +- [ ] Keep the old Typer entry point available until the wheel-first pilot is + green; restore it to roll back without rewriting command code. + +See [`typer-adapter.md`](typer-adapter.md) for adapter details. diff --git a/docs/migrations.md b/docs/migrations.md index c3d8f6d..db40e46 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -1,8 +1,48 @@ # Migration guide -This page collects the format for changes that affect the public `base-cli` -contract. The compatibility rules and deprecation timeline are defined in -[`api-stability.md`](api-stability.md). +This page is the starting point for adopting `base-cli` in an existing Python +CLI. It collects framework-specific recipes as well as the format for changes +that affect the public `base-cli` contract. The compatibility rules and +deprecation timeline are defined in [`api-stability.md`](api-stability.md). + +## Choose a migration path + +- [Click](migration-click.md) — keep an existing Click command tree and add a + shared lifecycle boundary. +- [Typer](migration-typer.md) — keep typed Typer declarations and attach the + generated command to the lifecycle boundary. +- [Cement](migration-cement.md) — keep Cement while evaluating the boundary, + then move command definitions incrementally to Click plus `base-cli`. +- [argparse](migration-argparse.md) — keep parser behavior stable while + replacing application-owned lifecycle code one command at a time. + +The [framework choice guide](framework-choice.md) explains the boundary and +the [adopter readiness guide](adopter-readiness.md) is the production handoff +checklist. These recipes assume a pinned `base-cli` release and a wheel-first +smoke test before changing a user's command behavior. + +## Common rollout and rollback checklist + +1. Inventory command names, options, exit codes, configuration precedence, + output consumed by automation, and log/diagnostic locations. +2. Add `base-cli` to the lock file and preserve the existing parser and + callback tests. Start with one low-risk command. +3. Attach the existing tree (or add a small `App`) and move policy into a + consumer-owned `CliProfile`. Mark secrets as sensitive. +4. Add success, usage-error, and unexpected-error contract fixtures. Keep + stdout reserved for command output and diagnostics on stderr. +5. Run the full platform/dependency matrix, then compare old and new output + and retained run metadata in a pilot environment. +6. Keep the previous wheel, configuration schema, and output contract + available for the documented compatibility window. If the pilot fails, + restore the previous entry point and wheel, and retain the evidence for a + follow-up issue. + +`base-cli` owns invocation lifecycle, context, logging, redaction, runtime +state, cleanup, history hooks, and versioned output contracts. The parser +(Click, Typer, Cement, or `argparse`) remains responsible for command trees, +parameters, help, completion, and parser-specific errors. Product callbacks, +services, configuration policy, and domain schemas remain consumer-owned. ## Migrating a deprecated API diff --git a/mkdocs.yml b/mkdocs.yml index 558c112..f5ef991 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,6 +41,10 @@ nav: - API stability: api-stability.md - Dependency support: dependency-support.md - Migration guide: migrations.md + - Click migration: migration-click.md + - Typer migration: migration-typer.md + - Cement migration: migration-cement.md + - argparse migration: migration-argparse.md - Output contracts: output-contracts.md - JSON contracts: json-contracts.md - JSON Schema artifacts: schemas.md diff --git a/scripts/validate_docs.py b/scripts/validate_docs.py index 398b653..f8b4afb 100644 --- a/scripts/validate_docs.py +++ b/scripts/validate_docs.py @@ -26,6 +26,10 @@ "integrations.md", "json-contracts.md", "local-config.md", + "migration-argparse.md", + "migration-cement.md", + "migration-click.md", + "migration-typer.md", "migrations.md", "output-contracts.md", "performance.md",