Explains Python dependency conflicts in plain English and suggests fixes
Project description
depwhy
Explains Python dependency conflicts in plain English and suggests ranked fixes.
The Problem
When pip, uv, or poetry fails to resolve dependencies, the error output is verbose, cryptic, and non-actionable. You're left manually cross-referencing PyPI to figure out which packages conflict and why. depwhy reads your requirements, finds every conflict, and tells you exactly what to change — with a ready-to-run pip install command.
Install
pip install depwhy
Or with uv:
uv pip install depwhy
Requires Python 3.10+.
CLI Usage
# Analyze a requirements.txt file
depwhy requirements.txt
# Analyze a pyproject.toml file
depwhy pyproject.toml
# Analyze packages installed in the current virtualenv
depwhy --env
# Output machine-readable JSON
depwhy requirements.txt --json
# Exit code only (0 = clean, 1 = conflicts) — useful in CI
depwhy requirements.txt --quiet
# Use cached metadata only (no network calls)
depwhy requirements.txt --offline
# Override target Python version for environment marker evaluation
depwhy requirements.txt --python-version 3.11
CLI Flags
| Flag | Description |
|---|---|
--env |
Analyze installed packages in the current venv |
--json |
Output machine-readable JSON to stdout |
--quiet |
No output; exit code only |
--offline |
Use disk cache only — no network calls |
--python-version |
Override Python version for marker evaluation |
--no-color |
Disable colored terminal output |
--version |
Print version and exit |
Exit Codes
| Code | Meaning |
|---|---|
| 0 | No conflicts |
| 1 | Conflicts found |
| 2 | Error |
Example Output
$ depwhy requirements.txt
2 conflicts found
urllib3
Problem: requests==2.28.0 requires urllib3<1.27, but you have urllib3==2.0.0 pinned
Solution: Remove your pin — use urllib3==1.26.18 (latest compatible)
→ pip install urllib3==1.26.18
Alternative: Upgrade requests to >=2.29.0 (loosens urllib3 upper bound)
→ pip install requests==2.29.0
certifi
Problem: cryptography==41.0.0 requires certifi<2023.0, but pip requires certifi>=2017.4.17
Solution: Downgrade certifi to satisfy both constraints
→ pip install certifi==2022.12.7
Alternative: Upgrade cryptography to >=42.0.0 (drops certifi upper bound)
→ pip install cryptography==42.0.0
Library API
import depwhy
report = depwhy.analyze("requirements.txt")
if report.has_conflicts:
for conflict in report.conflicts:
print(conflict.package)
print(f" Problem: {conflict.problem}")
print(f" Solution: {conflict.solution.description}")
print(f" {conflict.solution.pip_command}")
if conflict.alternative:
print(f" Alternative: {conflict.alternative.description}")
print(f" {conflict.alternative.pip_command}")
else:
print(f"No conflicts found ({report.analyzed_packages} packages checked)")
The analyze() function accepts a path to requirements.txt or pyproject.toml, a list of requirement strings, or a pathlib.Path object:
# From a list of requirement strings
report = depwhy.analyze(["django>=4.0", "celery==5.2", "kombu==5.3"])
# With Python version and platform for environment marker evaluation
report = depwhy.analyze("requirements.txt", python_version="3.11", platform="linux")
# Offline mode (uses disk cache only — no network calls)
report = depwhy.analyze("requirements.txt", offline=True)
Return Types
analyze() returns a ConflictReport:
| Field | Type | Description |
|---|---|---|
has_conflicts |
bool |
Whether any conflicts were found |
conflicts |
list[Conflict] |
All detected conflicts |
warnings |
list[str] |
Narrowed-but-valid ranges (soft issues) |
analyzed_packages |
int |
Total packages examined |
Each Conflict contains:
| Field | Type | Description |
|---|---|---|
package |
str |
The package that cannot be resolved |
problem |
str |
Plain-English one-liner describing the conflict |
constraints |
list[Constraint] |
All incoming version constraints |
solution |
FixCandidate |
Recommended fix |
alternative |
FixCandidate | None |
Second-best fix |
Each FixCandidate contains:
| Field | Type | Description |
|---|---|---|
description |
str |
Human-readable explanation of the fix |
pip_command |
str |
Copy-pasteable pip install command |
is_alternative |
bool |
False for the top fix, True for the fallback |
How It Works
depwhy follows a six-stage pipeline: parse → fetch → graph → detect → rank → explain.
- Parse — reads your
requirements.txtorpyproject.tomlto collect every dependency and version pin. - Fetch — retrieves dependency metadata from PyPI in parallel using async HTTP, caching results locally (
~/.cache/depwhy/) so repeat runs are instant. - Graph — builds a directed constraint graph (via
networkx) connecting every package to the packages that require it and the version ranges they accept. Environment markers are evaluated so platform-specific dependencies are handled correctly. - Detect — computes the intersection of all version constraints for each package using PEP 440 specifier algebra. An empty intersection means no single version can satisfy every requirement (hard conflict). A narrowed intersection triggers a soft warning.
- Rank — evaluates three fix strategies for each conflict — loosen a user pin, upgrade a depender, or downgrade a depender — and selects the best fix (fewest changes, smallest version delta, prefer newer) plus a fallback alternative.
- Explain — generates a plain-English problem description and renders the output to the terminal with color-coded labels, or as structured JSON.
Architecture
src/depwhy/
├── _analyze.py # Core analyze() orchestrator
├── cli.py # Typer CLI entrypoint
├── parsers/
│ ├── requirements.py # requirements.txt parser
│ └── pyproject.py # pyproject.toml parser
├── fetcher/
│ ├── pypi.py # Async PyPI metadata client (httpx + semaphore)
│ └── cache.py # Disk cache (SHA-256 keyed, TTL via mtime)
├── graph/
│ ├── builder.py # Constraint graph construction (networkx.DiGraph)
│ └── models.py # Frozen dataclasses: Constraint, Conflict, etc.
├── solver/
│ ├── detector.py # Conflict detection (SpecifierSet intersection)
│ └── ranker.py # Fix candidate ranking (3 strategies)
└── explain/
├── generator.py # Template-based explanation builder
└── formatter.py # Rich terminal + JSON formatter
Contributing
See CONTRIBUTING.md for development setup, code quality standards, and the pull request process.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file depwhy-0.1.1.tar.gz.
File metadata
- Download URL: depwhy-0.1.1.tar.gz
- Upload date:
- Size: 106.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f06c14d0bfb3f8f66a980b2b59039a891072f7c9e5ed79237f0efd014d18a8c
|
|
| MD5 |
e8aa99c1795efa18c87a262486cd3b26
|
|
| BLAKE2b-256 |
0208e5635de4d30a8a6e5c2ff72bc8d68d1888943384af4d20faa063a4f01acf
|
File details
Details for the file depwhy-0.1.1-py3-none-any.whl.
File metadata
- Download URL: depwhy-0.1.1-py3-none-any.whl
- Upload date:
- Size: 32.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e253e383e3fa9c722c09c20a15776876ae2434f64229eef5a0bc4bfc8e4e0cf
|
|
| MD5 |
b3b06d0d07c14aff71dfb8d006b21799
|
|
| BLAKE2b-256 |
65774242e1883bff5ed51233be66b94bb54c1554ffc88fef892d8f980958f795
|