Skip to content

refactor(cli): migrate from Click to Cyclopts + Rich - #1894

Closed
noirbizarre wants to merge 3 commits into
pyinfra-dev:3.xfrom
noirbizarre:refactor/rich-cyclopts
Closed

refactor(cli): migrate from Click to Cyclopts + Rich#1894
noirbizarre wants to merge 3 commits into
pyinfra-dev:3.xfrom
noirbizarre:refactor/rich-cyclopts

Conversation

@noirbizarre

@noirbizarre noirbizarre commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Migrates the pyinfra CLI from Click to Cyclopts for argument parsing and adopts Rich for all human-facing output, while keeping the existing command syntax and behaviour.

Highlights:

  • Cyclopts-based CLI with grouped --help, colourised usage, shell completion, and a machine-readable --json mode (stdout stays pure JSON; all human output goes to stderr).
  • Rich debug-inventory: hosts rendered in a zebra-striped table with data shown as key: value lines (nested values as JSON), type-coloured to match the JSON highlighter.
  • Live hierarchical progress tree during deploys, with per-host/per-operation nodes and informative error handling that attributes failures to the right host and operation.

Output is routed through a small pluggable layer in pyinfra.api.output, decoupled from Rich/Click so the core stays testable. Existing behaviour, exit codes and command syntax are preserved; a few intentional UX changes (usage-error exit code, --version output, confirm-prompt re-prompting) are documented in the commits.

See #1885 for context (includes recordings and screenshots).

@noirbizarre
noirbizarre force-pushed the refactor/rich-cyclopts branch 3 times, most recently from 84923bc to 6b3c080 Compare July 20, 2026 21:05
Replace Click with Cyclopts for argument parsing/help and Rich for all output.

- pyinfra.api.output gains a shared Rich Console; format_text/echo are
  Rich-backed adapters preserving the legacy click.style signature
- pyinfra_cli.console holds the stderr (human) and stdout (JSON) consoles
- cli.py uses a single Cyclopts @app.default preserving the exact CLI syntax
  (inventory + variadic operations, faked subcommand dispatch, exec --
  passthrough, -v counting, --user/--port aliases, PYINFRA_YES)
- exceptions.py drops click.ClickException for Rich-rendered CliException
- CLI test harness swaps click.testing.CliRunner for a Cyclopts-based run_cli
- Shell completion installed via `pyinfra --install-completion` (Cyclopts);
  the old Click completion scripts are removed and docs/CHANGELOG updated

stdout stays byte-for-byte JSON; all human output remains on stderr.
- debug-inventory renders a Rich table with syntax-highlighted, pretty-printed
  host data; --json output is highlighted on a terminal and plain when piped
- de-duplicate host groups and tidy the table layout
- make INVENTORY positional-only and organise options into ordered help groups
- syntax-highlight the CLI help examples
…ling

Replace the flat `--> ` phase logs with a Rich rendering of deploys.

- Drop the `--> ` log prefix; render proposed changes and results as Rich
  trees/tables (file -> operation -> hosts) with per-op success/error/no-change
  counts and failed hosts listed under errored operations
- Hierarchical live tree (TTY, non-JSON, all verbosity levels): phases
  (Loading, Connecting, Preparing, each operation) render as spinner ->
  check/cross rows with a parent progress bar (n/total); verbose detail lines
  (facts, command input/output) nest under the host nodes
- New pyinfra_cli.routing attributes log/echo lines to hosts and records
  per-host errors so nothing interleaves with the live region
- Deferred, aggregated failure prompt: during Prepare, hosts evaluate in
  parallel, so failures are collected and a single prompt (preceded by a red
  "Failed hosts" block with each host's error) is shown after the phase
- Consistent status colours; dim the `@connector/` host-name prefix; restyle
  the bracket-free host log prefix
- New api operation_host_skipped state callback; fire operation_end before
  fail_hosts so the tree finalises before prompting

In piped/JSON modes the live tree is disabled and output stays clean/pure.
@noirbizarre
noirbizarre force-pushed the refactor/rich-cyclopts branch from 6b3c080 to c6eb31b Compare July 20, 2026 23:48
@wowi42 wowi42 added CLI CLI mode specific issues. new feature labels Jul 23, 2026

@wowi42 wowi42 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

This migrates the CLI from Click to Cyclopts for argument parsing and Rich for human-facing output, while keeping the existing command syntax and all CLI flags intact. The new output layer in pyinfra.api.output stays decoupled from Rich/Click so the core remains testable, and the live progress tree during deploys is a nice improvement. All CI passes, including the full end-to-end matrix.

The shell injection surface is clean: user input still flows through the same operation/fact code paths, and Cyclopts handles argument parsing without introducing any new quoting concerns. The _lenient_bool converter for PYINFRA_YES preserves the legacy Click behavior including on/off and empty-string-as-false. The host.print_prefix change from [hostname] to hostname (with color and dimmed connector prefix) is an internal API change, not a public contract, and the e2e tests were updated to match. The operation_end callback reordering (before fail_hosts instead of after) is deliberate so the progress tree can finalize its display before error handling prompts or raises.

One small thing: the _support_command registered as @app.command(name="--support") and the support parameter in cli() both handle --support. The parameter body (if support: print_support_info(); return) looks unreachable since Cyclopts routes --support to the subcommand. It is harmless dead code but worth cleaning up in a follow-up.

@Fizzadar Fizzadar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @noirbizarre - I'm SUPER excited by this PR! But I think there's a few things to address:

  • --serial/--no-wait loose all output under a TTY
  • --diff also drops output unless -v is set with a TTY
  • all host attributed logs from deploy code also dropped with TTY

This is also a significant change, I believe it would be better split into: click -> cyclopts, then Rich output / print and finally the live progress. This would make reviewing much easier as well if you don't mind 🙏

@noirbizarre

noirbizarre commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

Hi @Fizzadar

Thanks for the feedback; it was very useful. Especially the --diff case, which allowed me to start handling reusable prettified types.

So there it is; I split this PR into 3 separate stacked PRs (I'll keep this one as reference):

Those PRs include fixes for the case you both highlighted. I also provided some recordings, including more of those cases.

Important

I tried to have as stable and consistent as possible intermediate state for the PRs.
But, given Click is handling both arg parsing and styling while Cyclopts only do parsing and rich only do styling/presentation, and given live+tree was my solution to handle by task/host isolation, the intermediate states are really created for review purposes and not truly intended to last, especially the rich without live+tree.

Note

Also not that they are all targeting the 3.x branch as I that can't target my intermediate branches which are on my fork. Review on #1902 will only make sense once #1901 is reviewed and merged and refactor/rich rebase on the updated 3.x... I'll keep them in draft until then. I'll update the recordings each time on rebase too as addressing review comments might change them

@Fizzadar

Copy link
Copy Markdown
Member

@noirbizarre awesome, thank you! I think we can certainly merge Cyclopts migration on it's own first, then possibly the others together as you say. Going to focus on that PR first so we can start landing these changes ASAP!

@wowi42

wowi42 commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Closing this PR: the proposal and its details have been moved to the discussion at #1885 (comment) for further design discussion. The branch remains available if we decide to pick this up later.

Thanks @noirbizarre for the thorough work and the detailed write-up.

@wowi42 wowi42 closed this Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLI CLI mode specific issues. new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants