Skip to content

Derive the distribution version from git tags#275

Merged
Alek99 merged 1 commit into
mainfrom
alek/uv-dynamic-versioning
Jul 25, 2026
Merged

Derive the distribution version from git tags#275
Alek99 merged 1 commit into
mainfrom
alek/uv-dynamic-versioning

Conversation

@Alek99

@Alek99 Alek99 commented Jul 25, 2026

Copy link
Copy Markdown
Member

What

pyproject.toml switches from a static version = "0.0.1" to
dynamic = ["version"], resolved from the latest v* git tag by the
uv-dynamic-versioning
hatchling plugin.

Motivating drift: pyproject said 0.0.1 while v0.0.2 was the live tag. After
this, git tag vX.Y.Z && git push --tags is the whole release action — there is
no second number left to disagree with the tag.

Verified, not assumed

  • CalVer tags are excluded for free. git describe on main returns
    2026.30.1 (a docs-deploy tag, disjoint from library versions by design), but
    dunamai's default pattern requires the v prefix, so the plugin resolves
    0.0.2. No custom pattern needed.
  • Between-tag builds resolve to 0.0.3.dev1+5fc6676 (bump = true) — a
    local segment PyPI rejects, so only a tag produces an uploadable version.
  • sdist → wheel needs no git. The plugin reads PKG-INFO in an unpacked
    sdist; mangling it to 9.9.9 produced a 9.9.9 wheel. So
    pip install xy-X.Y.Z.tar.gz keeps the published version.
  • Installed-wheel smoke: xy.__version__ == "0.0.2", import xy at
    3.5 ms against the 200 ms budget, with importlib.metadata not eagerly
    imported.

The load-bearing part: fetch-depth: 0

actions/checkout defaults to a depth-1 clone carrying no tags, under which the
version resolves to the 0.0.0 fallback silently — the build succeeds and
ships the wrong number rather than failing, invisible until someone reads a PyPI
page. This applies to the tag push that publishes to PyPI.

All 21 checkouts now set fetch-depth: 0, and verify_ci_workflow.py rejects
any workflow that omits it (negative-tested: fires on a shallow checkout, passes
on a deep one).

The docs backend image is the one build that legitimately has no git metadata
(.dockerignore excludes .git), so CI resolves the version on the runner and
passes it in via UV_DYNAMIC_VERSIONING_BYPASS.

Ripple

  • xy.__version__ reads installed distribution metadata instead of a hardcoded
    string, resolved lazily through the existing __getattr__.
  • verify_wheel.py / verify_sdist.py check each artifact against itself
    (filename ↔ .dist-infoMETADATA; sdist root ↔ PKG-INFO) rather than
    reading a pyproject version that no longer exists. The wheel gains a
    .dist-info name check the old comparison did not cover.
  • The release gate drops its tag-vs-pyproject leg (now tautological), keeps
    tag ↔ CHANGELOG, and adds a check that the tag is shaped like a release tag.
  • uv omits the version for dynamic-versioned editable packages, so --frozen
    syncs do not churn per commit. Both lockfiles pass uv lock --check.

Test evidence

  • Full suite: 2267 passed, 4 skipped
  • check_public_api, verify_ci_workflow, ruff check, ruff format --check: clean
  • Real wheel + sdist built and both verifiers pass at the tag-derived 0.0.2

Note for whoever cuts the next release

CHANGELOG.md has no dated ## [0.0.2] entry — only 0.0.1 and
[Unreleased]. That predates this PR, but the release gate will now block the
next tag until a dated heading exists for it.

Summary by CodeRabbit

  • New Features

    • Package versions are now derived from Git release tags, with installed packages reporting their distribution version.
    • Release builds and validation now use artifact and tag versions consistently.
  • Bug Fixes

    • Improved release workflows by ensuring complete repository history and tags are available.
    • Release validation now requires a valid release tag and dated changelog entry.
    • Added safeguards against incorrectly versioned wheels, source archives, and builds.
  • Documentation

    • Updated release procedures and versioning guidance, including changelog and checkout requirements.

pyproject carried `version = "0.0.1"` while `v0.0.2` was the live tag — the
exact drift a hand-maintained version number invites. Switch to
uv-dynamic-versioning: `project.dynamic = ["version"]`, resolved from the
latest `v*` tag, so `git tag vX.Y.Z && git push --tags` is the whole release
action and there is no second number left to disagree with it.

Dunamai's default pattern requires the `v` prefix, which is what keeps the
docs-deploy CalVer tags (2026.WW.N, disjoint from library versions by design)
from being read as releases. `bump = true` versions between-tag builds as
`<next>.devN+<commit>` — a local segment PyPI rejects, so only a tag can
produce an uploadable version. Unpacked sdists resolve from their own
PKG-INFO, so `pip install xy-X.Y.Z.tar.gz` needs no repo.

The load-bearing part is `fetch-depth: 0` on every checkout. actions/checkout
defaults to a depth-1 clone carrying no tags, under which the version resolves
to the 0.0.0 fallback *silently* — a build succeeds and ships the wrong number
rather than failing, invisible until someone reads a PyPI page. verify_ci_workflow
now rejects any workflow that omits it. The docs backend image is the one build
that legitimately has no git metadata (.dockerignore excludes .git), so CI
resolves the version on the runner and passes it in explicitly.

Ripple:

- `xy.__version__` reads installed distribution metadata instead of a hardcoded
  string, resolved lazily through the existing `__getattr__` so importlib.metadata
  stays out of the import-time budget (measured: import xy at 3.5 ms of 200).
- verify_wheel/verify_sdist check each artifact against itself — filename vs
  .dist-info vs METADATA, sdist root vs PKG-INFO — rather than reading a
  pyproject version that no longer exists. The wheel gains a .dist-info name
  check the old comparison did not cover.
- The release gate drops its tag-vs-pyproject leg (now tautological) and keeps
  tag vs CHANGELOG, plus a check that the tag is shaped like a release tag.
- uv omits the version for dynamic-versioned editable packages, so `--frozen`
  syncs do not churn per commit.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Versioning and release pipeline

Layer / File(s) Summary
Dynamic version configuration and propagation
pyproject.toml, python/xy/__init__.py, scripts/check_public_api.py, tests/test_public_api.py, docs/app/dockerfiles/backend.Dockerfile, .github/workflows/_build-docs-images.yml, CHANGELOG.md, spec/process/production-readiness.md
Package versions now derive from Git tags, xy.__version__ reads installed metadata lazily, and documentation image builds receive an explicit version.
Artifact version consistency checks
scripts/verify_sdist.py, scripts/verify_wheel.py, spec/process/production-readiness.md
sdist and wheel validation compares embedded metadata with versions encoded by each artifact’s identifiers.
Release tag and publication flow
scripts/check_release_version.py, .github/workflows/release.yml, tests/test_check_release_version.py
Release validation requires a vX.Y.Z tag and dated changelog entry; Pyodide publishing selects tags from the ref or Git history.
Unshallow checkout enforcement
.github/workflows/*.yml, scripts/verify_ci_workflow.py, tests/test_verify_ci_workflow.py
Relevant workflows fetch full history and tags, while workflow verification rejects checkout steps without fetch-depth: 0.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitTag
  participant GitHubActions
  participant DynamicVersioning
  participant ReleaseGate
  participant Artifact
  GitTag->>GitHubActions: trigger release workflow
  GitHubActions->>DynamicVersioning: provide full history and tags
  DynamicVersioning->>Artifact: derive distribution version
  GitHubActions->>ReleaseGate: validate tag and CHANGELOG.md
  ReleaseGate-->>GitHubActions: allow or reject publication
Loading

Suggested reviewers: carlosabadia

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: switching version derivation to git tags.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alek/uv-dynamic-versioning

Comment @coderabbitai help to get the list of available commands.

@codspeed-hq

codspeed-hq Bot commented Jul 25, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 102 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing alek/uv-dynamic-versioning (e2f902f) with main (63c0697)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/verify_ci_workflow.py (1)

258-258: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Validate the docs workflow’s checkout depth too.

.github/workflows/docs.yml builds the editable package, but this checker only enforces fetch-depth: 0 for CI, CodSpeed, and release. Add docs-workflow validation and a shallow-checkout regression test so its dynamic version cannot silently regress.

Also applies to: 622-622, 668-668

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/verify_ci_workflow.py` at line 258, Update the workflow verification
logic around _require_unshallow_checkouts to validate the docs workflow in
addition to CI, CodSpeed, and release workflows, ensuring docs.yml requires
fetch-depth: 0. Add or extend the shallow-checkout regression test to cover the
docs workflow and its dynamic-version build path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 25-28: Disable persisted checkout credentials by adding
persist-credentials: false to every actions/checkout step alongside fetch-depth:
0: .github/workflows/ci.yml lines 25-28, 61-64, 234-237, 266-269, 370-373,
447-450, 548-551, 603-606, 667-670, and 741-744; .github/workflows/codspeed.yml
lines 28-31; and .github/workflows/docs.yml lines 47-50 and 86-89.

---

Nitpick comments:
In `@scripts/verify_ci_workflow.py`:
- Line 258: Update the workflow verification logic around
_require_unshallow_checkouts to validate the docs workflow in addition to CI,
CodSpeed, and release workflows, ensuring docs.yml requires fetch-depth: 0. Add
or extend the shallow-checkout regression test to cover the docs workflow and
its dynamic-version build path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 09dd2ee7-b606-4cdf-aa21-1d3a14359e53

📥 Commits

Reviewing files that changed from the base of the PR and between 63c0697 and e2f902f.

⛔ Files ignored due to path filters (2)
  • docs/app/uv.lock is excluded by !**/*.lock
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • .github/workflows/_build-docs-images.yml
  • .github/workflows/benchmark-refresh.yml
  • .github/workflows/ci.yml
  • .github/workflows/codspeed.yml
  • .github/workflows/docs.yml
  • .github/workflows/release.yml
  • CHANGELOG.md
  • docs/app/dockerfiles/backend.Dockerfile
  • pyproject.toml
  • python/xy/__init__.py
  • scripts/check_public_api.py
  • scripts/check_release_version.py
  • scripts/verify_ci_workflow.py
  • scripts/verify_sdist.py
  • scripts/verify_wheel.py
  • spec/process/production-readiness.md
  • tests/test_check_release_version.py
  • tests/test_public_api.py
  • tests/test_verify_ci_workflow.py

Comment thread .github/workflows/ci.yml
Comment on lines +25 to +28
with:
# Full history and tags: the distribution version is derived
# from the latest `v*` tag, and a shallow clone has none.
fetch-depth: 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail
for f in .github/workflows/ci.yml .github/workflows/codspeed.yml .github/workflows/docs.yml; do
  echo "### $f"
  wc -l "$f"
done

echo "### checkout/persist/fetch-depth occurrences"
rg -n -C 2 'actions/checkout|persist-credentials|fetch-depth' .github/workflows/ci.yml .github/workflows/codspeed.yml .github/workflows/docs.yml

Repository: reflex-dev/xy

Length of output: 9457


🏁 Script executed:

set -euo pipefail

echo "### .github/workflows/ci.yml"
sed -n '1,90p' .github/workflows/ci.yml
echo
sed -n '220,285p' .github/workflows/ci.yml
echo
sed -n '360,385p' .github/workflows/ci.yml
echo
sed -n '440,460p' .github/workflows/ci.yml
echo
sed -n '540,560p' .github/workflows/ci.yml
echo
sed -n '595,615p' .github/workflows/ci.yml
echo
sed -n '660,680p' .github/workflows/ci.yml
echo
sed -n '735,755p' .github/workflows/ci.yml

echo "### .github/workflows/codspeed.yml"
sed -n '1,60p' .github/workflows/codspeed.yml

echo "### .github/workflows/docs.yml"
sed -n '1,110p' .github/workflows/docs.yml

Repository: reflex-dev/xy

Length of output: 17788


Disable persisted checkout credentials. These actions/checkout steps keep the repo token in git config by default; add persist-credentials: false alongside fetch-depth: 0 in each checkout under .github/workflows/ci.yml, .github/workflows/codspeed.yml, and .github/workflows/docs.yml.

🧰 Tools
🪛 zizmor (1.26.1)

[warning] 24-28: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

📍 Affects 3 files
  • .github/workflows/ci.yml#L25-L28 (this comment)
  • .github/workflows/ci.yml#L61-L64
  • .github/workflows/ci.yml#L234-L237
  • .github/workflows/ci.yml#L266-L269
  • .github/workflows/ci.yml#L370-L373
  • .github/workflows/ci.yml#L447-L450
  • .github/workflows/ci.yml#L548-L551
  • .github/workflows/ci.yml#L603-L606
  • .github/workflows/ci.yml#L667-L670
  • .github/workflows/ci.yml#L741-L744
  • .github/workflows/codspeed.yml#L28-L31
  • .github/workflows/docs.yml#L47-L50
  • .github/workflows/docs.yml#L86-L89
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 25 - 28, Disable persisted checkout
credentials by adding persist-credentials: false to every actions/checkout step
alongside fetch-depth: 0: .github/workflows/ci.yml lines 25-28, 61-64, 234-237,
266-269, 370-373, 447-450, 548-551, 603-606, 667-670, and 741-744;
.github/workflows/codspeed.yml lines 28-31; and .github/workflows/docs.yml lines
47-50 and 86-89.

Source: Linters/SAST tools

@Alek99
Alek99 merged commit 3af5cc5 into main Jul 25, 2026
28 checks passed
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