Skip to content

chore(deps): move to uv and bump every dependency in one pass - #110

Merged
KRoperUK merged 1 commit into
mainfrom
chore/bump-dependencies
Sep 17, 2026
Merged

KRoperUK merged 1 commit into
mainfrom
chore/bump-dependencies

Conversation

@KRoperUK

Copy link
Copy Markdown
Owner

Replaces Poetry with uv, and folds the five open dependabot PRs into the single lock regeneration that migration requires.

Why one PR and not two

Bumping dependencies while still on Poetry wasn't possible here: uv can't consume poetry.lock, so moving to uv is the prerequisite for the bump, and uv lock resolves the newest allowed versions in the same step. Merging the five dependabot PRs separately would also have left the lock inconsistent with pyproject.toml between merges.

(For the record: your local poetry is broken — its venv points at a Homebrew python that's since been upgraded away. That's incidental, but it's why I couldn't regenerate poetry.lock even if that were the goal.)

Every open dependabot PR, resolved in one pass

Package From To
aiohttp 3.14.1 3.14.3
pydantic 2.13.4 2.13.5
pytest 8.4.2 9.1.1 major
pytest-cov 6.3.0 7.1.0 major
pre-commit 3.8.0 4.6.2 major
twine 6.2.0 7.0.0 major
python-dotenv 1.2.2 1.2.3
ruff 0.15.21 0.16.7
mypy 1.20.2 2.3.1 major

mypy wasn't one of the five — it was queued behind open-pull-requests-limit: 5. I included it because "all the majors" should mean all of them, and strict mypy already passed on 2.3.1 and 1.20.2, so it's not a gamble. Say the word if you'd rather I drop it.

Once this merges, dependabot should close its five PRs on its next run, since the versions are satisfied.

Packaging: PEP 621 + uv

[tool.poetry] → standard [project]; dev deps → PEP 735 [dependency-groups].dev; build backend → uv_build; uv.lock replaces poetry.lock.

Release automation keeps working — I checked release-please's Python strategy before committing:

const pyProject = parsedPyProject?.project || parsedPyProject?.tool?.poetry;

It reads [project] first, so version bumps need no config change.

Artefacts verified, as #202 asked

I downloaded the poetry-built 0.13.1 from PyPI and diffed it against uv build:

  • same module set, same py.typed, same console_scripts entry point, same bundled LICENSE, same sdist file list (and no stray uv.lock/.venv in the sdist);
  • the only substantive differences are PEP 639 metadata: License-Expression: MIT replaces License: MIT, and the deprecated Classifier: License :: OSI Approved :: MIT License is gone — uv warns about it on every build. If you'd rather keep byte-identical metadata I can put it back;
  • dependency specifiers restate the same caret semantics in PEP 440 form (>=3.14.3,<4 for what ^3.14.3 meant), so consumers see no surprise.

ruff is now pinned in exactly one place

This one matters. The previous arrangement pinned ruff twice — ruff-pre-commit's rev: in .pre-commit-config.yaml, and the version in poetry.lock that CI installed. Dependabot can only bump one file per PR, so a ruff bump would have half-updated and CI would silently lint with a different version than contributors get. That is exactly the arrangement that made every ruff bump unmergeable in the integration repo until its second pin was removed (#201, #213). The hooks are now local with language: system and resolve ruff from the project environment, so uv.lock is the only source.

That required restricting types_or to python, pyi: ruff 0.16's ruff-format hook also claims markdown, and your Python blocks in README/docs are hand-aligned on purpose — a naive rev bump would have reflowed the temperature-constant table in docs/api-reference.md. [tool.ruff] extend-exclude = ["*.md"] keeps a bare ruff format . in step with the hooks.

CI

All three jobs use astral-sh/setup-uv (SHA-pinned) and uv sync --frozen. The Poetry install steps, the hand-rolled venv cache, and both "read the version out of the lock file" greps are gone — uv run resolves the tools from uv.lock directly. The test matrix gains 3.14, which the classifiers already claimed.

--frozen, deliberately not --locked — and this is worth knowing before someone "fixes" it:

uv records the project's own version inside uv.lock. release-please bumps version in pyproject.toml without touching uv.lock, so --locked fails on every release PR and leaves main's lock stale until someone refreshes it. I verified this: a version bump alone makes uv lock --check report "The lockfile needs to be updated". --frozen installs exactly what the lock pins without comparing it to pyproject.toml. A dependency added without re-locking still fails loudly, as a missing import.

Dependabot

package-ecosystem: uv (confirmed valid — Dependabot::Uv::PackageManager::ECOSYSTEM = "uv"), and the group no longer filters on update-types, so majors and minors arrive as one PR instead of a queue of five. Re-adding update-types: [minor, patch] splits majors back out.

Docs

CONTRIBUTING, README and docs/ now describe uv sync / uv run. This also fixes instructions that were already broken: pip install -e ".[dev]" referred to an extra that has never existed, since the dev dependencies were a Poetry group.

Verification

uv lock --check consistent · 196 tests pass on Python 3.14.2 · ruff check + format --check clean repo-wide · mypy strict clean · pre-commit run --all-files passes with the new hooks · uv build produces both artefacts.

Two notes for you: your existing .venv was built by hand (ruff 0.14.14, mypy 2.3.1 — not from any lock), so run uv sync after merging to reconcile it; and I validated in a throwaway environment rather than touching it.

Replaces Poetry with uv, and folds the five open dependabot PRs into the single
lock regeneration that migration requires.

## Why one commit and not two

Bumping dependencies while still on Poetry was not possible here: the local Poetry
install is broken (its venv points at a Homebrew python that has since been
upgraded away), and uv cannot consume poetry.lock — so moving to uv is the
prerequisite for the bump, and `uv lock` resolves to the newest allowed versions in
the same step. Merging each dependabot PR separately would also have left the lock
inconsistent with pyproject.toml between merges.

## Packaging: PEP 621 + uv

`[tool.poetry]` becomes a standard `[project]` table, the dev dependencies become a
PEP 735 `[dependency-groups].dev`, and the build backend is `uv_build` rather than
poetry-core. `uv.lock` replaces `poetry.lock`.

Release automation keeps working: release-please's Python strategy reads
`parsedPyProject.project || parsedPyProject.tool.poetry`, so it bumps the version in
a PEP 621 `[project]` table without a config change.

## Artefacts verified before touching the publish path

#202 asked for this specifically, so the uv-built wheel and sdist were diffed
against the poetry-built 0.13.1 downloaded from PyPI:

* same module set, same `py.typed`, same `console_scripts` entry point, same
  bundled LICENSE, same sdist file list;
* the only substantive differences are PEP 639 metadata — `License-Expression: MIT`
  replaces `License: MIT`, and the now-deprecated `Classifier: License :: OSI
  Approved :: MIT License` is gone (uv warns about it on every build);
* dependency specifiers restate the same caret semantics in PEP 440 form
  (`>=3.14.3,<4` where `^3.14.3` was meant), so consumers see no surprise.

## Every open dependabot PR, resolved

| Package | From | To |
| --- | --- | --- |
| aiohttp | 3.14.1 | 3.14.3 |
| pydantic | 2.13.4 | 2.13.5 |
| pytest | 8.4.2 | **9.1.1** (major) |
| pytest-cov | 6.3.0 | **7.1.0** (major) |
| pre-commit | 3.8.0 | **4.6.2** (major) |
| twine | 6.2.0 | **7.0.0** (major) |
| python-dotenv | 1.2.2 | 1.2.3 |
| ruff | 0.15.21 | 0.16.7 |
| mypy | 1.20.2 | **2.3.1** (major) |

mypy was not among the five open PRs — it was queued behind
`open-pull-requests-limit: 5`. It is included because "all the majors" should mean
all of them, and strict mypy was already verified to pass on 2.3.1 as well as
1.20.2.

## ruff is now pinned in exactly one place

The previous arrangement pinned ruff twice: ruff-pre-commit's `rev:` in
.pre-commit-config.yaml, and the version in poetry.lock that CI installed. Those are
two pins dependabot can only ever bump one of, which is precisely what made every
ruff bump unmergeable in the integration repo until its second pin was removed
(dimplex-controller-hass#201, #213). The hooks are now local and `language: system`,
resolving ruff from the project environment, so uv.lock is the single source.

That also required restricting `types_or` to `python, pyi`: ruff 0.16's ruff-format
hook additionally claims `markdown`, and the Python blocks in README.md and docs/
are hand-aligned on purpose (see the temperature-constant table in
docs/api-reference.md). `[tool.ruff] extend-exclude = ["*.md"]` keeps a bare
`ruff format .` in step with the hooks.

## CI

All three jobs use `astral-sh/setup-uv` and `uv sync --frozen`; the Poetry install
steps, the hand-rolled venv cache and the two "read the version out of the lock
file" greps are gone, because `uv run` resolves the tools from uv.lock directly.
The test matrix gains 3.14, which the package's classifiers already claimed.

`--frozen`, deliberately not `--locked`: uv records the project's own version inside
uv.lock, so release-please bumping `version` in pyproject.toml makes the lock
"stale" — `--locked` would fail on every release PR and leave main's lock stale
until someone refreshed it. `--frozen` installs exactly what the lock pins without
comparing it to pyproject.toml. A dependency added without re-locking still fails
loudly, as a missing import.

## Dependabot

`package-ecosystem: uv`, and the group no longer filters on `update-types`, so
majors and minors arrive together as one PR instead of a queue of five. Re-adding
`update-types: [minor, patch]` splits majors out again if that is ever preferred.

## Docs

CONTRIBUTING, README and docs/ now describe `uv sync` / `uv run`. That also fixes
instructions that were already broken: `pip install -e ".[dev]"` referred to an
extra that never existed, since the dev dependencies were a Poetry group.

Verified: 196 tests pass (Python 3.14.2), ruff check and format clean repo-wide,
mypy strict clean, pre-commit passes with the new hooks, `uv build` produces both
artefacts, and `uv lock --check` reports the lock consistent.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
@KRoperUK
KRoperUK merged commit c01160a into main Sep 17, 2026
10 checks passed
@KRoperUK
KRoperUK deleted the chore/bump-dependencies branch September 17, 2026 21:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant