Skip to content

Commit 031ad56

Browse files
feanilclaude
andauthored
docs: add how-to for managing a uv dependency version matrix (#1436)
* docs: add how-to for managing a uv dependency version matrix Explains how to add, update, and retire dependencies tested against multiple versions (e.g. Django 5.x and 6.x) using uv's conflicts feature with uv-venv-lock-runner in tox. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * temp: Apply suggestions from code review Co-authored-by: Feanil Patel <feanil@axim.org> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 24b4b2c commit 031ad56

1 file changed

Lines changed: 182 additions & 0 deletions

File tree

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
How To Manage a uv Dependency Version Matrix
2+
#############################################
3+
4+
.. tags:: developer, how-to
5+
6+
This how-to explains how to add, update, or retire a dependency that is tested
7+
against multiple versions (e.g. Django 5.x *and* 6.x) in a repository that uses
8+
``uv`` for dependency management.
9+
10+
Assumptions
11+
***********
12+
13+
* Your repository uses ``uv``, ``tox-uv``, and ``uv-venv-lock-runner`` for
14+
dependency management and testing. If it does not, see the
15+
`uv ADR in openedx-proposals`_ for background on the expected setup.
16+
17+
* You understand
18+
`PEP 735 dependency groups`_ and how ``[tool.uv].conflicts`` lets ``uv``
19+
produce a single ``uv.lock`` with independent resolutions for mutually
20+
exclusive groups.
21+
22+
.. _uv ADR in openedx-proposals: https://docs.openedx.org/projects/openedx-proposals/en/latest/best-practices/oep-0067/decisions/backend/0001-uv.html
23+
.. _PEP 735 dependency groups: https://peps.python.org/pep-0735/
24+
25+
Steps: Adding a new version
26+
****************************
27+
28+
Use this process when you want to start testing against a new version while
29+
still keeping the previous one in the matrix (e.g. add Django 6.0 while still
30+
testing Django 5.2).
31+
32+
The ``test`` group always represents the **current default version** — the one
33+
used by quality checks, docs builds, and the primary CI matrix entry. When
34+
promoting a new version to default, the old version moves to its own named
35+
group (e.g. ``django52``).
36+
37+
1. **Update the** ``test`` **group** in ``pyproject.toml`` to the new version:
38+
39+
.. code-block:: toml
40+
41+
# Before
42+
test = [
43+
{include-group = "test-base"},
44+
"Django>=5.0,<6.0",
45+
]
46+
47+
# After
48+
test = [
49+
{include-group = "test-base"},
50+
"Django>=6.0,<7.0",
51+
]
52+
53+
2. **Add a legacy group** for the version being retained:
54+
55+
.. code-block:: toml
56+
57+
django52 = [
58+
{include-group = "test-base"},
59+
"Django>=5.0,<6.0",
60+
]
61+
62+
3. **Register the conflict** so ``uv`` knows the groups are mutually exclusive:
63+
64+
.. code-block:: toml
65+
66+
[tool.uv]
67+
conflicts = [
68+
[
69+
{group = "test"},
70+
{group = "django52"}, # new
71+
],
72+
]
73+
74+
Add the new group to the existing conflict entry — all version groups for
75+
the same dependency belong in a single list.
76+
77+
4. **Update** ``tox.ini`` — add the new legacy factor to ``envlist`` and a
78+
factor conditional to ``dependency_groups``:
79+
80+
.. code-block:: ini
81+
82+
# Before
83+
[tox]
84+
envlist = py312-django{52},quality,docs
85+
86+
[testenv]
87+
runner = uv-venv-lock-runner
88+
dependency_groups =
89+
django52: test
90+
91+
# After
92+
[tox]
93+
envlist = py312-django{52,60},quality,docs
94+
95+
[testenv]
96+
runner = uv-venv-lock-runner
97+
dependency_groups =
98+
django52: django52
99+
django60: test
100+
101+
5. **If the new version requires overriding a global constraint** (for example,
102+
the global edx-lint constraint pins ``Django<6.0`` but you need ``Django<7.0``
103+
to allow 6.x to resolve), add an override to ``[tool.edx_lint].uv_constraints``
104+
in ``pyproject.toml``:
105+
106+
.. code-block:: toml
107+
108+
[tool.edx_lint]
109+
uv_constraints = [
110+
"Django<7.0", # allows Django 6.x; overrides the global Django<6.0 pin
111+
]
112+
113+
6. **Regenerate the lockfile**:
114+
115+
.. code-block:: bash
116+
117+
make upgrade
118+
119+
Steps: Retiring a version
120+
**************************
121+
122+
Use this process when a version reaches end-of-life and you want to drop it
123+
from the matrix entirely.
124+
125+
1. **Remove the legacy group** (e.g. ``django52``) from ``[dependency-groups]``
126+
in ``pyproject.toml``.
127+
128+
2. **Remove it from the** ``conflicts`` **list** in ``[tool.uv]``:
129+
130+
.. code-block:: toml
131+
132+
[tool.uv]
133+
conflicts = [
134+
[
135+
{group = "test"},
136+
{group = "django60"},
137+
],
138+
]
139+
140+
3. **Remove the factor conditional** from ``dependency_groups`` in ``tox.ini``
141+
and drop it from ``envlist``:
142+
143+
.. code-block:: ini
144+
145+
# Before
146+
[tox]
147+
envlist = py312-django{52,60},quality,docs
148+
149+
[testenv]
150+
runner = uv-venv-lock-runner
151+
dependency_groups =
152+
django52: django52
153+
django60: test
154+
155+
# After
156+
[tox]
157+
envlist = py312-django{60},quality,docs
158+
159+
[testenv]
160+
runner = uv-venv-lock-runner
161+
dependency_groups =
162+
django60: test
163+
164+
4. **Regenerate the lockfile**:
165+
166+
.. code-block:: bash
167+
168+
make upgrade
169+
170+
**Maintenance chart**
171+
172+
.. list-table::
173+
:header-rows: 1
174+
175+
* - Review Date
176+
- Working Group Reviewer
177+
- Release
178+
- Test situation
179+
* - 2026-04-27
180+
- @feanil
181+
- main
182+
- Pass

0 commit comments

Comments
 (0)