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 the app directory clearly exists
  • Import works from the project root but fails from subdirectories
  • Works in IDE but fails from terminal
  • Works with python -m app.cli but not python 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. 1.Run as module instead of script:
  2. 2.```bash
  3. 3.# WRONG - adds scripts/ to sys.path
  4. 4.python scripts/migrate.py

# CORRECT - adds project root to sys.path python -m scripts.migrate ```

  1. 1.Set PYTHONPATH explicitly:
  2. 2.```bash
  3. 3.# In terminal
  4. 4.export PYTHONPATH="/path/to/project:$PYTHONPATH"
  5. 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. 1.Add editable install for development:
  2. 2.```bash
  3. 3.# pyproject.toml
  4. 4.[project]
  5. 5.name = "myapp"
  6. 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. 1.Programmatically fix sys.path (for scripts that must run standalone):
  2. 2.```python
  3. 3.import sys
  4. 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. 1.Use src layout for cleaner imports:
  2. 2.`
  3. 3.project/
  4. 4.├── src/
  5. 5.│ └── myapp/
  6. 6.│ ├── __init__.py
  7. 7.│ ├── models.py
  8. 8.│ └── cli.py
  9. 9.├── tests/
  10. 10.└── pyproject.toml
  11. 11.`
  12. 12.```bash
  13. 13.# pyproject.toml
  14. 14.[tool.setuptools.packages.find]
  15. 15.where = ["src"]

pip install -e . python -m myapp.cli # Always works with editable install ```

Prevention

  • Use python -m instead of python path/to/script.py for project code
  • Use editable installs (pip install -e .) during development
  • Set PYTHONPATH in 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