ModuleNotFoundError: No module named 'fastapi' - the Three Unrelated Causes
You have installed FastAPI. You can see it in the lockfile. You installed it three times to be sure. And still, every time you run the app:
ModuleNotFoundError: No module named 'fastapi'
This error is uniquely infuriating because the message is a lie of omission. It says the module is not there, and you have proof it is. The truth is that the Python interpreter running your code cannot see FastAPI, which is a different and much more specific claim. Chasing "install it again" will not help, because the package was never the problem.
There are three genuinely unrelated causes hiding behind this one message, and the fix for each is different. The reason people go in circles is that they guess a fix for cause two while they actually have cause three. So do not guess. This article is a decision procedure: three checks, in order, that tell you exactly which one you have.
the interpreter running your code cannot see the package. Diagnose in this order:
- Cause 1: wrong/no venv active. Check
python -c "import sys; print(sys.executable)". - Cause 2: package genuinely not installed in that env. Check
pip show fastapiagainst the same interpreter. - Cause 3: stale editable-install metadata from switching pip/poetry/uv. Delete the venv and reinstall clean with one tool.
The exact error, and what it actually claims
Traceback (most recent call last):
File "app/main.py", line 1, in <module>
from fastapi import FastAPI
ModuleNotFoundError: No module named 'fastapi'
Read it as: "the interpreter that is running app/main.py has no fastapi on its import path." Everything below flows from finding out which interpreter that is and what it can see.
Cause 1: the wrong virtualenv (or none) is active
This is the most common and the least respected. Your editor's integrated terminal, a new shell, a sudo, a cron job, or a Dockerfile step can all run a different Python than the one you installed into. Ask the interpreter directly:
python -c "import sys; print(sys.executable)"
Expected output is a path inside your project's virtualenv, something like /home/you/project/.venv/bin/python. If instead you see /usr/bin/python3 or a global path, you are running the system Python, which of course has no FastAPI. Activate the right environment (source .venv/bin/activate, or select the interpreter in your editor) and try again.
A subtle variant: you run the app with python main.py but installed with uv into .venv without activating it. Use uv run python main.py, or activate the venv, so the interpreter and the install match.
Cause 2: the package genuinely is not in this environment
If the interpreter is correct, ask whether the package is actually present in that environment:
pip show fastapi
# or, if you manage the env with uv:
uv pip show fastapi
If this prints nothing (exit status 1), FastAPI is not installed in the active environment, full stop. This happens when you installed into a different venv, when a uv sync or poetry install ran against a different lockfile, or when the dependency is declared but the install step never ran in this environment (a very common Docker mistake, where the layer that copies code runs after, or without, the install layer). Install it into this environment and re-check:
uv pip install fastapi # or: pip install fastapi
uv pip show fastapi # now prints Name, Version, Location
Cause 3: stale editable-install metadata after switching tools
This is the one no generic article covers, and it is the one that produces the maddening "the interpreter is right, pip show finds it, and it still fails" situation, usually for your own package rather than FastAPI, in a src-layout project.
When you do an editable install (pip install -e ., uv pip install -e .), the tool writes a pointer that tells Python "this package's source lives over there, on disk." The mechanism differs by tool and era:
| Tool / era | How the editable pointer is recorded |
|---|---|
Legacy setup.py develop | an .egg-link file plus an easy-install.pth entry |
| Modern pip (PEP 660) | a .pth file or an __editable__ import finder |
| uv | records the editable install via direct_url.json and its own layout, not an .egg-link |
Now switch tools without cleaning up. You start with Poetry, later run uv sync against the same repo, maybe a colleague used plain pip. Each tool assumes it owns the environment. The old .pth or .egg-link can point at a src/ directory that moved, or reference the package under a layout the new tool did not build. The pointer resolves to nothing, or to the wrong place, and import yourpackage (or an import of FastAPI re-exported through your package) fails even though the metadata says it is installed. Do not assume the failure modes are identical across tools; they are not, which is exactly why the symptom is so slippery.
The fix: one clean environment, one tool
Do not hand-edit .pth files. Blow the environment away and rebuild it with a single tool:
# From the project root
rm -rf .venv
# Pick ONE tool and stick with it. With uv:
uv venv
uv pip install -e .
# ...or with pip:
python -m venv .venv && source .venv/bin/activate
pip install -e .
Because the src-layout is a frequent partner to this bug, confirm your project actually declares its package location so the editable install knows what to point at. In pyproject.toml a src layout typically needs the package directory declared, for example under your build backend's configuration (setuptools, hatchling, and so on). If that declaration is wrong, even a clean editable install will not find your package.
Verification
Prove all three facts line up: right interpreter, package present, import works.
python -c "import sys; print(sys.executable)" # your .venv path
python -c "import fastapi; print(fastapi.__version__)" # prints a version
python -c "import yourpackage; print(yourpackage.__file__)" # points into src/
If all three succeed, the import path is coherent and the error is gone for a real reason, not by coincidence. Run your app the same way you verified (uv run ... or with the venv activated) so you do not slip back into cause one.
What people get wrong
Reinstalling FastAPI over and over. If the interpreter is wrong (cause one) or an editable pointer is stale (cause three), reinstalling the package changes nothing, because the package was never missing. Diagnose the interpreter and the metadata first.
Mixing package managers on one project. Running poetry add today and uv sync tomorrow on the same repo produces divergent lockfiles and leftover editable pointers. Pick one tool per project and delete the others' artifacts (poetry.lock vs uv.lock, and any stale .venv). Consistency here is worth more than any single tool's features.
Adding the directory to PYTHONPATH to force the import. Setting PYTHONPATH=./src makes the import succeed in your shell and hides the fact that the install is broken. It will fail again in Docker, in CI, and for the next person, because you papered over the missing editable pointer instead of fixing it. If you need PYTHONPATH to import your own installed package, the install is wrong.
When it is still broken
- It imports in the shell but fails under Uvicorn. Uvicorn may be launching a different interpreter, or your app string points at a module the process cannot reach from its working directory. That is a separate, well-defined failure; I cover it in Error loading ASGI app: why Uvicorn cannot import your FastAPI module.
- Works locally, fails in Docker. Almost always the install layer and the code-copy layer are out of order, or you install into the system Python and run a different one. Confirm the
pip installand theCMDuse the same interpreter. - Only one specific import fails. Then it is not this class of bug at all; it is a genuinely missing transitive dependency or a name collision with a local module named the same as a package. Rename any local
fastapi.pyorqueue.pyshadowing a real package.
The discipline that ends this loop is refusing to fix before you diagnose. Interpreter, presence, metadata: check them in that order, and the three-headed error resolves into whichever single, specific problem you actually have.
Frequently asked questions
- Why do I get ModuleNotFoundError: No module named 'fastapi' when it is clearly installed?
- Because the Python running your code is not the Python that has fastapi installed. The three usual causes are: the wrong or no virtualenv is active, the package genuinely is not in the active environment, or a stale editable-install pointer left over from switching between pip, poetry and uv. Run python -c "import sys; print(sys.executable)" first to see which interpreter is actually running before assuming anything.
- How do I tell which of the three causes I have?
- Check the interpreter with python -c "import sys; print(sys.executable)" and confirm it is your venv. Then run pip show fastapi (or uv pip show fastapi) against that same interpreter. If the interpreter is wrong, it is cause one. If the interpreter is right but pip show finds nothing, it is cause two. If pip show finds it but import still fails, or your own package fails to import, it is cause three: stale editable metadata.
- Why does the error start after I switched from poetry to uv?
- Switching package managers on the same project without deleting the old virtualenv leaves behind that manager's editable-install metadata. Poetry and pip use .egg-link or .pth files; uv records editable installs differently via direct_url.json and its own layout. The old pointers can reference paths that moved or that the new tool does not understand, so imports that used to resolve silently break. Delete the old venv and reinstall cleanly with one tool.
- How do I fix a broken editable install cleanly?
- Delete the virtualenv directory entirely, recreate it, and reinstall from scratch with a single tool. For example rm -rf .venv, then uv venv, then uv pip install -e . (or the poetry/pip equivalent). Do not try to patch individual .pth or egg-link files by hand; a clean reinstall removes the stale pointers and rebuilds them correctly for the layout you actually have.