From 2483705ed65962f90285efc9d725974f1669d938 Mon Sep 17 00:00:00 2001 From: Ivan Shcheklein Date: Wed, 8 Jul 2026 10:53:50 -0700 Subject: [PATCH 1/3] chore(deps): drop Python 3.9, add 3.14, consolidate dependency updates 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 --- .github/workflows/check-test-release.yml | 10 +++++----- gto/api.py | 1 + pyproject.toml | 18 ++++++++++-------- tests/conftest.py | 9 +-------- tests/test_api.py | 1 + tests/test_registry.py | 2 ++ 6 files changed, 20 insertions(+), 21 deletions(-) diff --git a/.github/workflows/check-test-release.yml b/.github/workflows/check-test-release.yml index 1b8d6d46..161bf3ea 100644 --- a/.github/workflows/check-test-release.yml +++ b/.github/workflows/check-test-release.yml @@ -26,10 +26,10 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 with: fetch-depth: 0 @@ -44,7 +44,7 @@ jobs: - run: pytest - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v7 with: fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} @@ -59,12 +59,12 @@ jobs: id-token: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v7 with: fetch-depth: 0 - uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.14' - run: pip install build twine - run: python -m build diff --git a/gto/api.py b/gto/api.py index a7fdd753..93e3b3dd 100644 --- a/gto/api.py +++ b/gto/api.py @@ -364,6 +364,7 @@ def format_hexsha(hexsha): d["version"], ] + [d["stage"][name] for name in stages], + strict=False, ), ) for name, d in sorted(models_state.items()) diff --git a/pyproject.toml b/pyproject.toml index fb7a128f..b4b7b606 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,15 +12,17 @@ keywords = ["git", "repo", "repository", "artifact", "registry", "developer-tool classifiers = [ "Development Status :: 2 - Pre-Alpha", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] -requires-python = ">=3.9" +requires-python = ">=3.10" dynamic = ["version"] dependencies = [ + # gto imports click directly, so declare it explicitly + "click>=8,<9", "entrypoints", "funcy", "pydantic>=2", @@ -30,7 +32,10 @@ dependencies = [ "scmrepo>=3,<4", "semver>=2.13.0", "tabulate>=0.8.10", - "typer>=0.4.1", + # 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", ] [project.urls] @@ -48,11 +53,8 @@ tests = [ ] dev = [ "gto[tests]", - "mypy==1.17.1; python_version > '3.9'", - # mypy>=1.11.0 crashes when used with pydantic-settings on Python 3.9, - # see: https://github.com/python/mypy/issues/17535 - "mypy<1.11.0; python_version <= '3.9'", - "pylint==3.3.8", + "mypy==2.2.0", + "pylint==4.0.6", "types-PyYAML", "types-filelock", "types-freezegun", diff --git a/tests/conftest.py b/tests/conftest.py index c6ada54f..d2c69d30 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ # pylint: disable=redefined-outer-name -import inspect import os import sys from time import sleep @@ -19,13 +18,7 @@ class Runner: def __init__(self): - # Older versions of CliRunner need this - # Can we removed when we drop Py 3.9 support - init_sig = inspect.signature(CliRunner.__init__) - if "mix_stderr" in init_sig.parameters: - self._runner = CliRunner(mix_stderr=False) # pylint: disable=unexpected-keyword-arg - else: - self._runner = CliRunner() + self._runner = CliRunner() def invoke(self, *args, **kwargs) -> Result: return self._runner.invoke(app, *args, **kwargs) diff --git a/tests/test_api.py b/tests/test_api.py index 5d980b08..c9439bdf 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -298,6 +298,7 @@ def test_check_ref_catch_the_bug(scm: Git): for assignment, tag in zip( [assignment1, assignment2, assignment3], [f"{NAME}#staging#1", f"{NAME}#prod#2", f"{NAME}#dev#3"], + strict=False, ): events = gto.api.check_ref(scm, tag) assert len(events) == 1, events diff --git a/tests/test_registry.py b/tests/test_registry.py index fd9a5161..a98137ec 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -431,6 +431,7 @@ def test_registry_state_tag_tag(tmp_dir: TmpDir): for appeared, expected in zip( iter_over(appeared_state["artifacts"][artifact]["versions"]), iter_over(expected_state["artifacts"][artifact]["versions"]), + strict=False, ): check_obj(appeared, expected, exclude["versions"]) @@ -438,6 +439,7 @@ def test_registry_state_tag_tag(tmp_dir: TmpDir): for a, e in zip( iter_over(appeared[key]), iter_over(expected[key]), + strict=False, ): check_obj(a, e, exclude[key]) From 50178f49fb2209efab18f829eb5a6b422f0616ec Mon Sep 17 00:00:00 2001 From: Ivan Shcheklein Date: Wed, 8 Jul 2026 11:49:20 -0700 Subject: [PATCH 2/3] use zip(strict=True) where iterables are provably equal-length 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 --- gto/api.py | 2 +- tests/test_api.py | 2 +- tests/test_registry.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gto/api.py b/gto/api.py index 93e3b3dd..b437471f 100644 --- a/gto/api.py +++ b/gto/api.py @@ -364,7 +364,7 @@ def format_hexsha(hexsha): d["version"], ] + [d["stage"][name] for name in stages], - strict=False, + strict=True, ), ) for name, d in sorted(models_state.items()) diff --git a/tests/test_api.py b/tests/test_api.py index c9439bdf..579df0a5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -298,7 +298,7 @@ def test_check_ref_catch_the_bug(scm: Git): for assignment, tag in zip( [assignment1, assignment2, assignment3], [f"{NAME}#staging#1", f"{NAME}#prod#2", f"{NAME}#dev#3"], - strict=False, + strict=True, ): events = gto.api.check_ref(scm, tag) assert len(events) == 1, events diff --git a/tests/test_registry.py b/tests/test_registry.py index a98137ec..b7ab6e44 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -431,7 +431,7 @@ def test_registry_state_tag_tag(tmp_dir: TmpDir): for appeared, expected in zip( iter_over(appeared_state["artifacts"][artifact]["versions"]), iter_over(expected_state["artifacts"][artifact]["versions"]), - strict=False, + strict=True, ): check_obj(appeared, expected, exclude["versions"]) @@ -439,7 +439,7 @@ def test_registry_state_tag_tag(tmp_dir: TmpDir): for a, e in zip( iter_over(appeared[key]), iter_over(expected[key]), - strict=False, + strict=True, ): check_obj(a, e, exclude[key]) From 70bf9762585e85e7f8d5588b44e2514be68f89a9 Mon Sep 17 00:00:00 2001 From: Ivan Shcheklein Date: Wed, 8 Jul 2026 14:23:51 -0700 Subject: [PATCH 3/3] relax typer pin to <0.26: vendoring landed in 0.26, not 0.24 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 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b4b7b606..ff36dd6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,10 +32,10 @@ dependencies = [ "scmrepo>=3,<4", "semver>=2.13.0", "tabulate>=0.8.10", - # typer>=0.24 vendors click and is incompatible with gto's click-based + # typer>=0.26 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", + "typer>=0.4.1,<0.26", ] [project.urls]