How To Manage a uv Dependency Version Matrix#
This how-to explains how to add, update, or retire a dependency that is tested
against multiple versions (e.g. Django 5.x and 6.x) in a repository that uses
uv for dependency management.
Assumptions#
Your repository uses
uv,tox-uv, anduv-venv-lock-runnerfor dependency management and testing. If it does not, see the uv ADR in openedx-proposals for background on the expected setup.You understand PEP 735 dependency groups and how
[tool.uv].conflictsletsuvproduce a singleuv.lockwith independent resolutions for mutually exclusive groups.
Steps: Adding a new version#
Use this process when you want to start testing against a new version while still keeping the previous one in the matrix (e.g. add Django 6.0 while still testing Django 5.2).
The test group always represents the current default version — the one
used by quality checks, docs builds, and the primary CI matrix entry. When
promoting a new version to default, the old version moves to its own named
group (e.g. django52).
Update the
testgroup inpyproject.tomlto the new version:# Before test = [ {include-group = "test-base"}, "Django>=5.0,<6.0", ] # After test = [ {include-group = "test-base"}, "Django>=6.0,<7.0", ]
Add a legacy group for the version being retained:
django52 = [ {include-group = "test-base"}, "Django>=5.0,<6.0", ]
Register the conflict so
uvknows the groups are mutually exclusive:[tool.uv] conflicts = [ [ {group = "test"}, {group = "django52"}, # new ], ]
Add the new group to the existing conflict entry — all version groups for the same dependency belong in a single list.
Update
tox.ini— add the new legacy factor toenvlistand a factor conditional todependency_groups:# Before [tox] envlist = py312-django{52},quality,docs [testenv] runner = uv-venv-lock-runner dependency_groups = django52: test # After [tox] envlist = py312-django{52,60},quality,docs [testenv] runner = uv-venv-lock-runner dependency_groups = django52: django52 django60: test
If the new version requires overriding a global constraint (for example, the global edx-lint constraint pins
Django<6.0but you needDjango<7.0to allow 6.x to resolve), add an override to[tool.edx_lint].uv_constraintsinpyproject.toml:[tool.edx_lint] uv_constraints = [ "Django<7.0", # allows Django 6.x; overrides the global Django<6.0 pin ]
Regenerate the lockfile:
make upgrade
Steps: Retiring a version#
Use this process when a version reaches end-of-life and you want to drop it from the matrix entirely.
Remove the legacy group (e.g.
django52) from[dependency-groups]inpyproject.toml.Remove it from the
conflictslist in[tool.uv]:[tool.uv] conflicts = [ [ {group = "test"}, {group = "django60"}, ], ]
Remove the factor conditional from
dependency_groupsintox.iniand drop it fromenvlist:# Before [tox] envlist = py312-django{52,60},quality,docs [testenv] runner = uv-venv-lock-runner dependency_groups = django52: django52 django60: test # After [tox] envlist = py312-django{60},quality,docs [testenv] runner = uv-venv-lock-runner dependency_groups = django60: test
Regenerate the lockfile:
make upgrade
Maintenance chart
Review Date |
Working Group Reviewer |
Release |
Test situation |
|---|---|---|---|
2026-04-27 |
@feanil |
main |
Pass |