Metadata-Version: 2.4
Name: whydep
Version: 0.1.3
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Build Tools
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Summary: Show why a Python package is installed by tracing its lock file dependency graph
Keywords: uv,dependencies,python,dev-tools
License: MIT
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# whydep

[![Lint](https://github.com/colour-white/whydep/actions/workflows/lint.yml/badge.svg)](https://github.com/colour-white/whydep/actions/workflows/lint.yml)
[![Test](https://github.com/colour-white/whydep/actions/workflows/test.yml/badge.svg)](https://github.com/colour-white/whydep/actions/workflows/test.yml)
[![Bump](https://github.com/colour-white/whydep/actions/workflows/bump.yml/badge.svg)](https://github.com/colour-white/whydep/actions/workflows/bump.yml)
[![Release](https://github.com/colour-white/whydep/actions/workflows/release.yml/badge.svg)](https://github.com/colour-white/whydep/actions/workflows/release.yml)

A fast Rust CLI that answers **"why is this Python package installed?"** by tracing your project's lock file dependency graph and showing which source files use it.

## Install

```bash
pip install whydep
# or
uv add --dev whydep
```

## Usage

```bash
# Look up a single package
wd certifi
whydep certifi        # same tool, longer alias

# Point at a different project
wd certifi --dir /path/to/my-project

# Scan all direct dependencies for unused ones
wd --find-deprecated
```

## Example output

For a **transitive** dependency:

```
Why is 'MarkupSafe' installed?

└── jinja2
    └── flask
        · src/main.py:1: import flask
        · src/main.py:2: from flask import Flask, request
        └── (project root)
```

For a **direct** dependency:

```
Why is 'flask' installed?

· src/main.py:1: import flask
· src/main.py:2: from flask import Flask, request

└── (project root)
```

If no imports are found for a direct dependency, it is marked as removable:

```
Why is 'flask' installed?

· (not directly imported — may be aliased or dynamically imported)
(can be removed)

└── (project root)
```

Lines starting with `·` show where your code imports the direct dependency that pulled the package in.

### --find-deprecated

Scans all direct dependencies and lists those with no imports found in your source files:

```
Unused direct dependencies (2):

  certifi — no imports found, can be removed
  idna    — no imports found, can be removed
```

## Supported lock files

| Tool | Lock file |
|------|-----------|
| [uv](https://github.com/astral-sh/uv) | `uv.lock` |
| [Poetry](https://python-poetry.org) | `poetry.lock` |

`uv.lock` is preferred when both exist. The lock file is searched in the given directory and up to 3 parent directories.

## Configuration

Add a `[tool.whydep]` section to your `pyproject.toml` to limit source scanning to specific directories (faster, less noise):

```toml
[tool.whydep]
src = "src"              # single directory
# src = ["src", "tests"] # or multiple
```

Without this setting the entire project directory is scanned.

