Metadata-Version: 2.4
Name: depwhy
Version: 0.1.0
Summary: Explains Python dependency conflicts in plain English and suggests fixes
Project-URL: Homepage, https://github.com/<owner>/depwhy
Project-URL: Documentation, https://github.com/<owner>/depwhy#readme
Project-URL: Repository, https://github.com/<owner>/depwhy
Project-URL: Issues, https://github.com/<owner>/depwhy/issues
Project-URL: Changelog, https://github.com/<owner>/depwhy/CHANGELOG.md
Author-email: Your Name <you@example.com>
License: MIT License
        
        Copyright (c) 2026 Your Name
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: conflicts,dependencies,developer-tools,packaging,pip
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Software Distribution
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Requires-Dist: networkx>=3.2
Requires-Dist: packaging>=24.0
Requires-Dist: platformdirs>=4.2.0
Requires-Dist: rich>=13.7.0
Requires-Dist: tomli>=2.0.0; python_version < '3.11'
Requires-Dist: typer>=0.12.0
Provides-Extra: dev
Requires-Dist: hatch>=1.12.0; extra == 'dev'
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: pyright>=1.1.360; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Description-Content-Type: text/markdown

# 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

```bash
pip install depwhy
```

Or with `uv`:

```bash
uv pip install depwhy
```

Requires Python 3.10+.

## CLI Usage

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

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

```python
# 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**.

1. **Parse** — reads your `requirements.txt` or `pyproject.toml` to collect every dependency and version pin.
2. **Fetch** — retrieves dependency metadata from PyPI in parallel using async HTTP, caching results locally (`~/.cache/depwhy/`) so repeat runs are instant.
3. **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.
4. **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.
5. **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.
6. **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](CONTRIBUTING.md) for development setup, code quality standards, and the pull request process.

## License

MIT — see [LICENSE](LICENSE).
