Getting started

Installation

uv tool install pipdeptree
pipx install pipdeptree
pip install pipdeptree

For enhanced terminal output with colors and checkmarks, install the rich extra:

pip install pipdeptree[rich]

For Graphviz diagram generation, install the graphviz extra:

pip install pipdeptree[graphviz]

First run

Run pipdeptree with no arguments to see the full dependency tree of your environment:

$ pipdeptree
covdefaults==2.3.0
└── coverage [required: >=6.0.2, installed: 7.13.5]
diff_cover==10.2.0
├── Jinja2 [required: >=2.7.1, installed: 3.1.6]
│   └── MarkupSafe [required: >=2.0, installed: 3.0.3]
├── Pygments [required: >=2.19.1,<3.0.0, installed: 2.19.2]
├── chardet [required: >=3.0.0, installed: 7.1.0]
└── pluggy [required: >=0.13.1,<2, installed: 1.6.0]
graphviz==0.21
pipdeptree==2.33.0
└── packaging [required: >=26, installed: 26.0]
pytest-cov==7.0.0
├── coverage [required: >=7.10.6, installed: 7.13.5]
├── pluggy [required: >=1.2, installed: 1.6.0]
└── pytest [required: >=7, installed: 9.0.2]
    ├── iniconfig [required: >=1.0.1, installed: 2.3.0]
    ├── packaging [required: >=22, installed: 26.0]
    ├── pluggy [required: >=1.5,<2, installed: 1.6.0]
    └── Pygments [required: >=2.7.2, installed: 2.19.2]

Each top-level entry is a package with no parent depending on it. Indented lines show dependencies, with the required version range and the actually installed version.

Understanding the output

Each line shows:

  • Package name and version – the top-level line (e.g., pytest-cov==7.0.0).

  • Dependencies – indented with tree-drawing characters.

  • Version constraints[required: >=7, installed: 9.0.2] shows what the parent needs vs what’s installed.

When a dependency appears multiple times (e.g., pluggy above), it means multiple packages depend on it. If their version requirements conflict, pipdeptree will warn you.

The dependency graph for the above output looks like this:

        flowchart TD
    pytest-cov["pytest-cov<br/>7.0.0"]:::top --> coverage["coverage<br/>7.13.5"]:::dep
    pytest-cov --> pluggy["pluggy<br/>1.6.0"]:::shared
    pytest-cov --> pytest["pytest<br/>9.0.2"]:::dep
    pytest --> iniconfig["iniconfig<br/>2.3.0"]:::dep
    pytest --> packaging["packaging<br/>26.0"]:::shared
    pytest --> pluggy
    pytest --> pygments["Pygments<br/>2.19.2"]:::shared
    covdefaults["covdefaults<br/>2.3.0"]:::top --> coverage
    diff_cover["diff_cover<br/>10.2.0"]:::top --> jinja2["Jinja2<br/>3.1.6"]:::dep
    diff_cover --> pygments
    diff_cover --> chardet["chardet<br/>7.1.0"]:::dep
    diff_cover --> pluggy
    jinja2 --> markupsafe["MarkupSafe<br/>3.0.3"]:::dep
    classDef top fill:#2980b9,color:#fff
    classDef dep fill:#27ae60,color:#fff
    classDef shared fill:#8e44ad,color:#fff
    

Common operations

Filter to a specific package:

$ pipdeptree --packages pytest
pytest==9.0.2
├── iniconfig [required: >=1.0.1, installed: 2.3.0]
├── packaging [required: >=22, installed: 26.0]
├── pluggy [required: >=1.5,<2, installed: 1.6.0]
└── Pygments [required: >=2.7.2, installed: 2.19.2]

Find out why a package is installed (reverse tree):

$ pipdeptree --reverse --packages pygments
Pygments==2.19.2
├── rich==14.3.3 [requires: Pygments>=2.13.0,<3.0.0]
├── diff_cover==10.2.0 [requires: Pygments>=2.19.1,<3.0.0]
└── pytest==9.0.2 [requires: Pygments>=2.7.2]
    ├── pytest-mock==3.15.1 [requires: pytest>=6.2.5]
    ├── pytest-subprocess==1.5.3 [requires: pytest>=4.0.0]
    └── pytest-cov==7.0.0 [requires: pytest>=7]

Limit the tree depth:

$ pipdeptree -d 1
covdefaults==2.3.0
└── coverage [required: >=6.0.2, installed: 7.13.5]
diff_cover==10.2.0
├── Jinja2 [required: >=2.7.1, installed: 3.1.6]
├── Pygments [required: >=2.19.1,<3.0.0, installed: 2.19.2]
├── chardet [required: >=3.0.0, installed: 7.1.0]
└── pluggy [required: >=0.13.1,<2, installed: 1.6.0]

Get an at-a-glance health report with --summary:

$ pipdeptree --packages pytest --summary
total packages:           5
direct dependencies:      1
transitive dependencies:  4
max depth:                2
cyclic dependencies:      0
missing dependencies:     0
conflicting dependencies: 0 (0 edges)
licenses:                 (Apache-2.0 OR BSD-2-Clause): 1, (BSD-2-Clause): 1, (MIT License): 1, (MIT): 2
unknown licenses:         0
copyleft licenses:        no
min requires-python:      3.10
total size:               6.0 MB

Drop --packages to report on the whole environment. Add -o rich for a styled table, or -o json for a machine-readable version to gate CI on. The programmatic pipdeptree.render(summary=True) returns the same report and displays as an HTML table in a notebook. See Output formats for the full field list.

Preview a tree before installing

So far you have looked at packages that are already installed. You can also peek at the tree a package would pull in before you commit to installing it. This lives in the from-index subcommand, which queries PyPI instead of inspecting your environment. It ships in an optional extra, so install that first:

$ pip install pipdeptree[index]

Now ask what starlette brings along:

$ pipdeptree from-index "starlette"
starlette==1.2.1
├── anyio [candidate: 4.13.0]
│   ├── idna [candidate: 3.17]
│   └── typing-extensions [candidate: 4.15.0]
└── typing-extensions [candidate: 4.15.0]

Read this the same way as the tree from your environment. The top line is the requirement you asked for and the indented lines are its dependencies. Each edge shows the candidate version the resolver selected from PyPI: nothing is installed and the resolver produces a single version per package without a requirement range, so the edges read [candidate: <version>] rather than the [required: ..., installed: ...] pair you see for an installed environment.

The positional argument is a PEP 508 requirement, the same string you would pass to pip install, so you can pin or bound it. Bound fastapi and resolve it alongside starlette – the pin lands on the upper bound:

$ pipdeptree from-index "fastapi<=0.115.2" starlette
fastapi==0.115.2
├── pydantic [candidate: 2.13.4]
│   ├── annotated-types [candidate: 0.7.0]
│   ├── pydantic-core [candidate: 2.46.4]
│   │   └── typing-extensions [candidate: 4.15.0]
│   ├── typing-extensions [candidate: 4.15.0]
│   └── typing-inspection [candidate: 0.4.2]
│       └── typing-extensions [candidate: 4.15.0]
├── starlette [candidate: 0.40.0]
│   └── anyio [candidate: 4.13.0]
│       ├── idna [candidate: 3.17]
│       └── typing-extensions [candidate: 4.15.0]
└── typing-extensions [candidate: 4.15.0]

The constraint moves the top-level pin to 0.115.2 and the dependency picks follow from it. You can also resolve a pyproject.toml with --pyproject or a requirements file with --requirements, and even a local checkout or a pinned git requirement, where the resolver reads the target’s metadata. See Usage patterns for those sources, the render flags, and the inputs the resolver rejects.

Render a lock file

If you already have a PEP 751 lock file (a pylock.toml, such as the one uv exports), point from-lock at it to read its tree:

$ pipdeptree from-lock pylock.toml
build==1.5.0
├── packaging [candidate: 26.2]
└── pyproject-hooks [candidate: 1.2.0]

The lock already lists every package, version and edge, so from-lock runs offline with no install, no network and no extra. The filtering and output flags you used above work here too. See Usage patterns for the full set.

Next steps

See Usage patterns for filtering, virtualenv support, warning control, and how to surface optional (extras) dependencies. See Output formats for all available output formats including JSON, Mermaid, and Graphviz. See How pipdeptree works for how pipdeptree decides when an optional dependency edge is “active”.