From d53401e941729698bb9e7800504d34321a621d42 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Tue, 30 Jun 2026 13:36:36 +0300 Subject: [PATCH 1/6] docs: design for Python 3.11/3.12 support Co-Authored-By: Claude Opus 4.8 (1M context) --- .../design.md | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 planning/changes/2026-06-30.01-python-3.11-3.12-support/design.md diff --git a/planning/changes/2026-06-30.01-python-3.11-3.12-support/design.md b/planning/changes/2026-06-30.01-python-3.11-3.12-support/design.md new file mode 100644 index 0000000..90a6dbb --- /dev/null +++ b/planning/changes/2026-06-30.01-python-3.11-3.12-support/design.md @@ -0,0 +1,135 @@ +--- +summary: Lower the supported Python floor from 3.13 to 3.11, rewriting retry.py off PEP 695 and proving 3.11-3.14 in a CI matrix. +--- + +# Design: Add Python 3.11 / 3.12 support + +## Summary + +Lower the package's supported Python floor from 3.13 to 3.11. The runtime +dependencies (tenacity, sqlalchemy, asyncpg) already support 3.11+, so the only +source change is `retry.py`, which uses PEP 695 generic syntax that is a syntax +error before 3.12. We rewrite those declarations to the pre-695 stdlib +`ParamSpec`/`TypeVar`/`TypeAlias` form (no new dependency), widen +`requires-python` and the classifiers, lower the ruff target, and add a CI +pytest matrix over 3.11-3.14 that proves every supported version at runtime. +This mirrors the established `faststream-redis-timers` convention in the same +org. + +## Motivation + +`postgres_retry` and friends have no runtime feature that requires 3.13 — the +floor is incidental, set by the PEP 695 syntax in `retry.py`. Widening to 3.11 +broadens adoption (3.11 and 3.12 are still widely deployed) at the cost of one +mechanical typing rewrite. The sibling `faststream-redis-timers` package already +ships `requires-python = ">=3.11,<4"` with a 3.11-3.14 CI matrix; aligning +`db-retry` keeps the org's packages consistent. + +## Non-goals + +- No runtime behavior change: the retry/failover/transaction semantics are + identical on every version. +- No new dependency: `typing_extensions` is not needed — `ParamSpec`, + `TypeVar`, and `TypeAlias` are stdlib since 3.10. +- No new tests and no version-gated code branches: the existing suite runs + unchanged across the matrix. + +## Design + +### 1. `retry.py` — rewrite off PEP 695 + +The current module uses two 3.12+ constructs that are hard syntax errors on +3.11: the `type` alias statement and generic-function syntax (`def f[**P, T]`). + +Today (3.12+ only): + +```python +type _Func[**P, T] = typing.Callable[P, typing.Coroutine[None, None, T]] +type _Decorator[**P, T] = typing.Callable[[_Func[P, T]], _Func[P, T]] + +@typing.overload +def postgres_retry[**P, T](func: _Func[P, T], *, retries: int | None = ...) -> _Func[P, T]: ... +``` + +Rewritten (3.11-compatible), keeping the named aliases: + +```python +P = typing.ParamSpec("P") +T = typing.TypeVar("T") + +_Func: typing.TypeAlias = typing.Callable[P, typing.Coroutine[None, None, T]] +_Decorator: typing.TypeAlias = typing.Callable[[_Func], _Func] + +@typing.overload +def postgres_retry(func: _Func, *, retries: int | None = ...) -> _Func: ... +@typing.overload +def postgres_retry(func: None = ..., *, retries: int | None = ...) -> _Decorator: ... +def postgres_retry(func: _Func | None = None, *, retries: int | None = None) -> _Func | _Decorator: + ... # body unchanged (lines 39-55) +``` + +`P` and `T` move to module scope; the bare aliases re-bind them per signature, +which is exactly how pre-695 generic aliases work. The function body — the +`tenacity.AsyncRetrying` construction and the `decorator`/`wrapped_method` +nesting — is untouched. The two public call forms (`@postgres_retry` and +`@postgres_retry(retries=N)`) and their inferred types are preserved. + +### 2. `pyproject.toml` + +- `requires-python = ">=3.13,<4"` -> `">=3.11,<4"`. +- Add classifiers `Programming Language :: Python :: 3.11` and `:: 3.12` (the + 3.13 / 3.14 entries stay). +- `[tool.ruff] target-version = "py313"` -> `"py311"`, so lint catches any + 3.11-incompatible syntax introduced later (and stops suggesting 3.12+-only + upgrades). + +### 3. CI — `.github/workflows/_checks.yml` + +The `pytest` job gains a version matrix mirroring `faststream-redis-timers`: + +```yaml + pytest: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12", "3.13", "3.14"] + services: + postgres: + ... # unchanged + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/setup-uv@v8.2.0 + - run: uv python install ${{ matrix.python-version }} + - run: | + uv sync --all-extras --no-install-project + uv run --no-sync pytest . --cov=. --cov-report xml + env: + ... # unchanged +``` + +The `lint` job stays pinned to `uv python install 3.13` (single-version gate). + +### 4. Docs + +- `README.md:216`: "Python 3.13+" -> "Python 3.11+". The pyversions badge is + driven by PyPI metadata and updates automatically. +- Promote into `architecture/retry.md` in the implementing PR (note the + pre-695 typing form so the capability page stays code-current). + +## Testing + +- `just lint-ci` (ruff `--check` + `ty check`) passes with `target-version = + py311`; `ty check` confirms the rewritten overloads still type-check. +- `uv run pytest` passes locally against `DB_DSN`. +- CI proves runtime correctness on 3.11, 3.12, 3.13, and 3.14, each holding the + existing `--cov-fail-under=100` gate. + +## Risk + +- **Low: the rewritten generic aliases type-check differently than PEP 695.** + Mitigation: `ty check` in the lint gate plus the unchanged overload tests + catch any inference regression before merge. +- **Low: a transitive dependency drops 3.11 wheels.** Mitigation: the 3.11 CI + leg fails loudly at `uv sync` if so; deps are unpinned and currently all + publish 3.11 wheels. From 872414b61f094a9a53d3fb1394e0b5c7f83b4888 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Tue, 30 Jun 2026 13:58:06 +0300 Subject: [PATCH 2/6] docs: implementation plan for Python 3.11/3.12 support Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plan.md | 337 ++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 planning/changes/2026-06-30.01-python-3.11-3.12-support/plan.md diff --git a/planning/changes/2026-06-30.01-python-3.11-3.12-support/plan.md b/planning/changes/2026-06-30.01-python-3.11-3.12-support/plan.md new file mode 100644 index 0000000..49c8922 --- /dev/null +++ b/planning/changes/2026-06-30.01-python-3.11-3.12-support/plan.md @@ -0,0 +1,337 @@ +# python-3.11-3.12-support — implementation plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:subagent-driven-development (recommended) or +> superpowers:executing-plans to implement this plan task-by-task. Steps +> use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Lower the supported Python floor from 3.13 to 3.11 — rewrite +`retry.py` off PEP 695, widen packaging metadata, and prove 3.11-3.14 in a CI +matrix. + +**Spec:** [`design.md`](./design.md) + +**Branch:** `feat/python-3.11-3.12-support` (already created and checked out; +the design commit is on it). + +**Commit strategy:** Per-task commits. + +## Global Constraints + +- Python floor: `requires-python = ">=3.11,<4"`. Code must parse and run on + 3.11 through 3.14. +- No new dependency. `ParamSpec`, `TypeVar`, `TypeAlias` are stdlib `typing` + since 3.10 — do not add `typing_extensions`. +- No runtime behavior change: retry/failover/transaction semantics stay + identical. The existing test suite is the behavior contract. +- All imports at module level (project rule). Annotate all function arguments. +- Type-checker is `ty`; suppression comments are `ty: ignore` (never + `type: ignore`). +- Coverage gate `--cov-fail-under=100` must stay green on every version. +- Promote behavior changes into `architecture/.md` in this same PR; + finalize the bundle `summary` at ship. Run `just check-planning` before push. + +--- + +### Task 1: Rewrite `retry.py` off PEP 695 + +**Files:** +- Modify: `db_retry/retry.py:22-38` +- Modify: `architecture/retry.md:17-19` +- Test: `tests/test_retry.py` (existing, unchanged) + +**Interfaces:** +- Consumes: nothing new. +- Produces: `postgres_retry` keeps its exact public contract — two + `typing.overload`s, callable as bare `@postgres_retry` (returns the wrapped + `_Func`) and as `@postgres_retry(retries=N)` (returns a `_Decorator`). The + named module-level aliases `_Func` and `_Decorator` are retained. + +This is a behavior-preserving typing refactor. There is no new local test that +can prove 3.11 syntax-compatibility (the local interpreter is 3.13+); the proof +is the 3.11 CI leg added in Task 3. Locally we verify the rewrite preserves +typing (`ty check`) and behavior (existing `test_retry.py`). + +- [ ] **Step 1: Confirm the existing retry tests pass before touching code** + + Run: `uv run pytest tests/test_retry.py -v` + Expected: PASS (all existing tests green). This is the green baseline the + refactor must preserve. + +- [ ] **Step 2: Replace the PEP 695 declarations** + + In `db_retry/retry.py`, replace the current lines 22-38: + + ```python + type _Func[**P, T] = typing.Callable[P, typing.Coroutine[None, None, T]] + type _Decorator[**P, T] = typing.Callable[[_Func[P, T]], _Func[P, T]] + + + @typing.overload + def postgres_retry[**P, T](func: _Func[P, T], *, retries: int | None = ...) -> _Func[P, T]: ... + + + @typing.overload + def postgres_retry[**P, T](func: None = ..., *, retries: int | None = ...) -> _Decorator[P, T]: ... + + + def postgres_retry[**P, T]( + func: _Func[P, T] | None = None, + *, + retries: int | None = None, + ) -> _Func[P, T] | _Decorator[P, T]: + ``` + + with the pre-695 form (module-level `P`/`T`, `TypeAlias` aliases): + + ```python + P = typing.ParamSpec("P") + T = typing.TypeVar("T") + + _Func: typing.TypeAlias = typing.Callable[P, typing.Coroutine[None, None, T]] + _Decorator: typing.TypeAlias = typing.Callable[[_Func], _Func] + + + @typing.overload + def postgres_retry(func: _Func, *, retries: int | None = ...) -> _Func: ... + + + @typing.overload + def postgres_retry(func: None = ..., *, retries: int | None = ...) -> _Decorator: ... + + + def postgres_retry( + func: _Func | None = None, + *, + retries: int | None = None, + ) -> _Func | _Decorator: + ``` + + Leave the function body (the `decorator` / `wrapped_method` nesting and the + `tenacity.AsyncRetrying` construction, currently lines 39-55) completely + unchanged. `import typing` is already present at the top — do not re-import. + +- [ ] **Step 3: Type-check the rewrite** + + Run: `uv run ty check` + Expected: PASS, no errors. Confirms the overloads and aliases still + type-check (no inference regression from dropping PEP 695). + +- [ ] **Step 4: Run the retry tests to confirm behavior is preserved** + + Run: `uv run pytest tests/test_retry.py -v` + Expected: PASS — same set of tests green as in Step 1. + +- [ ] **Step 5: Promote into `architecture/retry.md`** + + In `architecture/retry.md`, in the "Public surface" section, append a + sentence to the paragraph ending at line 19 (after + "...it returns a decorator.") noting the typing form: + + ```markdown + The generics are expressed with module-level `typing.ParamSpec`/`TypeVar` + and `TypeAlias` aliases (`_Func`, `_Decorator`) rather than PEP 695 syntax, + so the module parses on Python 3.11+. + ``` + +- [ ] **Step 6: Commit** + + ```bash + git add db_retry/retry.py architecture/retry.md + git commit -m "refactor: express postgres_retry generics pre-PEP-695 for 3.11" + ``` + +--- + +### Task 2: Widen packaging metadata and docs + +**Files:** +- Modify: `pyproject.toml:8` (requires-python), `:11-18` (classifiers), `:58` + (ruff target-version) +- Modify: `README.md:216` + +**Interfaces:** +- Consumes: nothing. +- Produces: a package installable on 3.11+; ruff linting at `py311` target + (which, being below 3.12, will not suggest reverting to PEP 695 syntax). + +- [ ] **Step 1: Lower the Python floor in `pyproject.toml`** + + Change line 8 from: + + ```toml + requires-python = ">=3.13,<4" + ``` + + to: + + ```toml + requires-python = ">=3.11,<4" + ``` + +- [ ] **Step 2: Add the 3.11 and 3.12 classifiers** + + In the `classifiers` list, add the two entries above the existing 3.13 line + so the block reads: + + ```toml + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + ``` + +- [ ] **Step 3: Lower the ruff target version** + + Change line 58 from: + + ```toml + target-version = "py313" + ``` + + to: + + ```toml + target-version = "py311" + ``` + +- [ ] **Step 4: Update the README support line** + + In `README.md`, change line 216 from `- Python 3.13+` to `- Python 3.11+`. + +- [ ] **Step 5: Run the full lint gate** + + Run: `just lint-ci` + Expected: PASS — ruff `--check`, ruff format `--check`, eof-fixer, and + `ty check` all clean at the new `py311` target. In particular, ruff must NOT + raise UP040/UP046/UP047 (PEP 695 upgrade hints) against the Task 1 rewrite; + if it does, the target lowering in Step 3 was not applied. + +- [ ] **Step 6: Commit** + + ```bash + git add pyproject.toml README.md + git commit -m "chore: lower supported Python floor to 3.11" + ``` + +--- + +### Task 3: Add the CI version matrix + +**Files:** +- Modify: `.github/workflows/_checks.yml:18-44` (the `pytest` job) + +**Interfaces:** +- Consumes: the 3.11-compatible package from Tasks 1-2. +- Produces: a `pytest` job that runs once per `3.11/3.12/3.13/3.14`, each + against the postgres service, each holding the 100% coverage gate. + +- [ ] **Step 1: Add the matrix and parameterize the interpreter** + + In `.github/workflows/_checks.yml`, edit the `pytest` job (lines 18-44). Add + a `strategy` block under `runs-on`, and change the hardcoded + `uv python install 3.13` to use the matrix value. The job becomes: + + ```yaml + pytest: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.11" + - "3.12" + - "3.13" + - "3.14" + services: + postgres: + image: postgres:latest + env: + POSTGRES_DB: postgres + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/setup-uv@v8.2.0 + - run: uv python install ${{ matrix.python-version }} + - run: | + uv sync --all-extras --no-install-project + uv run --no-sync pytest . --cov=. --cov-report xml + env: + PYTHONDONTWRITEBYTECODE: 1 + PYTHONUNBUFFERED: 1 + DB_DSN: postgresql+asyncpg://postgres:password@127.0.0.1/postgres + ``` + + Leave the `lint` job (lines 5-16) unchanged — it stays pinned to 3.13. + +- [ ] **Step 2: Validate the workflow YAML parses** + + Run: `uv run python -c "import yaml,pathlib; yaml.safe_load(pathlib.Path('.github/workflows/_checks.yml').read_text())"` + Expected: no output, exit 0 (valid YAML). + +- [ ] **Step 3: Commit** + + ```bash + git add .github/workflows/_checks.yml + git commit -m "ci: run pytest across Python 3.11-3.14" + ``` + +--- + +### Task 4: Finalize the planning bundle and open the PR + +**Files:** +- Modify: `planning/changes/2026-06-30.01-python-3.11-3.12-support/design.md` + (frontmatter `summary`) + +**Interfaces:** +- Consumes: the shipped Tasks 1-3. +- Produces: a validated planning bundle and a PR with CI running. + +- [ ] **Step 1: Finalize the bundle summary to the realized result** + + The `summary:` was written as intent; confirm it states what shipped. Leave + it as-is if still accurate, or tighten to the realized result (one line). + +- [ ] **Step 2: Validate planning shape** + + Run: `just check-planning` + Expected: `planning: OK`. + +- [ ] **Step 3: Run the full local pipeline once** + + Run: `just lint-ci` then `uv run pytest` + Expected: both PASS locally (DB_DSN set, or via `just test` in Docker). + +- [ ] **Step 4: Push and open the PR** + + ```bash + git push -u origin feat/python-3.11-3.12-support + gh pr create --fill + ``` + + Then watch CI: `gh pr checks --watch`. The four pytest matrix legs + (3.11-3.14) and the lint leg must all go green. The 3.11 leg is the real + proof that the Task 1 rewrite is syntax-compatible. + +--- + +## Self-review notes + +- **Spec coverage:** retry.py rewrite (Task 1), pyproject requires-python + + classifiers + ruff target (Task 2), README (Task 2), CI matrix (Task 3), + architecture promotion (Task 1 Step 5), bundle finalize (Task 4). All design + sections mapped. +- **No new tests:** by design — the existing suite is the behavior contract and + the CI matrix is the cross-version proof. No version-gated branches added. +- **Type consistency:** alias names `_Func` / `_Decorator` and the public name + `postgres_retry` are used identically across Task 1 and the architecture + promotion. From e628c26c0d325e3ace511c1cdeea606235ffd1ed Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Tue, 30 Jun 2026 14:29:01 +0300 Subject: [PATCH 3/6] refactor: express postgres_retry generics pre-PEP-695 for 3.11 --- architecture/retry.md | 4 +++- db_retry/retry.py | 17 ++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/architecture/retry.md b/architecture/retry.md index dbb5561..9bf832d 100644 --- a/architecture/retry.md +++ b/architecture/retry.md @@ -16,7 +16,9 @@ async def handler(...) -> ...: ... Two `typing.overload`s back the dual form: called with a function it returns the wrapped function; called with `func=None` (i.e. `@postgres_retry(...)`) it -returns a decorator. The wrapped function keeps its signature via +returns a decorator. The generics are expressed with module-level `typing.ParamSpec`/`TypeVar` +and `TypeAlias` aliases (`_Func`, `_Decorator`) rather than PEP 695 syntax, +so the module parses on Python 3.11+. The wrapped function keeps its signature via `functools.wraps`. `retries` defaults to `None`, which defers to [`settings.get_retries_number()`](settings.md) **at call time** — so the env var is read per invocation, not frozen at decoration. diff --git a/db_retry/retry.py b/db_retry/retry.py index f449fea..2c391f3 100644 --- a/db_retry/retry.py +++ b/db_retry/retry.py @@ -19,23 +19,26 @@ def _log_and_decide(exception: BaseException) -> bool: return False -type _Func[**P, T] = typing.Callable[P, typing.Coroutine[None, None, T]] -type _Decorator[**P, T] = typing.Callable[[_Func[P, T]], _Func[P, T]] +P = typing.ParamSpec("P") +T = typing.TypeVar("T") + +_Func: typing.TypeAlias = typing.Callable[P, typing.Coroutine[None, None, T]] # noqa: UP040 +_Decorator: typing.TypeAlias = typing.Callable[[_Func], _Func] # noqa: UP040 @typing.overload -def postgres_retry[**P, T](func: _Func[P, T], *, retries: int | None = ...) -> _Func[P, T]: ... +def postgres_retry(func: _Func, *, retries: int | None = ...) -> _Func: ... @typing.overload -def postgres_retry[**P, T](func: None = ..., *, retries: int | None = ...) -> _Decorator[P, T]: ... +def postgres_retry(func: None = ..., *, retries: int | None = ...) -> _Decorator: ... -def postgres_retry[**P, T]( - func: _Func[P, T] | None = None, +def postgres_retry( + func: _Func | None = None, *, retries: int | None = None, -) -> _Func[P, T] | _Decorator[P, T]: +) -> _Func | _Decorator: def decorator(f: _Func[P, T]) -> _Func[P, T]: @functools.wraps(f) async def wrapped_method(*args: P.args, **kwargs: P.kwargs) -> T: From f12282b05d3f818d85f27d1f42566f28eea81ba9 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Tue, 30 Jun 2026 14:36:22 +0300 Subject: [PATCH 4/6] chore: lower supported Python floor to 3.11 Co-Authored-By: Claude Sonnet 4.6 --- README.md | 2 +- db_retry/retry.py | 4 ++-- pyproject.toml | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3bdc598..e5dd3c0 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,7 @@ export DB_RETRY_RETRIES_NUMBER=5 ## Requirements -- Python 3.13+ +- Python 3.11+ - SQLAlchemy with asyncio support - asyncpg PostgreSQL driver - tenacity for retry logic diff --git a/db_retry/retry.py b/db_retry/retry.py index 2c391f3..2ac62cf 100644 --- a/db_retry/retry.py +++ b/db_retry/retry.py @@ -22,8 +22,8 @@ def _log_and_decide(exception: BaseException) -> bool: P = typing.ParamSpec("P") T = typing.TypeVar("T") -_Func: typing.TypeAlias = typing.Callable[P, typing.Coroutine[None, None, T]] # noqa: UP040 -_Decorator: typing.TypeAlias = typing.Callable[[_Func], _Func] # noqa: UP040 +_Func: typing.TypeAlias = typing.Callable[P, typing.Coroutine[None, None, T]] +_Decorator: typing.TypeAlias = typing.Callable[[_Func], _Func] @typing.overload diff --git a/pyproject.toml b/pyproject.toml index 3fa9de2..532e4b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,12 +5,14 @@ authors = [ { name = "Artur Shiriev", email = "me@shiriev.ru" }, ] readme = "README.md" -requires-python = ">=3.13,<4" +requires-python = ">=3.11,<4" license = "MIT" keywords = ["postgresql", "sqlalchemy", "retry", "database", "asyncio", "python"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Typing :: Typed", @@ -55,7 +57,7 @@ module-root = "" fix = false unsafe-fixes = true line-length = 120 -target-version = "py313" +target-version = "py311" [tool.ruff.format] docstring-code-format = true From 8e2a90441a5b3e132e58758012e1c6b31aabb441 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Tue, 30 Jun 2026 14:39:39 +0300 Subject: [PATCH 5/6] ci: run pytest across Python 3.11-3.14 --- .github/workflows/_checks.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/_checks.yml b/.github/workflows/_checks.yml index b0b9939..757ab52 100644 --- a/.github/workflows/_checks.yml +++ b/.github/workflows/_checks.yml @@ -17,6 +17,14 @@ jobs: pytest: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.11" + - "3.12" + - "3.13" + - "3.14" services: postgres: image: postgres:latest @@ -34,7 +42,7 @@ jobs: steps: - uses: actions/checkout@v6 - uses: astral-sh/setup-uv@v8.2.0 - - run: uv python install 3.13 + - run: uv python install ${{ matrix.python-version }} - run: | uv sync --all-extras --no-install-project uv run --no-sync pytest . --cov=. --cov-report xml From 31042b905fb7fead64cd746a52be291f7a9e1dc2 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Tue, 30 Jun 2026 15:12:35 +0300 Subject: [PATCH 6/6] ci: pin matrix interpreter with uv python pin uv python install only downloads the interpreter; uv python pin writes .python-version so uv sync/run actually selects it. --- .github/workflows/_checks.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/_checks.yml b/.github/workflows/_checks.yml index 757ab52..f5186f1 100644 --- a/.github/workflows/_checks.yml +++ b/.github/workflows/_checks.yml @@ -13,6 +13,7 @@ jobs: enable-cache: true cache-dependency-glob: "**/pyproject.toml" - run: uv python install 3.13 + - run: uv python pin 3.13 - run: just install lint-ci pytest: @@ -43,6 +44,7 @@ jobs: - uses: actions/checkout@v6 - uses: astral-sh/setup-uv@v8.2.0 - run: uv python install ${{ matrix.python-version }} + - run: uv python pin ${{ matrix.python-version }} - run: | uv sync --all-extras --no-install-project uv run --no-sync pytest . --cov=. --cov-report xml