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.txt or pyproject.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 formatter

Key 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.