Skip to content

refactor: drop typer, build the CLI directly on click#499

Merged
shcheklein merged 3 commits into
mainfrom
refactor/drop-typer
Jul 9, 2026
Merged

refactor: drop typer, build the CLI directly on click#499
shcheklein merged 3 commits into
mainfrom
refactor/drop-typer

Conversation

@shcheklein

Copy link
Copy Markdown
Member

Follow-up to #497 — implements the mitigation plan from the typer discussion there: remove typer entirely instead of migrating to its vendored-click API. Net: -1 dependency, -1 pin, net -7 lines, and immunity to typer's release churn.

Why dropping typer (not migrating to typer ≥0.26)

gto only used typer's thin core — declaring command params via function signatures. Everything that defines the CLI's actual behavior (sectioned help, command aliases, "Examples:" epilogs, the unified error funnel) was already hand-written click code. So the migration is mechanical: re-express ~10 command signatures as click decorators, delete the typer layer.

What changed

  • Every command's params moved from typer Argument/Option defaults to @click.option/@click.argument decorators. Flags, defaults, help texts, callbacks, exit codes: unchanged.
  • app is now a click.Group (same gto.cli:app entry point).
  • typer.Exit/typer.colors/typer.BadParameter → click equivalents.
  • New GtoArgument(click.Argument) carries argument help (click has no native argument help) and GtoCliMixin.format_options renders an "Arguments" section — so test_commands_args_help still enforces help on every param.
  • GtoGroup.list_commands override keeps definition order (click sorts alphabetically by default).
  • Tests: typer.testing.CliRunnerclick.testing.CliRunner; the get_command_from_info fixture becomes app.commands.values().
  • Dead option definitions from long-removed commands dropped (--rev, --all, --expected, --type, --path, --description, --custom, --table, --commit, --push-commit, --branch, arg_version, arg_stage, option_version, option_to_version, GTOGroupSection).
  • pyproject.toml: typer removed; click>=8.2,<9 (8.2 floor for CliRunner without mix_stderr and kebab-case command naming).

User-visible changes

  • Top-level gto --help output is byte-identical to before.
  • Subcommand --help switches from typer's rich boxes to click's classic plain format (matching the top-level help style), with an added "Arguments" section preserving argument descriptions. Everything else — usage lines, option lists, defaults — carries over.

Verification

  • pre-commit run --all-files green (ruff, mypy 2.2.0, pylint 4.0.6).
  • 165/165 tests pass on Python 3.10, 3.13, 3.14 (fresh venvs; typer confirmed absent).
  • End-to-end CLI exercise in a scratch repo: register, assign (via promote alias), show (table + --json), check-ref, history --plain, deprecate; error paths verified — GTOException → ❌ message + exit 1, usage error → exit 2, -h works on subcommands.
  • Warning count in pytest dropped 27 → 1 (typer's click-compat deprecation warnings gone).

🤖 Generated with Claude Code

typer >=0.26 vendors click and is incompatible with gto's click-based
CLI machinery (see #497). gto only used typer's signature-based param
declarations; all the custom UX (grouped help, aliases, examples,
error funnel) was already hand-written click code. Re-express command
params as click decorators and remove the typer dependency and pin.

- commands, options, defaults, flags, help texts, exit codes: unchanged
- top-level `gto --help` output is byte-identical
- subcommand help switches from typer's rich boxes to click's plain
  format; argument help is preserved via GtoArgument + an "Arguments"
  help section (click has no native argument help)
- GtoGroup.list_commands keeps definition order (click sorts
  alphabetically by default)
- drop dead option definitions left over from removed commands
  (--rev, --all, --expected, --type, --path, --description, --custom,
  --table, --commit, --push-commit, --branch, arg_version, arg_stage,
  option_version, option_to_version, GTOGroupSection)
- click>=8.2 floor: CliRunner without mix_stderr, kebab-case naming

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.16667% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
gto/cli.py 99.16% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

…paths

Manual output review of every command against pre-migration gto (typer
0.25.1): byte-identical on all success paths; only usage-error
decoration differs (click's plain "Error:" line instead of typer's
rich box, and the hint suggests --help instead of -h).

Review fixes:
- deprecate: version/stage annotated Optional[str] (they can be None)
- remove the "Manage artifact enrichments" help section: no command
  has used it since the enrichment commands were removed
- drop redundant parens in the error-funnel echo

Coverage: gto/cli.py 85% -> 97% (+23 tests). Newly covered: the
promote alias resolution, subcommand help ("Arguments" section),
Examples epilog rendering, --version/-v/--traceback/--tb flags,
click-exception passthrough, --simple/--sort validation errors,
assign version-without-ref and bare-HEAD branches, deprecate
unassign/full-artifact branches, all --json output modes, parse-tag
--key, multi-event refs, show --ref mismatch, print-state, doctor
with broken config, unknown command. Remaining uncovered lines are
defensive fallbacks, completion-mode returns, and __main__.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shcheklein

Copy link
Copy Markdown
Member Author

Post-migration review round (commit 98e8f04):

Manual output review of every command — ran the full command surface (~40 invocations: register/assign/promote/deprecate flows, all show/history/check-ref/stages output modes, parse-tag, print-state, doctor, plus error paths) under both the pre-migration build (main @ 21f6a8a, typer 0.25.1) and this branch, and diffed normalized outputs. Byte-identical on every success path. Only differences are in usage-error decoration: click's plain Error: ... line instead of typer's rich ╭─ Error ─╮ box, and the hint text suggests --help instead of -h (both flags work). Same messages, same exit codes.

Coveragegto/cli.py was at 85% after the migration; the uncovered lines included real regression-sensitive paths (notably the promote alias resolution, which no test exercised). Added 23 tests: alias, subcommand help + "Arguments" section, Examples epilog, --version/-v/--traceback, click-exception passthrough, --simple/--sort validation, assign/deprecate branch paths, all --json modes, parse-tag --key, multi-event refs, show --ref mismatch, print-state, doctor with broken config, unknown command. gto/cli.py: 85% → 97% (project total 86% → 88%; 165 → 188 tests). The 9 remaining uncovered lines are defensive fallbacks, completion-mode resilient_parsing returns, and __main__.

Critical review fixesdeprecate's version/stage params correctly annotated Optional[str]; removed the "Manage artifact enrichments" help section (dead since the enrichment commands were removed); minor paren cleanup. One observation left as-is: doctor's except WrongConfig around the CONFIG repr is unreachable in practice (module-level CONFIG falls back to defaults on parse errors; the failure surfaces later via _get_state → ❌ exit 1, which is now covered by a test) — left the defensive branch alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shcheklein shcheklein merged commit 6907170 into main Jul 9, 2026
16 checks passed
@shcheklein shcheklein deleted the refactor/drop-typer branch July 9, 2026 00:57
@shcheklein shcheklein requested a review from Copilot July 9, 2026 19:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Refactors gto’s CLI implementation to remove the typer dependency and define the CLI directly using click, aligning with the mitigation plan from #497 and reducing exposure to typer’s release changes.

Changes:

  • Replaced the Typer-based CLI with a Click-based app (click.Group), including custom help formatting (Arguments section, Examples epilog) and command ordering/alias handling.
  • Updated tests to use click.testing.CliRunner and to validate new/maintained CLI behaviors (version flag, aliasing, help rendering, exception pass-through).
  • Updated pyproject.toml dependencies to drop typer and require click>=8.2,<9.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
gto/cli.py Rebuilds the CLI on Click (group/commands/options/arguments), preserves help structure via custom formatter/argument class, and keeps unified error handling + aliases.
pyproject.toml Removes typer and bumps Click lower bound to 8.2.
tests/conftest.py Switches runner infrastructure to Click’s CliRunner.
tests/test_cli.py Updates fixtures for Click command discovery and adds/adjusts CLI behavior tests (help rendering, version flag, aliases, exception behavior).
tests/test_config.py Migrates from Typer’s runner to Click’s runner for help invocation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread gto/cli.py
deprecated: bool,
assignments_per_version: int,
versions_per_stage: int,
sort: str,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed — fixed in #503 (the PR merged before this could land here): params now annotated to match the callback return types (Optional[bool] / VersionSort), and the # type: ignore comments those wrong annotations forced are gone. mypy passes without any ignores.

Comment thread gto/cli.py
ref: str,
version: Optional[str],
message: Optional[str],
simple: str,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed — fixed in #503 (the PR merged before this could land here): params now annotated to match the callback return types (Optional[bool] / VersionSort), and the # type: ignore comments those wrong annotations forced are gone. mypy passes without any ignores.

Comment thread gto/cli.py
version: Optional[str],
stage: str,
message: Optional[str],
simple: str,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed — fixed in #503 (the PR merged before this could land here): params now annotated to match the callback return types (Optional[bool] / VersionSort), and the # type: ignore comments those wrong annotations forced are gone. mypy passes without any ignores.

Comment thread gto/cli.py
stage: Optional[str],
ref: Optional[str],
message: Optional[str],
simple: str,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed — fixed in #503 (the PR merged before this could land here): params now annotated to match the callback return types (Optional[bool] / VersionSort), and the # type: ignore comments those wrong annotations forced are gone. mypy passes without any ignores.

shcheklein added a commit that referenced this pull request Jul 9, 2026
Follow-up to #499, addressing [Copilot's post-merge review
comments](#499 (review)):
`callback_simple` normalizes `--simple` to `None|bool` and
`callback_sort` returns `VersionSort`, but the command params were
annotated `str` (carried over from the typer version). Annotate them
truthfully and drop the five `# type: ignore` comments that the wrong
annotations forced. `api.assign`'s `simple` accepts `None` ("auto") at
runtime, so it becomes `Optional[bool]` too.

No behavior change — mypy now passes without any ignores; full suite
(188) green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants