You ran 'uv init' or 'uv venv' because a tutorial, or your AI assistant, told you to. The next line in the tutorial says 'pip install requests'. You paste it and one of two things happens.
The kind one:
$ .venv/bin/pip --version bash: .venv/bin/pip: No such file or directory $ .venv/bin/python -m pip --version /tmp/uvt/demo/.venv/bin/python: No module named pip
You search the error and the first answer says "use uv pip" or "add --seed". Both work. Neither tells you why pip was missing, and that matters, because the second thing that can happen is worse.
The unkind one: the install succeeds. You see a wall of "Requirement already satisfied", you run your script, and you get 'ModuleNotFoundError: No module named requests'. Nothing failed, and nothing worked.
One note on versions: everything below ran on uv 0.11.32, and the interpreter uv picked for my project — the Python the project actually runs on — was a 3.15 beta. Your numbers will differ. The pattern won't.
The whole fix is two commands you'll meet below — 'which pip' and 'python -m pip --version' — but they only land once you've seen why pip went missing. Let's open the hood.
What a virtual environment actually is
Two definitions first, since every tutorial skips them.
A package manager is the program that downloads other people's code (packages, like 'requests') and puts it where Python can find it. 'pip' is one. 'uv' is another.
A virtual environment (venv) is a folder holding its own Python and its own set of installed packages, so one project's libraries don't leak into another's. Think of plumbing: the venv is the pipe run for your project, and pip is the tap you screw onto it to let water in.
Here is the thing nobody mentioned. 'python -m venv' fits a tap to every pipe run it builds. 'uv venv' ships the pipe without one. I made a fresh project on uv 0.11.32 and listed the folder:
$ uv init demo && cd demo $ uv venv $ ls .venv/bin activate activate.bat activate.csh activate.fish activate.nu activate.ps1 activate_this.py deactivate.bat pydoc.bat python python3 python3.15
Python is in there. No 'pip'. For comparison, the standard 'python3 -m venv' on my 3.11 box listed 'pip', 'pip3' and 'pip3.11' right in its 'bin' folder. uv does its own installing, so on uv 0.11.32 it ships none by default. It isn't a bug, and it isn't your mistake.
Sure, a missing tool is easy to fix. But a missing tool is also the lucky outcome, because of what happens next.
Where the second failure comes from
When you type 'pip', your shell doesn't know about your project. It walks a list of folders called the PATH, left to right, and runs the first thing named 'pip' it finds. Activating a venv — running 'source .venv/bin/activate', which switches your shell into the project's environment — puts the venv's 'bin' folder at the front of that list.
If the venv has a 'pip', it wins. If it has none, the shell keeps walking and grabs some other pip further down the list. That pip belongs to a different Python, and it will install into that Python without complaint.
I ran exactly this, with the venv activated:
$ source .venv/bin/activate $ which python /tmp/uvt/demo/.venv/bin/python $ which pip /usr/bin/pip
Look at the two lines together. 'python' is the project's. 'pip' is not. They have come from different places, and your terminal didn't say a word.
Then the install:
$ pip install requests Requirement already satisfied: charset_normalizer<4,>=2 in /root/.local/lib/python3.13/site-packages ... $ python -c "import requests" ModuleNotFoundError: No module named 'requests'
That 'python3.13' in the path is the giveaway. The pip that answered me belongs to Python 3.13. My project's Python is 3.15. The package went (or in my case, was already) in the wrong house.
The two commands
This is the whole diagnostic. Run both before you trust any 'pip install'.
1. Which pip answered?
which pip
You want a path inside your project, ending '.venv/bin/pip'. If it points at '/usr/bin', 'Library', 'AppData' or anything outside the project, you are about to install into something else. If it prints nothing, there is no pip on your PATH at all, which is the kind failure.
On Windows, the same job is done by 'where pip' in Command Prompt or 'Get-Command pip' in PowerShell. I only ran the Linux version, so check the output matches the same rule rather than taking my paths literally.
2. Does pip belong to the Python you'll run?
python -m pip --version
Two outcomes. Either it fails with 'No module named pip', meaning this Python has no pip, or it prints a line like this one, which I got from a seeded venv:
pip 26.2.2 from /tmp/uvt/seeded/.venv/lib/python3.15/site-packages/pip (python 3.15)
Read the end of that line: the version in brackets and the folder before it. They should match your project's Python and live inside your project, which here means the path runs through '.venv'. The habit works because 'python -m pip' asks the Python you named to run its own pip, so it can't wander off to another one. That makes it the safest spelling of the command, and the first thing to ask is always: 'python -m pip install', or just 'pip install'?
So the rule is short. If 'which pip' points outside your project, or 'python -m pip --version' names a different Python than the one that runs your script, stop. You've found the wrong Python before it cost you an afternoon. The cure is the next section: reach for 'uv add requests' instead of a bare 'pip install', and uv puts the package in the project's environment for you — no PATH guessing.
Why this matters
An error message at least tells you where to look. The silent version tells you nothing, and beginners read "Requirement already satisfied" as good news.
The failure this check prevents is installing into the wrong Python and then debugging your own code for something that was never your code's fault. I'd rather you spend ten seconds on two commands than an hour rewriting a correct import.
The three uv equivalents
Once you know what you're looking at, here are the uv commands that sidestep pip entirely. I ran each on the same demo project.
'uv add requests' adds the package to the project: it installs it and records it in 'pyproject.toml' under dependencies. This is the one to reach for in a project you'll keep.
dependencies = [ "requests>=2.34.2", ]
'uv pip install requests' is the pip-compatible install. It puts the package into the environment but doesn't write it to 'pyproject.toml'. It targets the project's .venv, not whichever pip your PATH happens to find. I checked: after it, 'pyproject.toml' had no mention of requests, but the package imported fine from the venv. Handy for experiments, a trap if you forget to record the dependency.
'uv run python script.py' runs your script against the project's environment without you activating anything. 'uv run python -c "import requests; print(requests.version)"' printed '2.34.2' right after the install.
Rough guide: 'add' to keep it, 'pip install' to try it, 'run' to use it.
And if you'd rather keep pip
'uv venv --seed' puts pip inside the venv. I tried it and got 'pip 26.2.2' in '.venv/bin'. It works, but it makes the missing-tap problem disappear without teaching you to look. Run the two commands anyway.
Final thought
You don't have to switch from pip, but you do have to know which one you're talking to.
Your next step: open a terminal in any Python project, activate its environment, and run 'which pip' and 'python -m pip --version'. If the two don't agree, you've just found your next bug before it arrived.