Introduction
When you run python scripts/migrate.py from a project directory, Python adds scripts/ to sys.path, not the project root. This means imports like from app.models import User fail because app/ is not on the path. The same issue occurs in cron jobs, Docker entrypoints, and CI pipelines where the working directory differs from the project root.
Symptoms
ModuleNotFoundError: No module named 'app'when theappdirectory clearly exists- Import works from the project root but fails from subdirectories
- Works in IDE but fails from terminal
- Works with
python -m app.clibut notpython app/cli.py
``` $ python scripts/migrate.py Traceback (most recent call last): File "scripts/migrate.py", line 3, in <module> from app.models import db ModuleNotFoundError: No module named 'app'
$ ls app/ __init__.py models.py views.py # Directory exists but Python can't find it! ```
Common Causes
- Running scripts from subdirectories instead of project root
- Python adds the script directory to sys.path, not cwd
- Cron jobs running from home directory
- Docker COPY placing code in non-standard paths
- pytest conftest.py importing from src layout without editable install
Step-by-Step Fix
- 1.Run as module instead of script:
- 2.```bash
- 3.# WRONG - adds scripts/ to sys.path
- 4.python scripts/migrate.py
# CORRECT - adds project root to sys.path python -m scripts.migrate ```
- 1.Set PYTHONPATH explicitly:
- 2.```bash
- 3.# In terminal
- 4.export PYTHONPATH="/path/to/project:$PYTHONPATH"
- 5.python scripts/migrate.py
# One-liner PYTHONPATH=. python scripts/migrate.py
# In cron 0 2 * * * cd /path/to/project && PYTHONPATH=. python scripts/migrate.py ```
- 1.Add editable install for development:
- 2.```bash
- 3.# pyproject.toml
- 4.[project]
- 5.name = "myapp"
- 6.version = "0.1.0"
# Install in editable mode pip install -e .
# Now 'from app.models import User' works from anywhere python scripts/migrate.py # Works! ```
- 1.Programmatically fix sys.path (for scripts that must run standalone):
- 2.```python
- 3.import sys
- 4.import os
# Add project root to path project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if project_root not in sys.path: sys.path.insert(0, project_root)
# Now imports work from app.models import db ```
- 1.Use src layout for cleaner imports:
- 2.
` - 3.project/
- 4.├── src/
- 5.│ └── myapp/
- 6.│ ├── __init__.py
- 7.│ ├── models.py
- 8.│ └── cli.py
- 9.├── tests/
- 10.└── pyproject.toml
- 11.
` - 12.```bash
- 13.# pyproject.toml
- 14.[tool.setuptools.packages.find]
- 15.where = ["src"]
pip install -e . python -m myapp.cli # Always works with editable install ```
Prevention
- Use
python -minstead ofpython path/to/script.pyfor project code - Use editable installs (
pip install -e .) during development - Set
PYTHONPATHin Dockerfiles, CI configs, and cron jobs - Use
src/layout to separate package code from scripts - Add path fix logic to entry point scripts, not to application modules
- Test imports from different working directories in CI