> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# ModuleNotFoundError Even Though You Definitely Installed It
- URL: https://debugly.dev/python-modulenotfounderror-installed-it/
- Published: 2025-08-26T08:45:00.000Z
- Updated: 2026-10-17T05:59:16.000Z
- Description: pip says it is installed, python says it is not. Both are telling the truth about different Pythons.
- Author: Rohit Bhadani
- Tags: Python, Error Autopsy, Debugging

## The quick version

```
ModuleNotFoundError: No module named 'requests'

```

You installed it into a different Python than the one running your code. Find out which:

```bash
which python; which pip
python -c "import sys; print(sys.executable)"
python -m pip --version

```

If `pip --version` reports a different path from `sys.executable`, that is your bug. Install with `python -m pip install requests` instead of `pip install requests`, which guarantees the same interpreter.

Tested on Python 3.13.2.

## Why this happens so often

Most systems have several Pythons. A macOS machine can easily have the system Python, a Homebrew Python, a pyenv Python, one per virtualenv, and one inside a Conda environment.

Each has its own `site-packages`. `pip` is a script with a shebang pointing at whichever interpreter it was installed for, and `python` is whatever your `PATH` resolves first. Nothing keeps them in sync.

The reliable fix is to stop invoking `pip` directly:

```bash
python -m pip install requests

```

`python -m pip` runs pip using the interpreter you just named, so the install always lands where the import will look. I would make this a habit even when things are working.

## Diagnosing it properly

```bash
python -c "import sys; print('\n'.join(sys.path))"

```

That is the exact list of directories Python searches, in order. If your package's `site-packages` is not in it, the import cannot succeed no matter what pip reported.

To see where a package actually landed:

```bash
python -m pip show requests | grep Location

```

Compare that against the `sys.path` output. If it is absent, you have confirmed the diagnosis.

For the venv case specifically:

```bash
echo $VIRTUAL_ENV
python -c "import sys; print(sys.prefix != sys.base_prefix)"

```

`True` means you are inside a virtual environment. `False` while `$VIRTUAL_ENV` is set means you activated a venv and then something changed `PATH`, which happens with some shell prompts and with `sudo`.

## The specific traps

### sudo pip

```bash
sudo pip install requests     # installs to the system Python, as root
python app.py                  # runs your venv Python

```

`sudo` resets the environment, so your venv is not active inside it. The package installs somewhere your code will never look, and you now also have root owned files in a system directory.

There is essentially never a good reason to `sudo pip install`. If you feel the need, use a venv or `pipx`.

### The venv was created with a different Python

```bash
python3.11 -m venv .venv
source .venv/bin/activate
python --version    # 3.11, permanently

```

A venv is bound to the interpreter that created it. Upgrading your system Python does not upgrade the venv, and a venv created against a Python you later uninstalled will break with confusing errors about missing shared libraries.

Recreate rather than repair:

```bash
rm -rf .venv && python3.13 -m venv .venv && source .venv/bin/activate
python -m pip install -r requirements.txt

```

### Your file shadows the package

```
myproject/
  requests.py     <-- this
  app.py

```

`import requests` finds your file first, because the script's directory is at the front of `sys.path`. The error is often not `ModuleNotFoundError` but something stranger inside the module, like `AttributeError: module 'requests' has no attribute 'get'`, which is a good tell.

Common offenders: `email.py`, `types.py`, `json.py`, `logging.py`, `queue.py`, `test.py`, `select.py`, `string.py`.

A stray `__pycache__` from a file you already deleted can also do this. Clear it:

```bash
find . -name "__pycache__" -type d -exec rm -rf {} +

```

### Editable install without the package installed

```
ModuleNotFoundError: No module named 'myapp'

```

Your own package, not a dependency. Running `python src/myapp/main.py` directly puts `src/myapp` on the path, not `src`, so `from myapp.utils import x` fails.

Install your project in editable mode:

```bash
python -m pip install -e .

```

Then imports resolve consistently regardless of where you run from. This requires a `pyproject.toml`, which you want anyway.

Alternatively run as a module, which puts the current directory on the path:

```bash
python -m myapp.main

```

Note that `PYTHONPATH=. python app.py` also works and is the version I would avoid, because it works on your machine and not in the container.

### Different Python in the container

Locally fine, in Docker `ModuleNotFoundError`. Usually one of:

```dockerfile
# installs for root, runs as appuser who cannot see it
RUN pip install -r requirements.txt
USER appuser

# correct
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
USER appuser

```

Or a multi stage build that copies the application without copying the installed packages:

```dockerfile
FROM python:3.13-slim AS build
RUN pip install --user -r requirements.txt

FROM python:3.13-slim
COPY --from=build /root/.local /root/.local     # this line is easy to forget
ENV PATH=/root/.local/bin:$PATH

```

This is one of the [causes of a build that works locally and fails in CI](https://debugly.dev/docker-build-works-locally-fails-ci/), and it usually presents at runtime rather than build time, which makes it slower to connect.

### Namespace package collisions

Two distributions installing into the same top level name, or a package installed both from PyPI and from a local checkout. Check for duplicates:

```bash
python -c "import mypkg; print(mypkg.__file__)"
python -m pip list | grep -i mypkg

```

If `__file__` points somewhere unexpected, you have two copies and the wrong one is winning.

## The modern answer

Most of this category disappears if you stop managing environments by hand.

**uv** has become my default. It creates the environment, resolves, installs, and runs, all bound to one interpreter:

```bash
uv venv
uv pip install -r requirements.txt
uv run python app.py

```

`uv run` guarantees the interpreter and the packages match, which removes the entire class of error this post is about.

**Poetry** and **PDM** do the same thing with more project management around it. **pipx** is the right tool for installing command line applications, so they get isolated environments instead of polluting your system Python.

Whichever you pick, the property that matters is that one command owns both the environment and the execution. The failure mode in this post exists because `pip` and `python` are two independent commands that can disagree.

## A diagnostic script

Worth keeping around, because it answers every question above at once:

```python
import sys, site, os
print("executable :", sys.executable)
print("version    :", sys.version.split()[0])
print("in venv    :", sys.prefix != sys.base_prefix)
print("VIRTUAL_ENV:", os.environ.get("VIRTUAL_ENV"))
print("site-packages:")
for p in site.getsitepackages():
    print("   ", p)
print("sys.path:")
for p in sys.path:
    print("   ", p or "(cwd)")

```

Run it with the same command that runs your application, not from your shell. If your app starts through gunicorn, a systemd unit, or an entrypoint script, run it there, because the environment those see is frequently not the environment you see.

That last point is the general lesson. This error is almost never about the package. It is about which interpreter is running and what it can see, and the fastest path to an answer is always to make the program tell you rather than to reason about what it should be.

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)