Metadata-Version: 2.5
Name: uv-override-prune
Version: 1.0.0
Summary: Detect redundant override-dependencies / constraint-dependencies in uv projects
Project-URL: Repository, https://github.com/iwamot/uv-override-prune
Project-URL: Issues, https://github.com/iwamot/uv-override-prune/issues
Author: Takashi Iwamoto
License-Expression: MIT
License-File: LICENSE
Keywords: audit,dependencies,override,prune,uv
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development
Requires-Python: >=3.11
Requires-Dist: packaging==26.3
Requires-Dist: tomlkit==0.15.1
Description-Content-Type: text/markdown

# uv-override-prune

[![pypi](https://img.shields.io/pypi/v/uv-override-prune.svg)](https://pypi.org/project/uv-override-prune/)
[![python](https://img.shields.io/pypi/pyversions/uv-override-prune.svg)](https://pypi.org/project/uv-override-prune/)

Detect prunable `override-dependencies` / `constraint-dependencies` entries in uv projects.

## Install

Requires `uv` on `PATH`, since the audit runs `uv lock`.

```bash
uv tool install uv-override-prune
```

Or run it without installing — useful for one-off checks:

```bash
uvx uv-override-prune
```

## Usage

```bash
# Detect prunable entries (default)
uv-override-prune                          # checks ./pyproject.toml
uv-override-prune path/to/pyproject.toml   # checks given file

# Remove prunable entries in place
uv-override-prune --fix
```

Example output:

```
=== override-dependencies (3 entries) ===
[KEEP]  aiohttp>=3.13.5    3.13.3
[PRUNE] httpx>=0.1.0       0.28.1
[SKIP]  foo==1.0           (non-lower-bound)

Run with --fix to prune entries marked [PRUNE].
```

Exit codes:

| Code | Meaning |
|------|---------|
| `0`  | No prunable entries (or `--fix` succeeded) |
| `1`  | Prunable entries found (without `--fix`) |
| `2`  | `pyproject.toml` not found or malformed, or an entry marked `[ERROR]` (the audit is incomplete, even when other entries were pruned) |

## Why

uv lets you pin a transitive dependency version via `[tool.uv] override-dependencies` and `constraint-dependencies`.

A common reason to reach for these is CVE mitigation: a vulnerability is disclosed in a transitive package, and you force the patched minimum version while direct deps catch up.

Once they do, the entry is no longer doing anything — but it's easy to forget which ones are still load-bearing. Stale overrides become a judgment cost at every audit or upgrade ("is this still needed, or just history?").

`uv-override-prune` answers that mechanically: it checks whether each entry's lower bound is already satisfied by what `uv lock` would resolve without the override, starting from the versions in your current `uv.lock`.

## How it works

For each candidate entry, the tool removes it in a temp copy of `pyproject.toml` and `uv.lock`, runs `uv lock` there, and checks whether the resulting natural resolution still satisfies the entry's specifier. If yes, the entry is `[PRUNE]`.

Starting from the existing `uv.lock` matters because `uv lock` keeps already-locked versions wherever it can. A from-scratch resolution would upgrade every package first, which can make an entry look redundant even though removing it in the real project would downgrade the package. To evaluate against the newest versions instead, run `uv lock --upgrade` before the tool. Without a `uv.lock`, the tool falls back to a from-scratch resolution and says so on stderr.

## Scope

- Targets entries in `[tool.uv] override-dependencies` and `constraint-dependencies`.
- Only specifiers using `>=` and/or `>` are checked. Entries using `==`, `~=`, `<`, `<=`, `!=` (alone or mixed) are skipped.
- Entries with an environment marker (e.g. `foo>=1.0; python_version >= "3.10"`) are skipped, since the natural `uv lock` resolution doesn't reflect the marker's intent.
- When uv forked the resolution on environment markers and locked several versions of the package (one per fork), the entry is `[PRUNE]` only if every version satisfies it. `[KEEP]` lists the versions below the bound; `[PRUNE]` lists them all.
- An entry that repeats an earlier entry in the same section (same package and specifier, ignoring spelling) is reported as `[PRUNE]` with `(duplicate)`. The first copy is evaluated with every copy removed, so its verdict reflects the natural resolution and `--fix` leaves exactly one copy.

## Known limitations

- The temp copy holds only `pyproject.toml` and `uv.lock`, so metadata that needs a build to read is out of reach. A project whose `dependencies`, `optional-dependencies`, or `requires-python` are declared `dynamic` has every entry `[SKIP]`ped as `(dynamic-metadata)`. A dynamic `version` is fine (the copy pins a placeholder), and `[tool.uv.sources]` path deps, workspace members, and `[project] readme` are rewritten to absolute paths.
- One-at-a-time evaluation: if overrides interact (e.g. cascade redundancy, shared transitive deps), individual runs may miss some prunable entries. Re-run after applying removals to surface the next layer.

## License

MIT
