Skip to content

Keep uv.lock's own version in step with pyproject on release - #2129

Draft
MaxGhenis wants to merge 5 commits into
mainfrom
uv-lock-version-in-step
Draft

MaxGhenis wants to merge 5 commits into
mainfrom
uv-lock-version-in-step

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #2128 (step 1 of 2).

What this does

uv.lock records this package's own version. The versioning workflow bumps pyproject.toml but not the lock, so the lock has drifted: 2.125.1 against 2.126.2 on main (2026-10-10; it was 2.109.5 against 2.113.0 when this PR opened). uv sync --locked rejects a lock in that state. The follow-up PR moves CI to uv sync --locked, so the lock has to stay in step with pyproject.toml.

  • .github/bump_version.py now also rewrites the [[package]] name = "policyengine-uk" entry's version in uv.lock, and only when that entry is the editable project (source = { editable = "." }). It touches that one line and nothing else. It updates the lock before pyproject.toml, so if the lock is missing, or has no such entry or more than one, it exits non-zero before changing either file.
  • versioning.yaml runs uv lock --check after the bump and before committing, so the versioning job refuses to push its release commit with a lock that uv sync --locked would reject. (A PR can still merge a dependency change without uv lock; the follow-up's --locked CI is what stops that.)
  • This PR doesn't edit uv.lock. The first version bump after merge fixes main's stale line. Editing that line here would race main's own bumps, which land on almost every merge.

PR CI is unchanged here, and so is the Publish job. Publish builds with python -m build in an isolated environment, so the lock doesn't affect release artifacts.

Invariants (tested)

For any lock text containing exactly one entry for this package, set_lock_version(text, pkg, v):

  • sets that entry's version to v;
  • leaves every other package's version unchanged, including dependencies that share the old version string and names that share a prefix (policyengine-uk-data);
  • changes exactly one line, so the line count is preserved;
  • is idempotent, and reverses exactly when applied with the old version;
  • raises when the entry is missing, repeated, or not the editable project.

These are checked by policyengine_uk/tests/code_health/test_version_bump.py:

  • Hypothesis property tests over generated locks;
  • example tests;
  • a differential test on the real uv.lock, which compares the regex rewrite with a tomllib parse;
  • tests that run the real script on a scratch repository: both files move together from a stale lock, and a missing lock or one without this package leaves both files unchanged.

The tests also fail on each of these mutations: a naive find-and-replace; a rewrite that leaves the lock stale; dropping main()'s update_lock call; writing pyproject.toml before the lock; dropping the editable-entry check.

Verified locally

  • With only the root version line corrected, uv lock --check passes on uv 0.10.0, 0.11.7, 0.12.13 and 0.12.23 (latest, verified on PyPI).
  • Running the new bump_version.py on a copy of the repo bumps 2.113.0 → 2.113.1 in both files, and uv lock --check passes afterwards.

Review: GPT-6.1 Sol, APPROVE, no must-fix (~/reviews/uk-weekly-uv-lock/review-2129.md); its three nits are addressed in d75ce69.

Re-verified 2026-10-10, after merging main (3173baa)

  • With only the root version line corrected, uv lock --check passes on main's lock under uv 0.13.0 (latest, per the astral-sh/uv releases API).
  • Running bump_version.py on the merged branch bumps 2.126.2 → 2.126.3 in pyproject.toml and in the lock's own entry (one line), and uv lock --check passes afterwards.
  • test_version_bump.py: 9 passed (5 at first, then the 4 added after review), repeated runs. One property test had failed Hypothesis's too_slow health check on this loaded host (input generation, not the rewrite), so it now suppresses that check (16e9eee).

axiom: n/a: CI and release tooling only

🤖 Generated with Claude Code

Reproducer: policyengine_uk/tests/code_health/test_version_bump.py::test_rewriting_the_real_lock_changes_only_this_package, policyengine_uk/tests/code_health/test_version_bump.py::test_a_dependency_sharing_the_old_version_is_left_alone, policyengine_uk/tests/code_health/test_version_bump.py::test_a_missing_or_repeated_entry_is_an_error, policyengine_uk/tests/code_health/test_version_bump.py::test_rewrite_sets_only_this_package_and_is_idempotent, policyengine_uk/tests/code_health/test_version_bump.py::test_main_moves_pyproject_and_lock_together, policyengine_uk/tests/code_health/test_version_bump.py::test_main_stops_before_changing_anything

bump_version.py also rewrites this package's entry in uv.lock, so CI can
install with uv sync --locked. The versioning job runs uv lock --check before
committing. Tests cover the rewrite with Hypothesis properties, examples and
a differential check against the real lock.

Part of #2128.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis force-pushed the uv-lock-version-in-step branch from 99c9202 to 96a8a90 Compare October 4, 2026 02:41
MaxGhenis and others added 4 commits October 10, 2026 17:24
On a loaded runner Hypothesis can take over a second to draw the first
set of unique package names and fails the too_slow health check, which
says nothing about the rewrite under test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lock is now updated before pyproject.toml, a missing lock is an error,
and only the editable project entry is rewritten. New tests run the real
script on a scratch repository: both files move together, and a missing
lock or a lock without this package leaves both unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 11, 2026
#2129 makes bump_version.py move uv.lock's own version too and stop when
the lock is missing; the end-to-end bump tests then still pass.

Refs #2314

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Coordination note from #2324 (untyped changelog fragments, #2314), which also edits .github/bump_version.py.

Merging the two gives one conflict, in bump_version.py's imports: keep from dataclasses import dataclass alongside your import tomllib. main() merges cleanly; your update_lock(...) call lands after #2324's config loading.

One of your tests then fails. #2324 makes bump_version.py read the fragment types from [tool.towncrier] and stop when there is no such table, and the pyproject.toml in test_version_bump.py's run_bump has none. Add a minimal table:

     (tmp_path / "pyproject.toml").write_text(
-        f'[project]\nname = "{PACKAGE}"\nversion = "1.2.3"\n'
+        f'[project]\nname = "{PACKAGE}"\nversion = "1.2.3"\n\n'
+        '[tool.towncrier]\ndirectory = "changelog.d"\n\n'
+        '[[tool.towncrier.type]]\ndirectory = "fixed"\nname = "Fixed"\n'
     )

On a local merge of both branches with that change, all 56 tests in test_version_bump.py and test_changelog_fragments.py pass. #2324's own end-to-end bump tests already copy uv.lock, so they pass with this PR's lock step. Patch file: ~/reviews/uk-changelog-fragment-check-2026-10-11/pr2129-test-fixture.patch.

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
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