Architecture
depwhy follows a six-stage pipeline: parse → fetch → graph → detect → rank → explain.
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Parse │ → │ Fetch │ → │ Graph │ → │ Detect │ → │ Rank │ → │ Explain │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
1. Parse
src/depwhy/parsers/
- Reads
requirements.txtorpyproject.toml - Extracts PEP 508 requirement strings
- Files:
requirements.py,pyproject.py
2. Fetch
src/depwhy/fetcher/
- Retrieves dependency metadata from PyPI in parallel using async HTTP (httpx)
- Results cached locally at
~/.cache/depwhy/(SHA-256 keyed, TTL via mtime) - Files:
pypi.py(async client with semaphore),cache.py(disk cache)
3. Graph
src/depwhy/graph/
- Builds directed constraint graph using NetworkX DiGraph
- Connects packages to their dependents with version range edges
- Evaluates environment markers for platform-specific deps
- Files:
builder.py,models.py
4. Detect
src/depwhy/solver/detector.py
- Computes intersection of all version constraints per package using PEP 440 SpecifierSet
- Empty intersection = hard conflict
- Narrowed intersection = soft warning
5. Rank
src/depwhy/solver/ranker.py
- Evaluates three fix strategies: loosen user pin, upgrade depender, downgrade depender
- Selects best fix (fewest changes, smallest version delta, prefer newer) + fallback alternative
6. Explain
src/depwhy/explain/
- Generates plain-English problem descriptions from templates
- Renders output via Rich (terminal) or JSON serializer
- Files:
generator.py,formatter.py
Project Structure
text
src/depwhy/
├── __init__.py # Public API exports
├── _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 formatterKey Dependencies
- httpx — Async HTTP client for PyPI API
- networkx — Directed graph library
- packaging — PEP 440 version specifier handling
- typer — CLI framework
- rich — Terminal output formatting
- platformdirs — Cross-platform cache directory
The core pipeline is async internally but exposed synchronously via asyncio.run() in the analyze() function.