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 standardrequirements.txtor.instyle file;--pyproject FILE– apyproject.tomlhanded to the resolver, which reads[project].dependenciesand 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:
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:
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:
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:
lock-version = "1.0"
$ pipdeptree from-lock pylock.toml
missing 'packages' array
pipdeptree reports the parse error for an invalid package entry:
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.