Skip to content

How to Inspect a Virtual Environment You Did Not Create

Inheriting a project often means inheriting a .venv directory with no pyproject.toml, no requirements.txt, and no record of why anything in it is installed. uv reads that environment directly, without a project file and without modifying it.

Prerequisites

Point uv at the environment, not the directory

uv tree is the wrong command here. It resolves a project’s dependencies, writing a lockfileA file that records the exact version of every installed package, so everyone working on the project gets identical installs. if none exists, and stops when there is no project at all:

$ uv tree
error: No `pyproject.toml` found in current directory or any parent directory

uv pip tree reads installed packages instead. The --python flag names the interpreter to inspect, so the environment never has to be activated. The path is resolved relative to the working directory, so run these from the directory holding .venv or pass an absolute path:

uv pip tree --python .venv/bin/python

Top-level entries are packages nothing else requires, which is the closest thing to a list of what someone chose to install. Ignore pip and anything else venv seeded when it created the environment:

flask v3.1.3
├── blinker v1.9.0
├── click v8.4.2
├── itsdangerous v2.2.0
├── jinja2 v3.1.6
│   └── markupsafe v3.0.3
├── markupsafe v3.0.3
└── werkzeug v3.1.8
    └── markupsafe v3.0.3
pip v26.0.1
requests v2.34.2
├── certifi v2026.7.22
├── charset-normalizer v3.5.1
├── idna v3.18
└── urllib3 v2.7.0

Trace a package back to whatever required it

--invert flips the tree so a package appears above the packages that depend on it. Adding --show-version-specifiers prints the range each dependent declared:

uv pip tree --python .venv/bin/python --invert --package markupsafe --show-version-specifiers
markupsafe v3.0.3
├── flask v3.1.3 [requires: markupsafe >=2.1.1]
├── jinja2 v3.1.6 [requires: markupsafe >=2.0]
│   └── flask v3.1.3 [requires: jinja2 >=3.1.2]
└── werkzeug v3.1.8 [requires: markupsafe >=2.1.1]
    └── flask v3.1.3 [requires: werkzeug >=3.1.0]

The specifiers decide whether a package can be upgraded. Every constraint on markupsafe here is a lower bound, and no dependent pins an exact version or caps the top of the range, so nothing blocks an upgrade. Lower bounds still block a downgrade: installing markupsafe==2.0.1 in this environment violates the >=2.1.1 that Flask and Werkzeug both declare.

Confirm nothing is missing or conflicting

An inherited environment may have been edited by hand. uv pip check verifies that every installed package has its requirements satisfied:

uv pip check --python .venv/bin/python
Checked 13 packages in 0.28ms
All installed packages are compatible

The command exits 0 when the environment is consistent and 1 when it is not, so it works as a gate in a script or CI job. uv pip tree --strict reports the same problems as warnings but still exits 0, which makes it the wrong choice for automation.

Reach for pipdeptree for licenses and diagrams

uv pip tree covers the tree, the reverse lookup, and the specifiers. pipdeptree adds license reporting, a one-command environment summary, Mermaid or Graphviz export, and a --path mode that needs no working interpreter:

uvx pipdeptree --python .venv/bin/python --summary

Run it through uvx rather than installing it into the environment under inspection, which would add pipdeptree and its own dependencies to the tree being reported.

Read an environment whose interpreter is gone

A copied or abandoned .venv often points at a Python that no longer exists. Every uv command here fails on it, and the suggested remedy would overwrite the environment:

$ uv pip tree --python .venv/bin/python
error: No virtual environment or system Python installation found for path `.venv/bin/python`; run `uv venv` to create an environment

Do not run uv venv. It replaces the environment being investigated. pipdeptree reads the package directory itself, with no interpreter involved:

uvx pipdeptree --path .venv/lib/python3.13/site-packages

Substitute the python3.x directory that exists in the venv. The tree comes back exactly as it would from a working interpreter, which is often enough to reconstruct a requirements file before rebuilding.

Warning

Always pass --python to pipdeptree. With no environment active it falls back to its own interpreter, prints a plausible tree for the wrong environment, and exits 0. When an environment is active it honors VIRTUAL_ENV and prints the interpreter it resolved, so the banner is worth reading before trusting the tree.

Learn More

Last updated on