Upgrade one or more Python packages with uv, by assessing the real impact of the upgrade on the project
before applying it, then validating it through tests. $ARGUMENTS = the packages to upgrade (and, if specified, the
target version of each).
The risk is the breaking change that goes unnoticed. This skill therefore enforces the order:
- understand what changes
- check whether the project uses it
- apply
- prove that it holds.
Workflow
Copy this checklist into the response and tick it off as you go:
- [ ] 1. Cadrer : lister les paquets, version courante → cible, pin exact vs plage
- [ ] 2. Changelogs : prévisualiser les co-montées, puis lire les entrées entre courant et cible, extraire les cassures
- [ ] 3. Impact : confronter chaque changement cassant à l'usage réel dans le code
- [ ] 4. Décision : impact → corriger dans la foulée ou s'arrêter ; sinon continuer
- [ ] 5. Appliquer : la montée via uv
- [ ] 6. Lock : git diff uv.lock — vérifier ce qui a bougé
- [ ] 7. Tests : suite de tests + test de démarrage si binaire de service
- [ ] 8. Audit : uv audit --frozen (si la montée visait une CVE)
- [ ] 9. Rapport : synthèse + indexer pyproject.toml et uv.lock (ne pas commiter)
Steps 2 to 4 are repeated per package ; step 5 applies them together.
Delegation. When several packages are to be analyzed (requested + sensitive co-upgrades identified by the dry-run),
launch in a single message a subagent Agent (subagent_type=general-purpose) per package, each with the
prompt references/analyse-paquet.md filled in (package, versions, project root,
role). Each agent returns the compact table defined in the prompt — never a massive copy-paste. A single package
with no co-upgrade → do steps 2–3 inline, without a subagent. In all cases, step 4 and the following ones remain
in the main conversation.
1. Frame
First gather the information that will be used later.
- Current version : read
pyproject.toml(constraint) anduv.lock(locked version). If in doubt:uv tree --package <pkg>. - Target version : from
$ARGUMENTS. Not specified or "latest" → latest stable. - Form of the constraint : exact pin (
pkg==X) or range (pkg>=X,>=X,<Y) — this changes the mechanics (step 5).
2. Changelogs
First, list the co-upgrades. An upgrade often pulls in other packages (raised minimum bounds, shared dependencies). Preview the resolution before any reading of changelogs:
- exact pin:
uv add --dry-run 'pkg==Y'(with extras if any) ; - range:
uv lock --dry-run --upgrade-package pkg.
Identify all the packages that would move. Each co-upgrade of a sensitive package (server, worker, ORM, crypto, serialization, any package imported by the code) follows steps 2 to 4 just like the requested packages — not just a check afterwards.
For each package, read only the entries strictly between the current version (excluded) and the target (included). For a jump of several major versions, accumulate the breakages of each stage.
- Find the source: PyPI page
https://pypi.org/project/<pkg>/→ project links → GitHub Releases,CHANGELOG.md/CHANGES.rst.WebFetchon these URLs;WebSearch "<pkg> <cible> changelog breaking changes"as a complement. - Identify and note: Breaking / Removed / Deprecated / Backwards incompatible, changed default values, modified signatures, increase in the minimum required Python version.
- Ignore the rest (features, performance, internal fixes): only what can break the project matters here.
3. Impact on the project
For each breaking change identified, check whether the project actually uses the affected API — do not assume.
git grep -n "<symbole|import|api>"on the code (imports, calls, subclasses).- Do not forget: config files, CLI options, configuration keys, entry points /
[project.scripts]. - Classify each break: affected (to be handled) or not affected (no effect here).
4. Decision
- No impact → continue.
- Impact → never upgrade the version while breaking things silently. Two options, depending on the scope:
- small and directly related → fix the code in the same task;
- large or uncertain → stop and ask how to proceed (
AskUserQuestion).
5. Apply (uv mechanics)
- Exact pin
pkg==X— recommended:uv add 'pkg==Y'(updates the constraint inpyproject.toml, relocks and synchronizes in a single command). Keep the extras:uv add 'pkg[extra]==Y'. Several at once:uv add 'a==1' 'b==2'. - Range
pkg>=X— upgrade within the range without touching the constraint:uv lock --upgrade-package pkgthenuv sync. To go beyond the upper bound: edit the constraint (oruv add) then relock. - Do not run
uv lock --upgrade(without--package) unless explicitly requested: it upgrades the entire graph.
6. Check the lock
git diff uv.lock : confirm that the diff matches the co-upgrades planned in step 2 — nothing more. Any package
that moves without having been planned goes back to step 2.
7. Tests + smoke test
- Run the test suite of the project following the best practices defined in the project.
- Target first the modules that use the package, then expand.
- If the package provides a service binary (WSGI/ASGI server, worker, CLI), add a smoke test, a successful import does not prove that the service starts.
8. Audit (if CVE)
When the upgrade was aimed at a vulnerability: uv audit --frozen and confirm that the package has left the report.
9. Report
Compact summary, one table per batch:
| Package | Current → Target | Breaking changes | Project impact | Tests |
|---|
Then index pyproject.toml and uv.lock (git add). Do not commit : it is the maintainer's decision;
one upgrade (or one coherent batch) = one commit.
Safeguards
- The order changelog → impact → application is not negotiable: applying first means discovering the breakage in production.
- A silent upgrade that breaks a used API is a failure, not a time saver.
- Never upgrade the entire graph (
uv lock --upgrade) for convenience.