Skip to content

ci: cover the full supported Python and Django matrix on every PR - #58

Merged
imakecodes merged 2 commits into
mainfrom
ci/cover-full-supported-matrix
Sep 11, 2026
Merged

imakecodes merged 2 commits into
mainfrom
ci/cover-full-supported-matrix

Conversation

@imakecodes

Copy link
Copy Markdown
Member

Why

Unit tests already ran on every pull request, but the matrix had drifted away from what the package actually promises.

pyproject.toml has allowed django>=4.2,<7.0 since dfcfd9f. The CI matrix stopped at Django 5.2. Django 6.0 and 6.1 have been released, so the package has been claiming support for a major series that CI never executed once.

I verified locally before touching the workflow. All four tests pass on Django 6.0.8 and 6.1.1, on every Python those series support:

Python 3.12 Python 3.13 Python 3.14
Django 6.0.8 pass pass pass
Django 6.1.1 pass pass pass

So this is coverage that was missing, not a bug that was hiding.

What changed

The matrix is now the full intersection of what the package declares rather than a hand-maintained subset: each Python in project.classifiers crossed with each Django series the dependency specifier allows, minus the pairs upstream Django does not support. 11 legs become 17.

Python Django series
3.11 4.2, 5.0, 5.1, 5.2
3.12 4.2, 5.0, 5.1, 5.2, 6.0, 6.1
3.13 5.1, 5.2, 6.0, 6.1
3.14 5.2, 6.0, 6.1

The gaps are upstream constraints, not omissions: Django 4.2 and 5.0 do not support Python 3.13, and Django 6.x requires Python 3.12 or newer.

The matrix no longer runs twice per PR. on: push had no branch filter, so every pull request from a same-repo branch started two identical runs. PR #57 showed 27 checks where 14 would have done. push is now limited to main, and superseded PR runs are cancelled. Release runs are never cancelled: cancel-in-progress is gated on the event being a pull_request, so the workflow_call from the publish workflows is unaffected.

Each leg now reports the versions it actually resolved into the job summary. make test-django layers uv run --with <constraint> over the locked environment, so a constraint that silently failed to apply would pass as a false green against whatever uv.lock pins. The step is continue-on-error, purely diagnostic, and can never mask a test result.

New Required Checks job aggregates the workflow into one stable check name. Branch protection should require that rather than 17 job names that change whenever a series is added or dropped, because a required check that no longer exists stops being enforced silently.

Added workflow_dispatch for manual runs, and timeout-minutes on every job.

Supporting changes

  • Framework :: Django :: 4.2 through 6.1 classifiers, so the tested range is visible on PyPI. All six verified against the current trove list.
  • README.md said Django >=4.2,<6.0, which was stale in both directions. Now <7.0, with a pointer to the matrix.
  • docs/development.md and CLAUDE.md record the rule that caused this: widening requires-python, the django specifier, or the classifiers without adding the matching legs ships an untested claim of support.
  • Removed five em dashes I left in .claude/commands/ in docs: add CLAUDE.md and shared .claude configuration #57, and pointed /extend-generator at both ends of the range instead of only the oldest.

Verification

make ci passes locally: Ruff clean, 4 tests, wheel and sdist build. Wheel metadata confirms Requires-Dist: django<7.0,>=4.2 alongside the new classifiers.

Follow-ups not in this PR

  • Dependabot only watches github-actions. Adding the uv ecosystem would surface new Django series as PRs, which is the mechanism that would have caught this drift earlier.
  • The suite only runs on SQLite, while get_db_type() branches on five backends and builder.py imports django.contrib.postgres.indexes.HashIndex. A PostgreSQL leg is a separate axis worth its own PR.

🤖 Generated with Claude Code

Michel Wilhelm and others added 2 commits September 11, 2026 11:59
pyproject.toml has allowed django<7.0 since dfcfd9f, but the CI matrix
stopped at Django 5.2, so Django 6.0 and 6.1 were an untested claim of
support. Verified locally that all four tests pass on 6.0.8 and 6.1.1
across Python 3.12, 3.13 and 3.14, then added the six missing legs.

The matrix is now the full intersection of what the package declares:
each Python in project.classifiers crossed with each Django series the
dependency specifier allows, minus the pairs upstream Django rejects.
That is 17 legs, Django 4.2 to 6.1 on Python 3.11 to 3.14.

Also stop running the whole matrix twice per pull request. An unfiltered
push trigger alongside pull_request meant every same-repo PR started two
identical runs; push is now limited to main, and superseded PR runs are
cancelled. Release runs are never cancelled, since cancel-in-progress is
gated on the event being a pull_request.

Each leg now prints the Django and Python it actually resolved into the
job summary. make test-django layers uv run --with over the locked
environment, so a constraint that silently failed to apply would pass as
a false green against whatever uv.lock pins.

Add a Required Checks job that aggregates the workflow into one stable
check name for branch protection, because matrix job names change
whenever a series is added or dropped.

Declare the tested range as Framework :: Django classifiers, correct the
stale django<6.0 line in the README, and document the rule that widening
the declared support without widening the matrix is what caused this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GitHub Actions expressions accept single quotes only as string
delimiters, so join(needs.*.result, " ") made the whole workflow file
invalid. The run failed at startup with zero jobs and the pull_request
run never materialized.

Verified with actionlint across every workflow in .github/workflows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@imakecodes
imakecodes merged commit 3ca2eb1 into main Sep 11, 2026
21 checks passed
@imakecodes
imakecodes deleted the ci/cover-full-supported-matrix branch September 11, 2026 15:03
@imakecodes imakecodes mentioned this pull request Sep 11, 2026
imakecodes added a commit that referenced this pull request Sep 11, 2026
Minor rather than patch: #60 changes the generated output for every user.
A relation column is now typed by the column it points at, so a foreign
key to a BigAutoField primary key renders as `big_auto` where it used to
render as `foreign_key`. No API or CLI break, but anyone versioning a
.dbml file will see a diff.

Shipping since 1.1.2:

- #57 CLAUDE.md and shared .claude configuration
- #58 CI covers the full declared support matrix, Django 4.2 to 6.1 on
      Python 3.11 to 3.14, plus Framework :: Django classifiers so the
      tested range is visible on PyPI
- #59 command surface and relation invariants under test, 89% to 99%
- #60 relation columns typed by their target (closes #38)

Regenerate uv.lock alongside the version. The package is a member of its
own workspace, so uv.lock records project.version and `uv sync --locked`
fails without it. That would have broken the release workflow at the
sync step, before it built anything.

Drop the hardcoded version from the release example in CONTRIBUTING.md;
it went stale on every release.

Co-authored-by: Michel Wilhelm <michel@revealhealth.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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