# 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

```toml
[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`:

```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

```text
# 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](https://depos.dev/agents/).
