chore(deps): drop Python 3.9, add 3.14, consolidate dependency updates#497
Conversation
Consolidates open Renovate PRs and fixes CI breakage on main: - Drop Python 3.9 (EOL Oct 2025), add Python 3.14 to test matrix and classifiers; requires-python >=3.10 - actions/checkout v5 -> v7 (#496) - codecov/codecov-action v4 -> v7 (#493) - deploy job Python 3.13 -> 3.14 (#484) - pylint 3.3.8 -> 4.0.6 (#487, supersedes #482); single pin now that 3.9 is gone - mypy 1.17.1 -> 2.2.0 (#495 intent, supersedes #494); the py3.9-only <1.11.0 pin is obsolete with 3.9 dropped - declare click explicitly: typer >=0.24 vendors click and no longer pulls it in, so fresh installs on 3.10+ had no click at all while gto imports it directly (this is what broke CI on main) - pin typer <0.24: vendored-click typer is incompatible with gto's click-based CLI machinery (subclassing click.Command, @click.pass_context); the CLI crashes on startup with 0.24+. Migration to newer typer tracked separately - remove the CliRunner mix_stderr shim in tests (only needed for the old click that py3.9 resolved) - zip(..., strict=False) fixes from ruff B905, enforced now that target is 3.10+ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Reportβ All modified and coverable lines are covered by tests. π’ Thoughts on this report? Let us know! |
| classifiers = [ | ||
| "Development Status :: 2 - Pre-Alpha", | ||
| "Programming Language :: Python :: 3", | ||
| "Programming Language :: Python :: 3.9", |
There was a problem hiding this comment.
@skshetry are we good to drop Python 3.9 now? should not be impacting DVC I think, right?
There was a problem hiding this comment.
Verified against current DVC main β no impact:
- DVC only uses gto's pure library surface:
dvc/repo/artifacts.pyimportsgto.constants(assert_name_is_valid,fullname_re),gto.exceptions,gto.tag(find,parse_tag),gto.base(sort_versions). None of those modules import click/typer (that's confined togto/cli.py/gto/utils.py), and none changed in this PR. - DVC still supports and tests py3.9 (
requires-python = ">=3.9", 3.9 in its matrix) withgto>=1.6.0,<2. Once gto releases with>=3.10, pip on py3.9 simply resolves the last 3.9-compatible gto (1.9.x) β installs keep working; 3.9 users just stop receiving gto updates (3.9 has been EOL since Oct 2025). - No pin conflicts: DVC envs already carry click via celery (which requires
click>=8.1,<9β same range as the new pin), and nothing in DVC's tree uses typer. β οΈ One requirement: the next gto release must be1.10.0, not2.0.0β DVC caps atgto<2, so a major bump would be invisible to DVC until they raise the cap.
Side benefit for DVC docs/users: this PR fixes the gto CLI being broken on fresh 3.10+ installs (unpinned typer resolves to 0.26.x β gto --help crashes).
There was a problem hiding this comment.
yes, 3.9 is already EOL'd. I don't plan to drop support in dvc though. But 3.9 users should be able to install older version of GTO.
| # typer>=0.24 vendors click and is incompatible with gto's click-based | ||
| # CLI machinery (subclassing click.Command, @click.pass_context, etc.): | ||
| # the CLI crashes on startup. Migration tracked separately. | ||
| "typer>=0.4.1,<0.24", |
There was a problem hiding this comment.
what do we use typer for? explain, plan the mitigation strategy
There was a problem hiding this comment.
What typer is used for: the entire CLI (gto/cli.py) is built on it β app = Typer(cls=GtoGroup), every command declared via typer Argument/Option, plus typer.Context/typer.Exit/typer.colors. On top of that, gto customizes help/UX by subclassing typer.core.TyperCommand/TyperGroup mixed with real-click classes: GtoCliMixin(click.Command) implements grouped help sections ("Read the registry" / "Modify artifacts"), command aliases, and "Examples:" epilogs; gto_command() wraps every command with @click.pass_context for unified error handling; tests use typer.testing.CliRunner.
Why the pin: typer 0.24+ replaced its click dependency with a vendored copy (typer._click). Two consequences: (1) fresh installs stopped getting click at all while gto imports it directly β the current red main; (2) real-click classes mixed into vendored-click TyperCommand/TyperGroup MROs are incompatible β with typer 0.26 even gto --help crashes (TypeError: Command.__init__() got an unexpected keyword argument 'rich_markup_mode'). Every fresh pip install gto on 3.10+ has shipped a broken CLI since typer 0.24 started resolving.
Mitigation plan (in preference order):
- Migrate to typer-native APIs (preferred):
rich_help_panelcovers the command grouping,epilogcovers the Examples blocks, aliases via registering additional command names; replace the@click.pass_contexterror-handling wrapper with actx: typer.Contextparam and typer's re-exported exceptions (typer.Exit/Abort/BadParameter); tests dropclick.testing.Resultfortyper.testing's. Then remove both thetyper<0.24andclickpins. Scope: essentially one focused PR touchinggto/cli.py+tests/conftest.py. - Drop typer for plain click 8.x β the custom machinery is click-level already; typer mainly provides the decorator ergonomics.
- Rejected: importing from
typer._click(private API, breaks on any typer minor).
Happy to open a tracking issue for option 1.
There was a problem hiding this comment.
Correction after checking typer's release notes: the vendoring landed in typer 0.26.0 (2026-05-26), not 0.24 β 0.24.0 only dropped Python 3.9. Timeline matches our CI: the weekly run started failing June 8, right after 0.26.0's release.
Relaxed the pin accordingly to typer>=0.4.1,<0.26 in 70bf976 (verified typer 0.25.1 + click 8.4.2: CLI, lints, and the full suite pass).
Typer's stated reasons for vendoring, from the 0.26.0 release notes: simplifies work for both teams, lets typer evolve independently and enables planned features, and avoids dependency conflicts between packages pinning different click versions. The trade-off is exactly what bit us: click-specific integrations (subclassing click.Command, @click.pass_context, click.testing) are no longer supported against typer's runtime β hence the migration plan above.
There was a problem hiding this comment.
Pull request overview
This PR consolidates multiple dependency/CI updates while fixing the CLI breakage caused by newer typer versions (vendored click) and formally updates the supported Python range to 3.10β3.14.
Changes:
- Drops Python 3.9 support and adds Python 3.14 across metadata and CI.
- Fixes CLI/runtime dependency resolution by declaring
clickexplicitly and constrainingtyperto<0.24. - Updates CI actions and dev tooling pins (notably
pylintv4 andmypyv2), plus ruff-drivenzip(..., strict=...)edits.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
pyproject.toml |
Drops 3.9, sets requires-python>=3.10, adds click dependency, constrains typer, updates dev-tool pins. |
tests/conftest.py |
Removes the legacy CliRunner(mix_stderr=...) compatibility shim now that 3.9 is dropped. |
tests/test_api.py |
Adds explicit zip(..., strict=...) argument for Python 3.10+ target. |
tests/test_registry.py |
Adds explicit zip(..., strict=...) argument in registry-state comparisons. |
gto/api.py |
Adds explicit zip(..., strict=...) argument when building the table output rows. |
.github/workflows/check-test-release.yml |
Updates test matrix to 3.10β3.14 and bumps CI actions (checkout@v7, codecov-action@v7), deploy job uses Python 3.14. |
π‘ Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Per review: fail fast instead of silently truncating. The api.py pair derives both sides from the same stages list; the test zips are equality checks where truncation would mask mismatches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
typer 0.24 only dropped py3.9; 0.24/0.25 still depend on real click. Verified typer 0.25.1 + click 8.4.2: CLI works, lints and full test suite pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Follow-up to #497 β implements the mitigation plan from [the typer discussion there](#497 (comment)): 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.CliRunner` β `click.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](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Consolidates all open Renovate deps/chore PRs into one change, fixes the CI breakage on
main, and moves the supported Python range to 3.10β3.14.Why main was red
The weekly runs on
mainhave been failing on Python 3.10+ since early June. Root cause: typer β₯0.26 vendors click and no longer depends on it, so fresh installs stopped pulling click in β while gto importsclickdirectly (gto/cli.py,gto/utils.py,tests/conftest.py) without declaring it. pylint failed withE0401: Unable to import 'click'.It's worse than lint: gto's CLI machinery subclasses real-click classes (
GtoCliMixin(click.Command),@click.pass_context) and mixes them into typer's (now vendored-click)TyperCommand/TyperGroupMROs. With typer 0.26.x evengto --helpcrashes on startup (TypeError: Command.__init__() got an unexpected keyword argument 'rich_markup_mode') β i.e. a freshpip install gtoon Python 3.10+ currently yields a broken CLI. Python 3.9 was unaffected only because it resolves typer 0.23.2 (0.24+ requires Python β₯3.10; all pre-0.26 versions depend on real click).Fix: declare
click>=8,<9explicitly and pintyper>=0.4.1,<0.26. Migrating to vendored-click typer (β₯0.26) requires reworking the CLI help/exception machinery againsttyper._clickand should be tracked as a separate effort.Python support
requires-python = ">=3.10"CliRunnermix_stderrshim intests/conftest.py(only needed for the old click that py3.9 resolved)zip(..., strict=False)fixes auto-applied by ruffB905, which activates with the 3.10+ targetConsolidated Renovate updates
actions/checkoutv5 β v7codecov/codecov-actionv4 β v7mypy==2.2.0(the py3.9-only<1.11.0pin is gone with 3.9)<1.20.3Verification
Ran locally on macOS for Python 3.10, 3.13, and 3.14, each with a fresh venv:
pip install -e .[dev]resolves cleanly (click 8.4.2, typer 0.23.2, mypy 2.2.0, pylint 4.0.6, pygit2 1.19.3)pre-commit run --all-filesβ all hooks pass (ruff, ruff-format, mypy 2.2.0, pylint 4.0.6)pytestβ 165 passed on eachgto --help/gto show/gto --versionwork (broken before on 3.10+ with unpinned typer)python -m build && twine check --strict dist/*passes on 3.14 (deploy job path)π€ Generated with Claude Code