Python Error Guide: 'ModuleNotFoundError: No module named' — Fix Imports & Venvs
Fix Python 'ModuleNotFoundError: No module named' errors: install into the right virtualenv, fix PYTHONPATH and package layout, resolve import-name vs pip-name mismatches, and stop wrong-interpreter runs.
- #python
- #automation
- #troubleshooting
- #errors
Stuck on this Bash & Python Automation error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
A script that imports a library — one you’re sure is installed — dies immediately at import time with a traceback that ends in:
Traceback (most recent call last):
File "/opt/jobs/report.py", line 3, in <module>
import requests
ModuleNotFoundError: No module named 'requests'
The same error shows up for your own local packages, not just third-party ones:
ModuleNotFoundError: No module named 'app.utils'
ModuleNotFoundError (a subclass of ImportError since Python 3.6) means the interpreter searched every directory on sys.path and never found a package or module by that name. Ninety percent of the time the module is installed — just not for the interpreter that’s actually running. The rest is import-name vs pip-name mismatches, project-layout problems, or a missing PYTHONPATH. The fix is almost never “install it again blindly”; it’s “find out which Python is running and why it can’t see the module.”
Symptoms
- The script aborts at an
importline before any of its logic runs; exit code is1. - It works in your interactive shell / IDE but fails under cron, systemd, Docker, or CI — a different interpreter or environment.
pip install Xreports “Requirement already satisfied” yet the import still fails.- The missing name differs from what you pip-installed (
import cv2fails though you installedopencv-python;import yamlthough you installedPyYAML). - Your own subpackage fails (
No module named 'app.utils') even though the file clearly exists. - Running
python script.pyfails butpython3 script.py(or vice versa) works.
Common Root Causes
- Installed into a different environment — you
pip installed into the system Python or another venv, but the script runs under a different interpreter (the classic pip-and-python mismatch). - The virtualenv isn’t activated — cron/systemd/Docker call
/usr/bin/python3directly, bypassing the venv where the package lives. - Import name ≠ distribution name —
pip install Pillowbutimport PIL;pip install beautifulsoup4butimport bs4. The PyPI name and the import name are unrelated. - Project not installed / not on
sys.path— running a script deep inside a package soappisn’t importable, with nopip install -e .and noPYTHONPATH. - Missing
__init__.py(for classic packages) or running from the wrong working directory. - Shadowing — a local file named
requests.pyoremail.pyhijacks the import, or a leftover.pyc/__pycache__masks a rename. - Wrong Python version — installed for 3.11 but the script runs under 3.9 (separate
site-packages).
Diagnostic Workflow
First, find out exactly which interpreter runs the script and whether it can see the module. This one command settles most cases:
python3 -c 'import sys; print(sys.executable); print("\n".join(sys.path))'
Ask pip which interpreter it belongs to — the two must match. Use the module form so you pin the interpreter:
python3 -m pip --version
python3 -m pip show requests | grep -E 'Name|Location|Version'
If python3 -m pip show finds it but the import fails, the interpreter running your script differs from python3. Compare them directly:
which -a python python3
head -1 /opt/jobs/report.py # check the script's shebang
Confirm whether it’s an import-name vs pip-name mismatch by listing installed distributions:
python3 -m pip list | grep -i -E 'pillow|opencv|pyyaml|beautifulsoup'
For your own package failing to import, print the search path from the run location and check the layout:
cd /opt/jobs && python3 -c 'import sys; print(sys.path[0])'
ls -la app/ && test -f app/__init__.py && echo "has __init__" || echo "missing __init__"
Rule out a shadowing file in the current directory (a very sneaky cause):
ls requests.py email.py 2>/dev/null # a local file with a stdlib/3rd-party name shadows the real one
Example Root Cause Analysis
A nightly report works when run by hand but fails every night under cron:
Traceback (most recent call last):
File "/opt/jobs/report.py", line 3, in <module>
import pandas as pd
ModuleNotFoundError: No module named 'pandas'
Running it interactively works because the engineer’s shell activates a venv in ~/.venvs/reporting, whose python has pandas. Cron runs with a bare environment and no activation, so the shebang #!/usr/bin/python3 resolves to the system Python, which has no pandas.
Diagnosis confirms the mismatch:
# where pandas actually lives
~/.venvs/reporting/bin/python -m pip show pandas | grep Location
# Location: /home/deploy/.venvs/reporting/lib/python3.11/site-packages
# what cron would use
/usr/bin/python3 -c 'import pandas'
# ModuleNotFoundError: No module named 'pandas'
The wrong fix is sudo pip install pandas into the system Python — that pollutes the OS interpreter and drifts from the pinned project deps. The right fix is to make cron use the venv interpreter explicitly, either by shebang or by calling it directly:
# crontab: call the venv's python by absolute path, no activation needed
0 2 * * * /home/deploy/.venvs/reporting/bin/python /opt/jobs/report.py
Now cron runs the same interpreter that has the dependency, and the import resolves.
Prevention Best Practices
- Always install with
python -m pip, not a barepip, so the package lands in the same interpreter you’ll run — this eliminates the #1 cause. - Use a virtualenv per project and, for scheduled jobs, invoke its interpreter by absolute path (
/path/.venv/bin/python) instead of relying on activation in cron/systemd. - Pin dependencies in
requirements.txt/pyproject.tomland install from it in every environment (dev, CI, prod) so nothing is “installed on my machine only.” - Install your own project with
pip install -e .(with a properpyproject.toml) so its packages are importable from anywhere, instead of hackingPYTHONPATH. - Learn the name mismatches — keep a note that
PIL←Pillow,cv2←opencv-python,yaml←PyYAML,bs4←beautifulsoup4,sklearn←scikit-learn. - Never name a file after a module you import (
requests.py,email.py,queue.py); it will shadow the real one. - Match versions — verify
python3 --versionin dev, CI, and prod agree, since each minor version has its ownsite-packages.
Quick Command Reference
# Which interpreter runs, and its import search path
python3 -c 'import sys; print(sys.executable); print(sys.path)'
# Install into THIS interpreter (avoids the pip/python mismatch)
python3 -m pip install requests
# Is it installed for this interpreter, and where?
python3 -m pip show requests | grep -E 'Location|Version'
# List installed distributions (spot name mismatches)
python3 -m pip list | grep -i pillow
# Make your own package importable everywhere
python3 -m pip install -e .
# One-off path override (prefer editable install over this)
PYTHONPATH=/opt/jobs python3 /opt/jobs/report.py
# Detect a shadowing file in the cwd
ls requests.py email.py 2>/dev/null
Conclusion
ModuleNotFoundError: No module named almost never means “reinstall it” — it means the interpreter that’s running can’t see the module. Start by printing sys.executable and sys.path, then confirm with python3 -m pip show that the package lives in that same interpreter. The usual culprits are a venv that cron/systemd never activated, a pip that installed into a different Python, an import-name vs pip-name mismatch, or a project that was never installed. Standardize on python -m pip, one venv per project invoked by absolute path, and an editable install of your own code, and this error stops being a nightly surprise.
Fixed it? Get 500 Bash & Python Automation & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.