Dependency notes for Python
pyproject.toml and requirements.txt accept comments, but a # line beside a version specifier has room for a few words. The parts that matter do not fit: why this package over the alternatives, why the upper bound, which tests show an upgrade is safe, and what happened the last time someone tried.
Depos keeps a note for each distribution in .depos/python/DEPENDENCY-NOTES.md, beside the manifest, and shows it on the dependency's line in the editor.
What Depos reads
- Manifest:
pyproject.toml,requirements*.txt,requirements/*.txt - Notes file:
.depos/python/DEPENDENCY-NOTES.md - Section heading: the normalized distribution name:
## requests,## importlib-metadata
Depos reads named dependencies in standard project metadata, optional dependencies, dependency groups and build requirements; Poetry dependency tables and groups; uv legacy development dependencies; and named pip requirements. Includes, constraints, tool options, unnamed paths and lock files are not dependencies. Package names are matched case-insensitively with ., _ and - treated alike.
Depos never runs Python, pip, Poetry or uv.
Example
[project]
name = "billing"
requires-python = ">=3.12"
dependencies = [
"SQLAlchemy>=2.0,<2.1",
"httpx>=0.28",
]
[dependency-groups]
dev = ["pytest>=8.3"]
.depos/python/DEPENDENCY-NOTES.md:
## sqlalchemy
ORM and the base of the Alembic migrations. Models live in billing/db/models.py.
### Agent notes
- purpose: ORM for the billing database; the Alembic migrations build on its metadata
- constraint: stay below 2.1 until the migration tests pass on it (BILL-310)
- verify: `pytest tests/db` and `alembic upgrade head` on an empty database
- verified: 2.0.36
The heading is the normalized distribution name: SQLAlchemy is ## sqlalchemy, and Flask_SQLAlchemy would be ## flask-sqlalchemy. A package listed in both pyproject.toml and a requirements file of the same project shares one section.
Comments in requirements.txt and pyproject.toml
# SQLAlchemy: the ORM and the base of the Alembic migrations
SQLAlchemy>=2.0,<2.1 # 2.1 fails the migration tests (BILL-310)
httpx>=0.28
In a requirements file, a line that starts with # is a comment, and so is the rest of a line after whitespace and a #. Without the whitespace, pip reads the # as part of the requirement: requests==2.32.3#x stops pip install with Invalid requirement.
A requirements file that a tool writes keeps little of what you wrote in it. pip freeze > requirements.txt writes no comments; pip freeze -r requirements.txt keeps the comment lines, but not a comment at the end of a requirement. pip-compile and uv pip compile write the file anew on every run and carry no comment over, from the old file or from requirements.in.
pyproject.toml is TOML: # starts a comment anywhere outside a string. uv add and poetry add keep the comments already there, but uv remove and poetry remove leave the comment line above a removed dependency behind, above whatever comes next, and uv remove leaves the removed line's own comment as well. In a sorted list, uv add puts a new dependency in name order, which can land it between a comment and the dependency the comment was written for.
A note in .depos/python/DEPENDENCY-NOTES.md belongs to the distribution name, not to a line, so regenerating or reordering the requirements does not move it.
Pinning a version
== pins one version, in a requirements file and in [project] dependencies alike: both take the same requirement strings, requests==2.32.3. ~= is the compatible release clause; ~= 2.2 means >= 2.2, == 2.*.
uv add writes a lower bound on the version it resolved, such as >=2.32.3, and uv add "requests==2.32.3" writes the pin. poetry add writes a caret range such as ^2.34.2 under [tool.poetry.dependencies], and the same range spelled out, >=2.34.2,<3.0.0, under [project]. Under [tool.poetry.dependencies], a bare version such as requests = "2.32.3" means that version only.
uv.lock and poetry.lock record the exact versions installed. uv sync --locked and uv lock --check stop when uv.lock no longer matches pyproject.toml, and poetry install stops when poetry.lock is out of date. Neither the pin nor the lock says why: the constraint: line in the example above holds SQLAlchemy below 2.1, and says what has to pass first.
For AI coding agents
An agent reads a package's section before it adds, upgrades or removes the package: constraint: before it moves a version, verify: to check the result. It logs the attempt under ### Agent notes, even when it reverts it. See the rules agents follow.