# 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](https://pydevtools.com/handbook/reference/uv.md) reads that environment directly, without a project file and without modifying it.

## Prerequisites

- uv installed. See [How to install uv](https://pydevtools.com/handbook/how-to/how-to-install-uv.md).
- An existing [virtual environment](https://pydevtools.com/handbook/explanation/what-is-a-virtual-environment.md), referred to below as `.venv`.

## Point uv at the environment, not the directory

`uv tree` is the wrong command here. It resolves a project's dependencies, writing a {{< term "lockfile" >}} if none exists, and stops when there is no project at all:

```console
$ 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:

```bash
uv pip tree --python .venv/bin/python
```
```powershell
uv pip tree --python .venv\Scripts\python.exe
```
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:

```console
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:

```bash
uv pip tree --python .venv/bin/python --invert --package markupsafe --show-version-specifiers
```
```powershell
uv pip tree --python .venv\Scripts\python.exe --invert --package markupsafe --show-version-specifiers
```
```console
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:

```bash
uv pip check --python .venv/bin/python
```
```powershell
uv pip check --python .venv\Scripts\python.exe
```
```console
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](https://pydevtools.com/handbook/reference/pipdeptree.md) adds license reporting, a one-command environment summary, Mermaid or Graphviz export, and a `--path` mode that needs no working interpreter:

```bash
uvx pipdeptree --python .venv/bin/python --summary
```
```powershell
uvx pipdeptree --python .venv\Scripts\python.exe --summary
```
Run it through [uvx](https://pydevtools.com/handbook/reference/uvx.md) 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:

```console
$ 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:

```bash
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

- [pipdeptree](https://pydevtools.com/handbook/reference/pipdeptree.md)
- [How to debug uv dependency resolution failures](https://pydevtools.com/handbook/how-to/how-to-debug-uv-dependency-resolution-failures.md)
- [How to migrate from requirements.txt to pyproject.toml with uv](https://pydevtools.com/handbook/how-to/migrate-requirements.txt.md)
- [uv pip tree documentation](https://docs.astral.sh/uv/reference/cli/#uv-pip-tree)
- [uv pip check documentation](https://docs.astral.sh/uv/reference/cli/#uv-pip-check)
