Skip to content

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

Merged
JoyboyBrian merged 3 commits into
mainfrom
refactor/migrate-typer-0.26
Jul 1, 2026
Merged

[cli] refactor: migrate to typer 0.26 (vendored click)#248
JoyboyBrian merged 3 commits into
mainfrom
refactor/migrate-typer-0.26

Conversation

@JoyboyBrian

@JoyboyBrian JoyboyBrian commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

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 Expose ClickException and UsageError from the typer namespace 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.

Written for commit 1a01816. Summary will update on new commits.

Review in cubic

Typer 0.26 vendors Click, so exceptions raised by Typer are no longer
instances of the external click package's classes. Instead of pinning
typer <0.26 forever, migrate the CLI off direct `import click` entirely:

- All vendored symbols without a public typer re-export yet
  (fastapi/typer#1868) go through one module: cli/_click_compat.py.
  Everything else uses public typer API (typer.Exit, typer.Abort).
- main(): replace the empty-message UsageError hack for no_args_is_help
  with an explicit `except NoArgsIsHelpError`.
- get_output_context(): the ContextVar set by install_output_context()
  is now the source of truth; no more reaching into Click's threadlocal.
- OsmosisGroup stays: tests pin its single-candidate suggestions, hidden
  command filtering, and the 'help' nudge. Native suggest_commands is
  disabled so it cannot double-append suggestions.
- Tests construct vendored objects (typer.core.TyperCommand) and use
  typer.testing.CliRunner instead of external click equivalents.

CLI code no longer depends on the external click package at all, which
also ends click version conflicts with other click-dependent deps in
user environments.
@JoyboyBrian JoyboyBrian requested a review from BaiqingL as a code owner July 1, 2026 23:32
@github-actions github-actions Bot added cli CLI related refactor Code refactoring labels Jul 1, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 12 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Re-trigger cubic

typer-slim <0.22 ships its own copy of the typer/ package; the lock had
0.21.1 (via huggingface-hub), which overlay-corrupted the typer 0.26.8
install nondeterministically (install-order dependent) — green locally,
mixed 0.21/0.26 module tree on CI. typer-slim 0.22+ is an empty shim
that just requires typer, so pinning >=0.22 removes the file collision
for our CI and for downstream installs alike.
@JoyboyBrian

Copy link
Copy Markdown
Contributor Author

Why the first CI run failed (and passed locally): the lockfile carried typer-slim==0.21.1 — the last version of that distribution that ships its own copy of the typer/ package (it comes in via huggingface-hub). With typer==0.26.8 also installing typer/, the two wheels overlay each other and the winner is install-order dependent. CI's fresh venv ended up with a mixed 0.21/0.26 module tree (typer._click present, but typer/core.py from 0.21 raising external-click exceptions), while the incrementally-updated local venv happened to be clean.

typer-slim 0.22+ is an empty redirect shim (Requires-Dist: typer>=<version>, zero files), so the fix commit upgrades it in the lock and adds a typer-slim>=0.22 guard to pyproject.toml — the collision would otherwise reproduce in any downstream environment that resolves an old typer-slim next to typer 0.26.

Note this collision predates this PR: main's lock has typer==0.23.1 + typer-slim==0.21.1, also a nondeterministic overlay — it just happened to be benign while both versions still used the external click. Verified on a from-scratch venv this time: 1721 tests pass, pyright clean.

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
osmosis_ai/cli/_click_compat.py 71.42% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

The typer-slim>=0.22 floor protects new resolutions, but an environment
that was already overlay-corrupted (stale typer-slim files over typer's)
degrades silently: exceptions stop matching, exit codes drift, and
suggestions vanish. Assert the vendored-class lineage at import time and
raise an actionable ImportError instead.
@JoyboyBrian JoyboyBrian merged commit 97b8e5b into main Jul 1, 2026
11 checks passed
@JoyboyBrian JoyboyBrian deleted the refactor/migrate-typer-0.26 branch July 1, 2026 23:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI related refactor Code refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant