ADR 026: Do not use pip-tools for dependency management¶
Status¶
Accepted
Context¶
pip-tools provides pip-compile to
generate fully-pinned requirements.txt files from loose specifications, and
pip-sync to install exactly those pins. It is a popular choice for
reproducible dependency resolution in Python projects.
This project already uses Hatch for environment management and
Hatchling for building (ADR 016). Hatch
creates isolated virtual environments from the dependency groups declared in
pyproject.toml ([project.dependencies], [project.optional-dependencies],
and [tool.hatch.envs.*.dependencies]). Adding pip-tools on top of this would
create a second, overlapping dependency management layer.
The problem pip-tools solves¶
pip-tools addresses reproducible installs — ensuring every developer and CI run gets exactly the same dependency versions by pinning transitive dependencies in a lock file.
Why that problem doesn't apply here¶
| Concern | How this project handles it |
|---|---|
| Reproducible dev environments | Hatch environments auto-install from pyproject.toml extras. Developers get consistent envs without a lock file. |
| Reproducible CI | CI workflows install from pyproject.toml extras via pip install -e ".[test]" or Hatch envs. Pinning would require lock-file maintenance without meaningful benefit for a template repo. |
| Reproducible builds | Build artifacts (sdist/wheel) declare dependency ranges, not pins. End users resolve versions at install time — this is standard Python packaging behavior. A lock file doesn't affect built distributions. |
| Security auditing | pip-audit (in CI and pre-push hook) scans the resolved environment regardless of whether versions are pinned. |
| Dependency drift | Dependabot opens PRs for new dependency versions weekly. Hatch recreates envs from current specs on each invocation. |
What pip-tools would cost¶
- Maintenance burden: Every dependency change requires running
pip-compileto regenerate lock files and committing the result. With multiple extras (dev,test,docs), that means multiplerequirements*.txtfiles to keep in sync. - Merge conflicts: Lock files change frequently and produce large diffs that conflict across branches.
- Duplicate source of truth: Dependencies would be declared in
pyproject.toml(for Hatch and packaging) and pinned inrequirements*.txt(for pip-tools). Keeping both in sync is error-prone. - Hatch friction: Hatch environments don't consume
requirements.txtfiles natively — they readpyproject.toml. Using pip-tools would mean either abandoning Hatch envs or maintaining parallel install paths.
Decision¶
Do not adopt pip-tools. Continue using pyproject.toml as the single source
of truth for all dependency specifications, with Hatch managing environments.
The commented-out pip-tools line in pyproject.toml under extras is
retained as a reference for template users who may want it for their own
projects — but it is not active in this template.
Alternatives Considered¶
pip-tools (pip-compile + pip-sync)¶
Generates pinned requirements.txt from pyproject.toml or requirements.in.
Rejected because: Adds a second dependency management layer that duplicates what Hatch environments already provide. The lock-file maintenance cost outweighs the reproducibility benefit for this project's use case.
Poetry lock¶
Poetry generates a poetry.lock file automatically.
Rejected because: Would require switching from Hatchling/Hatch to Poetry for both build backend and project management — a much larger change than just adding pinning. See ADR 016 for why Hatchling/Hatch was chosen.
PDM lock¶
PDM supports PEP 621 metadata and generates lock files.
Rejected because: Similar trade-off to Poetry — would replace Hatch as the project manager to gain lock-file support. Not justified for a template repo.
uv lock¶
uv is a fast Rust-based package manager with lock-file support.
Rejected because: uv is rapidly evolving and may become a strong option in the future, but as of this writing it would replace Hatch as the project manager. The project may revisit this if uv matures into a stable Hatch alternative. Not justified as an addition on top of the existing Hatch setup.
Consequences¶
Positive¶
- Single source of truth for dependencies (
pyproject.toml) - No lock-file maintenance or merge-conflict churn
- Hatch environments work without workarounds
- Simpler contributor onboarding — one tool, one config file
Negative¶
- No guaranteed exact version reproducibility across environments (deps resolve at install time)
- Template users who need pinning must add pip-tools (or similar) themselves
Mitigations¶
- Dependabot catches outdated or vulnerable transitive dependencies
- pip-audit scans resolved environments for known vulnerabilities
- CI runs on fresh installs, so version drift surfaces quickly as test failures
- The commented-out pip-tools entry in
pyproject.tomlmakes it easy for template users to opt in if their project requires pinning
Implementation¶
- pyproject.toml — Dependency declarations (single source of truth)
- .github/dependabot.yml — Automated dependency update PRs
- ADR 016 — Hatchling + Hatch decision