Skip to content

ci: PR semver gate (griffe) — enforce version bump on breaking API changes#205

Merged
martinkersner merged 1 commit into
mainfrom
ci/semver-check
Jul 15, 2026
Merged

ci: PR semver gate (griffe) — enforce version bump on breaking API changes#205
martinkersner merged 1 commit into
mainfrom
ci/semver-check

Conversation

@martinkersner

Copy link
Copy Markdown
Member

Summary

Adds a PR-only "semver check" CI gate — the Python analog of the Rust sibling repo's cargo-semver-checks job. On every pull request it diffs the public API of the datamaxi package between the PR base commit and the PR head, and fails when the version bump in datamaxi/__version__.py is smaller than the change requires.

Why: several public modules (_endpoints.py, _ws_endpoints.py, _ws_models.py) are codegen-generated and churn often. Rather than block that churn, we enforce that the version bump matches the churn.

How it maps to the Rust cargo-semver-checks gate

  • diff public API against the PR base commit → here via griffe check (the standard Python public-API diff tool; static/AST-based)
  • derive the expected bump from the version and fail if the change exceeds what the bump allows → here via .github/scripts/check_semver.py

0.x-aware policy (future-proof)

  • pre-1.0 (base major 0): breaking change ⇒ at least a minor bump (0.29.0 → 0.30.0)
  • >=1.0: breaking change ⇒ major bump
  • version going backwards ⇒ always fail
  • no breaking changes + non-decreasing version ⇒ pass (we don't force a bump for additive-only changes, which griffe check doesn't surface)

Exact griffe command

python -m griffe check datamaxi -a <base.sha> -f oneline

-a = old ref (PR base), default -b = working tree (PR head). Exit 1 = breaking changes; the script also verifies the base ref up front and detects griffe tracebacks so a git error is never mistaken for a breakage. griffe checks out the base ref via an internal git worktree → workflow uses fetch-depth: 0.

Files

  • .github/workflows/semver.ymlon: pull_request, permissions: contents: read, checkout@v4 + fetch-depth: 0, setup-uv@v6, uv pip install --system 'griffe>=2,<3', runs the script with ${{ github.event.pull_request.base.sha }}.
  • .github/scripts/check_semver.py — stdlib + griffe only; black/flake8-clean under the repo config.

Tested locally (griffe 2.1.0, offline, no API key)

Scenario Base → Head Result
Breaking + adequate minor bump v0.28.0 (0.26.3) → wt 0.29.0 PASS exit 0
No API change HEAD → wt (identical) PASS exit 0
Breaking + only patch bump 0.26.3 → 0.26.4 FAIL exit 1, prints breakages + suggests 0.27.0
Version decrease 0.26.3 → 0.26.2 FAIL exit 1, "version went backwards"
Invalid base ref deadbeef123 TOOL ERROR exit 2, hints fetch-depth: 0

Confirmed griffe analyzes the flat-layout datamaxi package (including aio/ and ws/) with neither httpx nor websockets installed — no optional deps needed, no stderr warnings.

Caveats / limitations

  • griffe flags codegen default-value churn (e.g. Attribute value was changed) and param moves/removals as breaking — this is intentional: such PRs must carry a minor bump. Expect the gate to require a version bump on most _endpoints.py/WS-sync PRs.
  • Additive-only changes are not detected by griffe check; the gate only enforces on breaking changes + never-decreasing version (it won't force a patch bump for pure additions).
  • 0.x-aware; flips to major-on-breaking automatically once base version reaches ≥1.0.

@martinkersner
martinkersner marked this pull request as ready for review July 15, 2026 12:50
@martinkersner
martinkersner merged commit ac1ef09 into main Jul 15, 2026
6 checks passed
@martinkersner
martinkersner deleted the ci/semver-check branch July 15, 2026 12:50
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