6

I am having this project structure:

.
├── README.md
├── my_project
│   ├── __init__.py
│   ├── config.py
│   ├── integrations
│   │   ├── __init__.py
│   │   └── google_places.py
├── requirements.txt
└── scripts
    └── my_script.py

In my_script.py I have:

from my_project.integrations.google_places import GooglePlaces

However I get the following error when running python scripts/my_script.py:

ModuleNotFoundError: No module named 'my_project'

Locally I can pip install -e . my package (by adding a setup.py to the mix), however since I need to run this from GitHub Actions it feels it doesn't make sense to pursue the same avenue.

I've been reading about manipulating sys.path, but it seems a bit hackish.

Since this is a fairly common setup (I guess), what's the recommended way of fixing the above error? I can alter the project structure if it would make sense.

linkyndy
  • 17,038
  • 20
  • 114
  • 194

1 Answers1

2

This also surprised me, but after reading this pretty comprehensive answer https://stackoverflow.com/a/54613085/6180150, and into the linked python docs about the module search path in python, I think you have to adjust the PYTHONPATH. The problem is that the two python files reside in two separate packages, namely scripts and my_project, so my_script.py is not able to find the google_places.py, because the PYTHONPATH only contains the current package and not the parent folder and python will only search following directories (see the aforementioned search path docs):

  • The directory containing the input script (or the current directory when no file is specified).
  • PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
  • The installation-dependent default.

Also in this comment: Python: 'ModuleNotFoundError' when trying to import module from imported package the replier of above answer explains nicely that adjusting the PYTHONPATH is not really a hacky solution (although I would have also felt so), but the documented solution according to python, too.

If possible, in case I were in your shoes, I think I would move the my_script.py into the my_project package, like follows.

.
├── README.md
├── my_project
│   ├── __init__.py
│   ├── config.py
│   ├── integrations
│   │   ├── __init__.py
│   │   └── google_places.py
│   └── my_script.py
└── requirements.txt

Then you can adapt your import to:

from integrations.google_places import GooglePlaces

EDIT:

If you like to use the following file tree structure (or even the original should work).

.
├── README.md
├── my_project
│   ├── __init__.py
│   ├── config.py
│   ├── integrations
│   │   ├── __init__.py
│   │   └── google_places.py
│   └── scripts
│       └── my_script.py
└── requirements.txt

You can just modify your sys.path like this and then use the original import statement:

import sys
import os

sys.path.insert(0, os.getcwd())
from my_project.integrations.google_places import GooglePlaces
schilli
  • 1,700
  • 1
  • 9
  • 17
  • Thanks for your reply. I mention that `scripts` is not a package, since it doesn't contain any `__init__.py` file. But, I have moved `scripts` under `my_project`, made it a package, updated the import as per your suggestion and ran the scripts with `python my_project/scripts/my_script.py`, however it failed with: `ModuleNotFoundError: No module named 'integrations'` – linkyndy May 30 '21 at 19:55
  • Yes, that makes sense, because python will again search according to its search model: "The directory containing the input script (or the current directory when no file is specified)". Meaning it looks inside `my_project/scripts/` and the `PYTHONPATH`. If you need to keep it under `scripts`, you should update the `sys.path`. – schilli May 30 '21 at 20:35
  • @linkyndy I just added another sample code, which adapts the `sys.path`, so you can import the module successfully. It should also allow to use the original file tree structure. Let me know if it works :) – schilli May 30 '21 at 20:48
  • I ended up moving all scripts on the top level, no more nesting under `scripts`. Thanks for assisting into this! – linkyndy May 31 '21 at 19:16
  • That's what I would've also done. Feels less hacky :D – schilli May 31 '21 at 19:18