The install printed "Successfully installed". The script imported fine in one terminal. Then the editor's Run button, or a new shell, raised ModuleNotFoundError for the package you just installed, on the same machine and in the same project.
You have two interpreters on one machine, the system one and your project's venv, and pip install only touches the one that ran it.
Here is the measured version, on Linux with Python 3.14.9 on 2026-11-07. The venv module ships with Python, so there is nothing to install first (venv docs).
python3 -m venv .venv writes the venv into .venv/. Activating it is a shell change: source .venv/bin/activate on macOS and Linux, .venv\Scripts\activate on Windows. After that, python and pip mean the venv. Activation is convenient, not required, because .venv/bin/python is the same interpreter without it.
$ python3 --version
Python 3.14.9
$ python3 -m venv .venv
$ .venv/bin/pip install requests
The last line of the install output:
Successfully installed certifi-2026.10.23 charset_normalizer-3.5.2 idna-3.20 requests-2.34.2 urllib3-2.8.0
One pip install requests pulled five packages, and du -sh .venv reported 9.3M. Now run the same one-line probe with each interpreter, on the same machine in the same second:
$ .venv/bin/python -c "import requests; print(requests.__version__)"
2.34.2
$ python3 -c "import requests; print(requests.__version__)"
ModuleNotFoundError: No module named 'requests'
The first exits 0. The second exits 1. Nothing is wrong with the install. You asked a different Python.
The bug you will actually hit comes next. To find out which Python is running, many scripts read the shell variable VIRTUAL_ENV, which only exists while the venv is activated. Here is what that does.
Key takeaways
pip installonly ever touches the interpreter that ran it, so a project with a venv has two Pythons and the package landed in one of them.VIRTUAL_ENVis set by activation, not by the interpreter, so.venv/bin/python where.pystill reportssystem.sys.prefix != sys.base_prefixis the question to ask the interpreter itself, because it is true exactly when a venv started it.- The checker tests which interpreter is running, not whether your dependency versions are the right ones, and it cannot see the interpreter your editor runs.
Two files, one command
Save this as where.py. It decides by reading the environment, and it exits 0 only when it believes it is in a venv.
# where.py
import os
import sys
def which_interpreter():
if os.environ.get("VIRTUAL_ENV"):
return "venv"
return "system"
def main():
kind = which_interpreter()
print(kind)
return 0 if kind == "venv" else 1
raise SystemExit(main())
Save this next to it as check_where.py. It builds a throwaway venv, runs where.py with the venv's python and with the system one, and counts passes.
# check_where.py
import os
import subprocess
import sys
import tempfile
passed = 0
total = 0
def check(name, ok, detail=""):
global passed, total
total += 1
if ok:
passed += 1
print(f"PASS {name}")
else:
print(f"FAIL {name} <- {detail}")
root = os.path.dirname(os.path.abspath(__file__))
script = os.path.join(root, "where.py")
home = tempfile.mkdtemp(prefix="venvcheck-")
venv_dir = os.path.join(home, ".venv")
subprocess.run([sys.executable, "-m", "venv", venv_dir], check=True)
bindir = "Scripts" if os.name == "nt" else "bin"
venv_python = os.path.join(venv_dir, bindir, "python" + (".exe" if os.name == "nt" else ""))
inside = subprocess.run([venv_python, script], capture_output=True, text=True)
outside = subprocess.run([sys.executable, script], capture_output=True, text=True)
check("where.py runs when a venv calls it", inside.returncode == 0, f"exit {inside.returncode}")
check("a venv interpreter reports venv", inside.stdout.strip() == "venv", repr(inside.stdout.strip()))
check(
"the system interpreter reports system",
outside.returncode == 1 and outside.stdout.strip() == "system",
f"exit {outside.returncode}, said {outside.stdout.strip()!r}",
)
print()
print(f"{passed} of {total} checks passed")
raise SystemExit(0 if passed == total else 1)
Run python3 check_where.py. It exits 1:
FAIL where.py runs when a venv calls it <- exit 1
FAIL a venv interpreter reports venv <- 'system'
PASS the system interpreter reports system
1 of 3 checks passed
The venv's own python ran the script, and it still said system. source .venv/bin/activate changes your shell so that python means the venv. Calling .venv/bin/python directly never touches that shell variable, so VIRTUAL_ENV is empty. The script asked the shell, and the shell was never told.
Ask the interpreter instead. Inside a venv, sys.prefix points at the venv and sys.base_prefix points at the Python it was built from. Replace where.py with:
# where.py
import sys
def inside_venv():
return sys.prefix != sys.base_prefix
def main():
kind = "venv" if inside_venv() else "system"
print(kind)
return 0 if kind == "venv" else 1
raise SystemExit(main())
Run python3 check_where.py again. It exits 0:
PASS where.py runs when a venv calls it
PASS a venv interpreter reports venv
PASS the system interpreter reports system
3 of 3 checks passed
What this does not do
The checker tests which interpreter is running. It does not test whether your dependencies are the right ones, and it does not lock versions. It also cannot see the interpreter your editor is configured with, so the Run button still needs its own check.
I only ran this on Linux. The Windows branch in the checker (Scripts, python.exe) is there for you to try, and I did not verify it.
One more trap: pip freeze in a brand-new venv prints nothing. An empty requirements file is not proof of a broken install. After the requests install above, pip freeze printed exactly this:
certifi==2026.10.23
charset-normalizer==3.5.2
idna==3.20
requests==2.34.2
urllib3==2.8.0
Run the checker from inside the venv
Now run the same checker with the venv's own interpreter: .venv/bin/python check_where.py. One check fails, and the exit code is 1:
PASS where.py runs when a venv calls it
PASS a venv interpreter reports venv
FAIL the system interpreter reports system <- exit 0, said 'venv'
2 of 3 checks passed
The checker compares against sys.executable. In this run that is the venv python, so "the system interpreter" was never tested.
Before you debug the next import error, print sys.executable from the script that failed. Which Python is it, and is that the one you installed into?