[cli] refactor: migrate to typer 0.26 (vendored click)

Merged
JO
4mo ago
+140-81

Summary

Typer 0.26 vendors Click (breaking change), so exceptions raised by Typer are no longer instances of the external click package's classes — the reason for our previous typer <0.26 cap. Instead of staying frozen on 0.25 forever, this migrates the CLI off direct import click entirely and moves to typer>=0.26,<0.27.

Closes #243 (supersedes the Dependabot range widening; that PR would have broken the CLI's top-level error handling).

Changes

  • New cli/_click_compat.py — single import point for the vendored symbols Typer does not publicly re-export yet (UsageError, ClickException, NoArgsIsHelpError, Context, Command, get_current_context; see fastapi/typer#1868). The <0.27 cap keeps these private paths stable; the module is deleted once upstream exports them. Everything else uses public typer API (typer.Exit, typer.Abort).
  • main() error handling — the fragile "empty-message UsageError means help was already printed" hack is replaced with an explicit except NoArgsIsHelpError.
  • get_output_context() — the ContextVar set by install_output_context() is now the source of truth; the redundant reach into Click's threadlocal context stack is gone.
  • OsmosisGroup is kept unchanged — tests pin its deliberate behaviors (single-candidate suggestion format, hidden-command filtering, the osmosis help nudge). Typer's native suggest_commands is explicitly disabled so it cannot double-append its own (unfiltered, multi-candidate) suggestions.
  • Tests — construct vendored objects (typer.core.TyperCommand; the vendored Command is now abstract) and use typer.testing.CliRunner; the removed ctx.obj resolution layer's test is replaced by two tests asserting the new ContextVar contract.

Behavior

No user-facing behavior changes. Verified via the full suite plus real-process smoke tests: bare osmosis (rich help, exit 0), typo suggestions, osmosis help nudge, --version, and the --json structured error envelope.

CLI code no longer depends on the external click package at all, which also ends click version conflicts with other click-dependent dependencies in user environments.

Verification

  • pytest: 1721 passed
  • ruff check / ruff format --check: clean
  • pyright osmosis_ai/: 0 errors; --verifytypes: no unexpected public API errors

Note for local editable installs

Dependencies of editable installs do not auto-update: run pip install -e ".[dev]" (or uv sync) after pulling this change to pick up typer 0.26.


Summary by cubic

Migrate the CLI to typer 0.26 (vendored Click) and remove direct click usage to keep error handling correct and avoid version conflicts. Add a hard guard against typer-slim overlays and fail fast with a clear ImportError if a corrupted typer install is detected; no user-facing changes otherwise.

  • Refactors

    • Add osmosis_ai/cli/_click_compat.py to import vendored types (UsageError, ClickException, NoArgsIsHelpError, Context, Command, get_current_context).
    • Replace the empty-message UsageError hack with NoArgsIsHelpError; use typer.Exit/typer.Abort.
    • Make the ContextVar set by install_output_context() the source of truth in get_output_context(); remove threadlocal lookups.
    • Preserve OsmosisGroup suggestions and disable Typer’s suggest_commands; tests updated to typer.core and typer.testing.
    • Assert Typer↔vendored Click lineage at import and raise an actionable ImportError if typer files were overlaid (guides reinstall).
  • Dependencies

    • Pin typer>=0.26,<0.27 and add typer-slim>=0.22 to prevent overlay corruption; CLI no longer imports click.
    • For editable installs, run pip install -e ".[dev]" or uv sync to pick up the new versions.