Usage patterns

Running in virtualenvs

By default pipdeptree auto-detects your active virtual environment (venv, virtualenv, conda, or poetry) and inspects it. When no virtual environment is active, it falls back to the interpreter running pipdeptree. A system installation therefore inspects the system packages:

$ pipdeptree

To inspect a specific interpreter regardless of what is active, pass its path to --python:

$ pipdeptree --python /path/to/venv/bin/python

Use --python auto to require an active virtual environment. The command fails when it cannot find one:

$ pipdeptree --python auto

Installing pipdeptree inside the virtual environment and running it there gives the same result.

from-index (resolve by querying the index)

pipdeptree inspects an installed environment by default. The from-index subcommand queries a package index and returns the resolved tree before installation.

Positional arguments are PEP 508 requirements or local source paths, the same strings you pass to pip install. You supply files with repeatable flags:

  • positional REQUIREMENT – an inline PEP 508 requirement string, version specifiers and extras included;

  • --requirements FILE – a standard requirements.txt or .in style file;

  • --pyproject FILE – a pyproject.toml handed to the resolver, which reads [project].dependencies and honors its [tool.nab] configuration.

Pass at least one source. Each edge shows the candidate version the resolver selected rather than a package on your machine. The resolver produces one version per package with no requirement range, so edges read [candidate: <version>] instead of the [required: ..., installed: ...] pair shown for an installed environment.

Resolve inline requirements

A single requirement resolves to its full tree:

$ pipdeptree from-index "starlette"
starlette==1.3.1
└── anyio [candidate: 4.14.2]
    └── idna [candidate: 3.18]

Several requirements resolve together into one graph, and a version specifier bounds the pick:

$ 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.16.0]
│   ├── typing-extensions [candidate: 4.16.0]
│   └── typing-inspection [candidate: 0.4.2]
│       └── typing-extensions [candidate: 4.16.0]
├── starlette [candidate: 0.40.0]
│   └── anyio [candidate: 4.14.2]
│       └── idna [candidate: 3.18]
└── typing-extensions [candidate: 4.16.0]

Request extras with the name[extra] syntax. The resolver pulls the extra’s dependencies into the tree. They appear as children, such as pysocks below, with the pinned version from the resolve and no extra label:

$ pipdeptree from-index "requests[socks]"
requests==2.34.2
├── certifi [candidate: 2026.6.17]
├── charset-normalizer [candidate: 3.4.9]
├── idna [candidate: 3.18]
├── pysocks [candidate: 1.7.1]
└── urllib3 [candidate: 2.7.0]

An environment marker gates a requirement on the interpreter that the resolve targets. A matching marker includes the requirement; a non-matching marker drops it. Quote the argument so the shell keeps the marker attached:

$ pipdeptree from-index 'idna; python_version >= "3.10"'
idna==3.18

Resolve from a requirements file

Point --requirements at a requirements.txt:

$ pipdeptree from-index --requirements requirements.txt

pipdeptree parses the file as a standard requirements file, so a real-world one resolves as-is. Take this requirements.txt:

-r base.txt                        # nested include, followed
-c constraints.txt                 # constraints, fed to the resolver
httpx[http2]                       # extras kept
tomli; python_version < "3.11"     # environment marker kept
# pin chosen by the security team   <- comment, ignored
requests==2.32.3 \
    --hash=sha256:0000000000000000000000000000000000000000000000000000000000000000

Each directive maps as follows: the -r base.txt include is read inline, the -c constraints.txt pins bound the resolve, the marker and the [http2] extra reach the resolver intact, the comment drops out, and the --hash line resolves requests while pipdeptree ignores the hash (the resolver verifies nothing from the index). The same applies to --index-url and other pip-specific options.

Resolve from a pyproject.toml

With --pyproject as the sole source, the resolver receives the full [project] table and the file’s [tool.nab] configuration:

$ pipdeptree from-index --pyproject pyproject.toml

Add any other source and the [project].dependencies from that pyproject merge into one combined resolve instead. Its [tool.nab] settings drop out on this path:

$ pipdeptree from-index --pyproject pyproject.toml httpx

Combine and repeat sources

Both flags repeat. The resolver merges their sources with the positional requirements:

$ pipdeptree from-index --requirements a.txt --requirements b.txt --pyproject p.toml extra-pkg

Use a private or custom index

By default the resolve runs against PyPI. Point it at an internal index with --index-url and add more with --extra-index-url (repeatable):

$ pipdeptree from-index "internal-lib" --index-url https://nexus.corp/repository/pypi/simple
$ pipdeptree from-index "internal-lib" --extra-index-url https://nexus.corp/repository/pypi/simple

--index-url replaces PyPI as the primary index, matching pip’s --index-url. --extra-index-url keeps PyPI as the primary and appends each extra after it. When a flag is absent, the value falls back to the environment: --index-url reads PIP_INDEX_URL then UV_INDEX_URL, and --extra-index-url reads PIP_EXTRA_INDEX_URL then UV_EXTRA_INDEX_URL (both whitespace separated, like pip and uv). With nothing set the resolve uses PyPI.

The resolver searches indexes in order and uses the first index that contains a package. pip merges index results and picks the highest version. Put the trusted index for each package first.

The index flags and their environment fallbacks override a --pyproject’s own [tool.nab].indexes. With no flag and no environment override, the resolve uses the indexes declared in the pyproject.

$ PIP_INDEX_URL=https://nexus.corp/repository/pypi/simple pipdeptree from-index "internal-lib"
$ pipdeptree from-index --pyproject pyproject.toml --index-url https://nexus.corp/repository/pypi/simple

Apply render flags

The graph and render flags behave as they do for the default command. Emit JSON for tooling:

$ pipdeptree from-index "starlette" -o json
[
    {
        "package": {
            "key": "anyio",
            "package_name": "anyio",
            "candidate_version": "4.14.2"
        },
        "dependencies": [
            {
                "key": "idna",
                "package_name": "idna",
                "candidate_version": "3.18"
            }
        ]
    },
    {
        "package": {
            "key": "idna",
            "package_name": "idna",
            "candidate_version": "3.18"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "starlette",
            "package_name": "starlette",
            "candidate_version": "1.3.1"
        },
        "dependencies": [
            {
                "key": "anyio",
                "package_name": "anyio",
                "candidate_version": "4.14.2"
            }
        ]
    }
]

Trace why the resolver pulled a package in with --reverse (-r):

$ pipdeptree from-index "fastapi<=0.115.2" --reverse --packages anyio
anyio==4.14.2
└── starlette==0.40.0 [requires: anyio==4.14.2]
    └── fastapi==0.115.2 [requires: starlette==0.40.0]

Other supported flags include -o mermaid, the graphviz-* formats, --depth (-d), package filters, --extras (-x) and --encoding:

$ pipdeptree from-index "fastapi<=0.115.2" --depth 1
$ pipdeptree from-index "fastapi<=0.115.2" -o mermaid
$ pipdeptree from-index "fastapi<=0.115.2" --packages starlette
$ pipdeptree from-index "fastapi<=0.115.2" --exclude anyio
$ pipdeptree from-index "requests[socks]" --extras none

Resolve editable, local and git requirements

For a requirement that points at a checkout instead of the index, the resolver reads the target’s PEP 621 metadata. Static [project] data needs no build. A project that declares dynamic dependencies triggers its build backend.

Editable installs are a --requirements file directive, so pass them through a file:

$ printf -- '-e ./mypkg\n' > requirements.txt
$ pipdeptree from-index --requirements requirements.txt

Local paths (./mypkg, file:///abs/path) work the same, as positional arguments or file lines. For a pinned git requirement, the resolver clones the repo to its cache and reads the metadata:

$ pipdeptree from-index "mypkg @ git+https://github.com/o/r.git@<full-40-char-commit-sha>"

The git ref must be a full 40-character commit sha; pipdeptree refuses a tag or branch so the resolve stays reproducible. Bare wheel/sdist archive URLs and non-git VCS schemes (hg+/bzr+/svn+) stay out of scope.

Handle errors

A --requirements or --pyproject file must exist; a missing one stops the command. Positional arguments accept requirement strings. Use the file flags for paths:

$ pipdeptree from-index --requirements missing.txt
source file does not exist: missing.txt

A bare wheel/sdist archive URL or a non-git VCS scheme (hg+/bzr+/svn+) has no index mapping:

requirements.txt
foo @ https://h/foo-1.0-py3-none-any.whl
$ pipdeptree from-index --requirements requirements.txt
URL requirements are not supported by the index resolver: foo @ https://h/foo-1.0-py3-none-any.whl

An unpinned git ref fails the same way, and so does a constraint that carries extras or a URL.

The installed-environment display options inspect on-disk files, so from-index does not accept them:

$ pipdeptree from-index "starlette" --metadata license
error: unexpected argument '--metadata' found
...

The same holds for --computed (-c), --license and the environment-inspection options (--python, --path, -l/-u). from-index has neither package files nor an environment to read. No source is an error:

$ pipdeptree from-index
pipdeptree: error: from-index needs at least one REQUIREMENT, --requirements FILE, or --pyproject FILE

from-lock (render a PEP 751 lock)

The from-lock subcommand reads a PEP 751 lock file (pylock.toml) and renders its dependency tree. A lock records the pinned packages, their versions and the edges between them. from-lock runs offline with no package index or network on Python 3.10 through 3.14. The native extension parses TOML with Rust’s toml crate. Take this lock:

pylock.toml
lock-version = "1.0"

[[packages]]
name = "build"
version = "1.5.0"
dependencies = [{ name = "packaging" }, { name = "pyproject-hooks" }]

[[packages]]
name = "packaging"
version = "26.2"

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

Each edge shows the candidate: version the lock pinned, not a package on your machine. pipdeptree does not inspect an installed environment or install anything.

Producing a pylock.toml

PEP 751 emitters write files that from-lock can read. uv exports one from a project:

$ uv export -o pylock.toml          # or: uv lock then export
$ pipdeptree from-lock pylock.toml

Render flags on a lock

The lock supplies names, versions and edges, so the graph and render flags behave as they do for the default command. Use --packages (-p), --exclude (-e), --depth (-d), --extras (-x), --reverse (-r), --encoding or an output format from -o.

Emit JSON for another tool to consume:

$ pipdeptree from-lock pylock.toml -o json
[
    {
        "package": {
            "key": "build",
            "package_name": "build",
            "candidate_version": "1.5.0"
        },
        "dependencies": [
            {
                "key": "packaging",
                "package_name": "packaging",
                "candidate_version": "26.2"
            },
            {
                "key": "pyproject-hooks",
                "package_name": "pyproject-hooks",
                "candidate_version": "1.2.0"
            }
        ]
    },
    {
        "package": {
            "key": "packaging",
            "package_name": "packaging",
            "candidate_version": "26.2"
        },
        "dependencies": []
    },
    {
        "package": {
            "key": "pyproject-hooks",
            "package_name": "pyproject-hooks",
            "candidate_version": "1.2.0"
        },
        "dependencies": []
    }
]

Flip the edges with --reverse to see which packages pull in a given dependency:

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

Draw a Mermaid diagram for a docs page or chat client:

$ pipdeptree from-lock pylock.toml -o mermaid

Use the installed-environment filters on lock trees. Keep one root with --packages:

$ pipdeptree from-lock pylock.toml --packages build

Hide a package with --exclude:

$ pipdeptree from-lock pylock.toml --exclude packaging

Stop after the first level with --depth:

$ pipdeptree from-lock pylock.toml --depth 1

Locks without edges

A valid PEP 751 lock may pin packages without recording the edges between them. from-lock then renders a flat list of pinned packages, each with no children:

pylock.toml
lock-version = "1.0"

[[packages]]
name = "build"
version = "1.5.0"

[[packages]]
name = "packaging"
version = "26.2"

[[packages]]
name = "pyproject-hooks"
version = "1.2.0"
$ pipdeptree from-lock pylock.toml
build==1.5.0
packaging==26.2
pyproject-hooks==1.2.0

Lock limitations

A lock carries names, versions and edges, so the installed-environment display options have nothing to read. The subcommand omits them:

$ pipdeptree from-lock pylock.toml --metadata license
error: unexpected argument '--metadata' found
...
$ pipdeptree from-lock pylock.toml --license
error: unexpected argument '--license' found
...

The same holds for --computed (-c) and the environment-inspection options (--python, --path, -l/-u): a lock has no METADATA file, no on-disk sizes and no environment to point at.

A missing lock file stops the command with a message and exit code 1:

$ pipdeptree from-lock missing.toml
lock file does not exist: missing.toml
$ echo $?
1

Malformed TOML returns exit code 1. from-lock also rejects a TOML file with no packages array:

pylock.toml
lock-version = "1.0"
$ pipdeptree from-lock pylock.toml
missing 'packages' array

pipdeptree reports the parse error for an invalid package entry:

pylock.toml
lock-version = "1.0"

[[packages]]
version = "1"
$ pipdeptree from-lock pylock.toml
package is missing 'name'

Filtering packages

By default, pipdeptree shows the full dependency tree of your environment:

$ pipdeptree
covdefaults==2.3.0
└── coverage [required: >=6.0.2, installed: 7.15.1]
cryptography==2.7
diff_cover==10.3.0
├── chardet [required: >=3.0.0, installed: 7.4.3]
├── Jinja2 [required: >=2.7.1, installed: 3.1.6]
│   └── MarkupSafe [required: >=2.0, installed: 3.0.3]
├── pluggy [required: >=0.13.1,<2, installed: 1.6.0]
└── Pygments [required: >=2.19.1,<3.0.0, installed: 2.20.0]
oauthlib==3.0.0
pipdeptree==4.0.0
├── nab-index [required: >=0.0.11, installed: 0.0.11]
│   ├── packaging [required: >=24.0, installed: 26.2]
│   ├── truststore [required: >=0.10, installed: 0.10.4]
│   ├── typing_extensions [required: >=4.6, installed: 4.16.0]
│   └── urllib3 [required: >=2.0, installed: 2.7.0]
└── nab-python [required: >=0.0.11, installed: 0.0.11]
    ├── build [required: >=1.2, installed: 1.5.1]
    │   ├── packaging [required: >=24.0, installed: 26.2]
    │   └── pyproject_hooks [required: Any, installed: 1.2.0]
    ├── installer [required: >=0.7, installed: 1.0.1]
    ├── nab-index [required: ==0.0.11, installed: 0.0.11]
    │   ├── packaging [required: >=24.0, installed: 26.2]
    │   ├── truststore [required: >=0.10, installed: 0.10.4]
    │   ├── typing_extensions [required: >=4.6, installed: 4.16.0]
    │   └── urllib3 [required: >=2.0, installed: 2.7.0]
    ├── nab-resolver [required: ==0.0.11, installed: 0.0.11]
    │   └── typing_extensions [required: >=4.6, installed: 4.16.0]
    ├── pyproject_hooks [required: >=1.2, installed: 1.2.0]
    ├── tomli [required: >=2.0, installed: 2.4.1]
    ├── tomli_w [required: >=1.2, installed: 1.2.0]
    └── typing_extensions [required: >=4.6, installed: 4.16.0]
pyjwt==1.7.1
PySocks==1.7.1
pytest-cov==7.1.0
├── coverage [required: >=7.10.6, installed: 7.15.1]
├── pluggy [required: >=1.2, installed: 1.6.0]
└── pytest [required: >=7, installed: 9.1.1]
    ├── iniconfig [required: >=1.0.1, installed: 2.3.0]
    ├── packaging [required: >=22, installed: 26.2]
    ├── pluggy [required: >=1.5,<2, installed: 1.6.0]
    └── Pygments [required: >=2.7.2, installed: 2.20.0]
requests==2.32.3
├── certifi [required: >=2017.4.17, installed: 2024.8.30]
├── charset_normalizer [required: >=2,<4, installed: 3.4.0]
├── idna [required: >=2.5,<4, installed: 3.10]
└── urllib3 [required: >=1.21.1,<3, installed: 2.7.0]

Show selected packages with --packages (-p):

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

Separate multiple packages with commas. You can use wildcards:

$ pipdeptree --packages 'pytest*'
pytest-cov==7.1.0
├── coverage [required: >=7.10.6, installed: 7.15.1]
├── pluggy [required: >=1.2, installed: 1.6.0]
└── pytest [required: >=7, installed: 9.1.1]
    ├── iniconfig [required: >=1.0.1, installed: 2.3.0]
    ├── packaging [required: >=22, installed: 26.2]
    ├── pluggy [required: >=1.5,<2, installed: 1.6.0]
    └── Pygments [required: >=2.7.2, installed: 2.20.0]

A package entry may carry an extras spec such as somepackage[extra1,extra2] to include its optional dependencies. pipdeptree strips the extras spec before matching the name, so wildcards such as somepackage*[extra1] still work. pipdeptree includes an extra requested through --packages even with --extras none:

$ pipdeptree --packages "requests[socks]" --extras none
requests==2.32.3
├── certifi [required: >=2017.4.17, installed: 2024.8.30]
├── charset_normalizer [required: >=2,<4, installed: 3.4.0]
├── idna [required: >=2.5,<4, installed: 3.10]
├── PySocks [required: >=1.5.6,!=1.5.7, installed: 1.7.1, extra: socks]
└── urllib3 [required: >=1.21.1,<3, installed: 2.7.0]

Excluding packages

Use --exclude (-e) to hide specific packages:

$ pipdeptree --exclude pip,pipdeptree,setuptools,wheel
covdefaults==2.3.0
└── coverage [required: >=6.0.2, installed: 7.15.1]
cryptography==2.7
diff_cover==10.3.0
├── chardet [required: >=3.0.0, installed: 7.4.3]
├── Jinja2 [required: >=2.7.1, installed: 3.1.6]
│   └── MarkupSafe [required: >=2.0, installed: 3.0.3]
├── pluggy [required: >=0.13.1,<2, installed: 1.6.0]
└── Pygments [required: >=2.19.1,<3.0.0, installed: 2.20.0]
nab-python==0.0.11
├── build [required: >=1.2, installed: 1.5.1]
│   ├── packaging [required: >=24.0, installed: 26.2]
│   └── pyproject_hooks [required: Any, installed: 1.2.0]
├── installer [required: >=0.7, installed: 1.0.1]
├── nab-index [required: ==0.0.11, installed: 0.0.11]
│   ├── packaging [required: >=24.0, installed: 26.2]
│   ├── truststore [required: >=0.10, installed: 0.10.4]
│   ├── typing_extensions [required: >=4.6, installed: 4.16.0]
│   └── urllib3 [required: >=2.0, installed: 2.7.0]
├── nab-resolver [required: ==0.0.11, installed: 0.0.11]
│   └── typing_extensions [required: >=4.6, installed: 4.16.0]
├── pyproject_hooks [required: >=1.2, installed: 1.2.0]
├── tomli [required: >=2.0, installed: 2.4.1]
├── tomli_w [required: >=1.2, installed: 1.2.0]
└── typing_extensions [required: >=4.6, installed: 4.16.0]
oauthlib==3.0.0
pyjwt==1.7.1
PySocks==1.7.1
pytest-cov==7.1.0
├── coverage [required: >=7.10.6, installed: 7.15.1]
├── pluggy [required: >=1.2, installed: 1.6.0]
└── pytest [required: >=7, installed: 9.1.1]
    ├── iniconfig [required: >=1.0.1, installed: 2.3.0]
    ├── packaging [required: >=22, installed: 26.2]
    ├── pluggy [required: >=1.5,<2, installed: 1.6.0]
    └── Pygments [required: >=2.7.2, installed: 2.20.0]
requests==2.32.3
├── certifi [required: >=2017.4.17, installed: 2024.8.30]
├── charset_normalizer [required: >=2,<4, installed: 3.4.0]
├── idna [required: >=2.5,<4, installed: 3.10]
└── urllib3 [required: >=1.21.1,<3, installed: 2.7.0]

Use --exclude-dependencies to hide their transitive dependencies:

$ pipdeptree --exclude pipdeptree --exclude-dependencies
covdefaults==2.3.0
└── coverage [required: >=6.0.2, installed: 7.15.1]
cryptography==2.7
diff_cover==10.3.0
├── chardet [required: >=3.0.0, installed: 7.4.3]
├── Jinja2 [required: >=2.7.1, installed: 3.1.6]
│   └── MarkupSafe [required: >=2.0, installed: 3.0.3]
├── pluggy [required: >=0.13.1,<2, installed: 1.6.0]
└── Pygments [required: >=2.19.1,<3.0.0, installed: 2.20.0]
oauthlib==3.0.0
pyjwt==1.7.1
PySocks==1.7.1
pytest-cov==7.1.0
├── coverage [required: >=7.10.6, installed: 7.15.1]
├── pluggy [required: >=1.2, installed: 1.6.0]
└── pytest [required: >=7, installed: 9.1.1]
    ├── iniconfig [required: >=1.0.1, installed: 2.3.0]
    ├── packaging [required: >=22, installed: 26.2]
    ├── pluggy [required: >=1.5,<2, installed: 1.6.0]
    └── Pygments [required: >=2.7.2, installed: 2.20.0]
requests==2.32.3
├── certifi [required: >=2017.4.17, installed: 2024.8.30]
├── charset_normalizer [required: >=2,<4, installed: 3.4.0]
├── idna [required: >=2.5,<4, installed: 3.10]
└── urllib3 [required: >=1.21.1,<3, installed: 2.7.0]

packaging no longer appears under pytest because the command excluded it as a transitive dependency of pipdeptree.

Reverse dependency lookup

Use --reverse (-r) with --packages to find the packages that require a dependency:

$ pipdeptree --reverse --packages pygments
Pygments==2.20.0
├── diff_cover==10.3.0 [requires: Pygments>=2.19.1,<3.0.0]
└── pytest==9.1.1 [requires: Pygments>=2.7.2]
    └── pytest-cov==7.1.0 [requires: pytest>=7]

Writing requirements files

Extract top-level packages with a zero-depth tree:

$ pipdeptree --depth 0
covdefaults==2.3.0
cryptography==2.7
diff_cover==10.3.0
oauthlib==3.0.0
pipdeptree==4.0.0
pyjwt==1.7.1
PySocks==1.7.1
pytest-cov==7.1.0
requests==2.32.3

Use freeze format for pip-compatible output:

$ pipdeptree -o freeze --warn silence | grep -E '^[a-zA-Z0-9\-]+' > requirements.txt

Freeze output is a human-readable lock file with indented dependencies:

$ pipdeptree --packages pytest -o freeze
pytest==9.1.1
  iniconfig==2.3.0
  packaging==26.2
  pluggy==1.6.0
  Pygments==2.20.0

Warning control

pipdeptree warns about conflicting and circular dependencies on stderr. Control this with -w:

  • -w suppress (default): show warnings and exit 0.

  • -w silence: hide warnings and exit 0.

  • -w fail: show warnings and exit 1 if pipdeptree finds a problem (useful in CI).

$ pipdeptree -w fail
$ echo $?
0

A conflict adds warnings and a non-zero exit code:

$ pipdeptree -w fail
Jinja2==2.11.2
└── MarkupSafe [required: >=0.23, installed: 0.22]
Warning: dependency problems found:
* Jinja2==2.11.2
  - markupsafe [required: >=0.23, installed: 0.22]
------------------------------------------------------------------------
$ echo $?
1

Use from Python or a notebook

Call pipdeptree.render() when a Jupyter cell or another environment has no command-line access. It returns the dependency tree as a string without argv or stdout:

import pipdeptree

print(pipdeptree.render())                       # text tree of the current env
data = pipdeptree.render(output_format="json")   # JSON string, e.g. for json.loads
sub = pipdeptree.render(packages="rich", reverse=True)

output_format accepts text (default), json, json-tree, mermaid and dot (Graphviz source). The programmatic renderer raises ValueError for binary Graphviz formats such as png or svg. Use dot to get the source, or use the command-line interface for binary rendering.

The return value is a str, so print, slicing and comparisons behave as usual. The default text format implements notebook display hooks: the result renders as a Mermaid dependency diagram without a Graphviz binary, falling back to an HTML <pre> and then plain text on front-ends that do not render Mermaid. str(render()) and print(render()) give the plain text tree. The other formats (json, json-tree, mermaid, dot) return a plain string with no rich display, so their JSON or source shows verbatim.

Unlike the CLI, pipdeptree.render() defaults to warn="silence" so a notebook cell stays free of stderr noise; pass warn="suppress" to show warnings or warn="fail" to fail on them.

Depth limiting

Limit how deep the tree renders with -d:

$ pipdeptree -d 1 --packages pytest-cov,pipdeptree
pipdeptree==4.0.0
├── nab-index [required: >=0.0.11, installed: 0.0.11]
└── nab-python [required: >=0.0.11, installed: 0.0.11]
pytest-cov==7.1.0
├── coverage [required: >=7.10.6, installed: 7.15.1]
├── pluggy [required: >=1.2, installed: 1.6.0]
└── pytest [required: >=7, installed: 9.1.1]

Use -d 0 to show top-level packages without dependencies:

$ pipdeptree -d 0 --packages pytest-cov,pipdeptree
pipdeptree==4.0.0
pytest-cov==7.1.0

Package metadata

Display metadata fields from the package’s METADATA file with --metadata (-m). Pass a comma-separated list of field names. The renderer appends metadata to each package in parentheses:

$ pipdeptree --metadata license --packages pipdeptree --depth 0
pipdeptree==4.0.0 (MIT License)

Combine multiple fields:

$ pipdeptree --metadata license,summary --packages pipdeptree --depth 0
pipdeptree==4.0.0 (MIT License, Display installed Python package dependencies as a tree)

Common metadata fields: license, summary, author, author-email, home-page, requires-python. pipdeptree accepts fields present in the package’s METADATA file.

Note

The --license flag still works for backwards compatibility. Use --metadata license instead; version 4 deprecates --license.

Computed fields

Display computed package information with --computed (-c):

  • size – installed size on disk (human-readable)

  • size-raw – installed size in bytes (integer, useful for JSON output)

  • unique-deps-count – number of dependencies exclusive to this package (hidden when there are none)

  • unique-deps-names – names of dependencies exclusive to this package (hidden when there are none)

  • unique-deps-size – total installed size of exclusive dependencies (hidden when there are none)

Unique dependencies are transitive: if removing a package would orphan a dependency, and that orphaned dependency would in turn orphan its own dependencies, the renderer counts all of them. Rich output marks unique dependencies with a ⭐ icon (alongside ✗ or ⚠ if applicable).

$ pipdeptree --computed size --packages pipdeptree --depth 0
pipdeptree==4.0.0 (0 B)
$ pipdeptree --computed unique-deps-count,unique-deps-names,unique-deps-size --packages pipdeptree --depth 0
pipdeptree==4.0.0 (10 unique deps, unique: build | installer | nab-index | nab-python | nab-resolver | pyproject-hooks | tomli | tomli-w | truststore | typing-extensions, unique size: 0 B)

Both --metadata and --computed work with each output format. You can combine them. In JSON output, size_raw and unique_deps_count are native integers, while unique_deps_names is a list of strings.

Including extras

The default, --extras explicit, follows an optional dependency when a parent requests that extra with name[extra]. Use --extras active to show optional groups whose complete dependency set is present:

$ pipdeptree --extras active --packages oauthlib
oauthlib==3.0.0
├── cryptography [required: Any, installed: 2.7, extra: signedtoken]
└── pyjwt [required: >=1.0.0, installed: 1.7.1, extra: signedtoken]

Optional edges carry the extra’s name. --extras none removes optional edges, while bare --extras is a compatibility spelling of --extras explicit. See How pipdeptree works for the three modes.

To surface one package’s extra, request it through --packages using the somepackage[extra] syntax from the “Filtering packages” section. pipdeptree shows that extra even with --extras none.