Dependency groups¶
The template uses PEP 735 dependency groups (uv's first-class support for them) to separate dev-time tooling from runtime requirements. Each group corresponds to one workflow task family.
Runtime¶
The [project.dependencies] list in pyproject.toml. These ship in the published wheel. Add via:
uv add <package>
Dev-time groups¶
Listed under [dependency-groups] in pyproject.toml. They're installed by develop.bootstrap and on demand by individual workflow tasks, but never end up in the wheel.
| Group | Packages | Used by |
|---|---|---|
dev |
Aggregator that pulls in every group below. | Default uv sync target; convenient for IDE setup. |
develop |
pre-commit, commitizen, tomlkit |
./workflow.cmd develop.*, release.* (commitizen). |
lint |
ruff, pylint, ty, complexipy |
./workflow.cmd lint and format. |
test |
pytest, pytest-cov, pytest-xdist, pytest-env, pytest-metadata, pytest-html, coverage, tox, tox-uv |
./workflow.cmd test. |
document |
properdocs, mkdocstrings[python], mkdocs-include-markdown-plugin |
./workflow.cmd document. |
quality |
pyscn |
./workflow.cmd quality. |
security |
pip-audit, cyclonedx-py |
./workflow.cmd secure. |
Adding to a group¶
uv add --group <group-name> <package>
This updates both pyproject.toml and uv.lock. Commit both. See How-to: add a dependency for the full flow.
Why everything is pinned, and what bounds the rest¶
Every entry in every group is pinned to an exact version, and uv.lock records the whole
transitive closure. CI installs from the lock, so a resolution only changes when a commit
changes it.
This project departs from the template here. The template also stamps a fixed
[tool.uv] exclude-newer date that makes uv refuse anything published later, and expects
upgrades to happen by moving that date and re-resolving by hand. This repository is instead
maintained by Dependabot and an agent (see AGENTS.md), and a fixed
date would make every Dependabot lock update fail rather than wait. The seven-day cooldown
the date provided lives in .github/dependabot.yml (cooldown: default-days: 7), which
gates what is proposed: a release has to have been public for a week before Dependabot
opens a PR for it.
So upgrades still happen deliberately, one PR at a time:
- Dependabot opens PRs every Monday: one grouped PR for development tooling, one per runtime
dependency. Non-major tooling and GitHub Actions updates merge automatically when CI is
green; the rest is labelled
agent-reviewand reviewed. - Transitive dependencies are refreshed monthly with
uv lock --upgradein a PR titledbuild(deps): refresh lockfile. - Dependabot never touches
uvitself (see below) and, for now, ignoresruff >= 0.16; both exceptions are documented in.github/dependabot.yml.
[tool.docker-versions] is pinned by tag and digest. When bumping uv, change the version
in the tag and the digest together: a reference carrying both resolves to the digest, so a
bumped tag with a stale digest silently keeps the old image.
The uv pin, and the one command that moves it¶
uv gets its own treatment because [tool.uv] required-version is an exact match: a stale
uv pin does not merely lag, it refuses to run. And the version appears in five places that
must agree:
| Where | Why |
|---|---|
[tool.uv] required-version |
the constraint every uv invocation checks |
uv== in the test group |
tox-uv installs a uv binary into .venv/bin that would otherwise shadow the image's |
uv_build upper bound in [build-system] |
the build backend ships in lockstep with uv |
the base-image tag in [tool.docker-versions] |
CI runs inside that image |
uv.lock |
resolved from the test group entry |
Like the quarantine date, the pin was the newest release that had been public for a week when this project was generated — not a literal inherited from the template, which would have grown staler the longer the template went unbumped.
It does not advance on its own. This moves all five together, re-resolving the image digest and the lockfile in one step:
./workflow.cmd develop.bump-uv
It picks the newest release at least 7 days old — long enough that a same-day release
withdrawn hours later never reaches you, and the reason the pin is always resolvable under an
exclude-newer stamped at generation. It refuses a version the uv-build backend has not
published, says nothing changed rather than pretending, and warns when a bump crosses a minor
version, because uv is pre-1.0 and minor releases may break behaviour.
To take a specific version, including one newer than the cool-down allows:
./workflow.cmd develop.bump-uv --version=0.12.1
Editing any of those five by hand is the failure this command exists to avoid — most of all the tag, whose digest has to be re-resolved with it.
How CI picks them up¶
The CI workflow installs only the group it needs for each job — lint job installs the lint group, test job installs test. This keeps job containers small and parallel-safe.
The container images built by ./workflow.cmd container.publish cache the dev group's resolution so subsequent CI runs skip the install step.
See also¶
- Add a dependency — adding to any group.
- Configuration files — where the groups are declared.