Reputation: 189
I'm trying to import models from a folder in the parent directory. Im using sys.path.append(). My project structure:
-Project
In file1.py file:
sys.path.append('../Project')
from Project.folder2 import file2
I then get a:
ModuleNotFoundError: No module named Project
I know there are other ways but this seems like the simplest. I'm not sure if I need to put the absolute path to the Project folder, but I'm hoping not since I'll be running this Project on different computers (diff abs path).
Upvotes: 6
Views: 7431
Reputation: 535
I encountered the same issue in Windows OS. In addition to possible level injection as a parent, the problem with sys.path.append is that it creates a windows path which Python is not searching for some reason despite it being added to the sys.path. Instead try to use
ROOT_PATH = pathlib.Path(__file__).parents[1]
sys.path.append(os.path.join(ROOT_PATH, ''))
It will add file's grandparent to sys.path in a proper way.
Upvotes: 1
Reputation: 12263
TL;DR
You can solve this by creating a setup.py
file for your project and then running pip install -e .
, instead of modifying sys.path
.
Motivation
This answer might seem to come from left field for this question, but I'm putting it here because OP showed interest in this solution, and I think it's generally a preferable solution to mucking around with sys.path
.
Details
The solution I tend to prefer for my projects is to create an actual setup.py for them, as if I was going to publish them to PyPI, and then run pip install -e .
to have them actually installed in my (possibly virtual) python environment.
Here's a minimalist setup.py
similar to one I used before:
from setuptools import setup
setup(
name="project",
version="0.0.1",
python_requires=">=3.6",
author="Some cool dude",
long_description="Some cool project",
install_requires=["dep1", "dep2"],
)
In another project, I read my requirements.txt
file in my setup.py
:
from setuptools import setup
with open("requirements.txt") as f:
requirements = f.read().splitlines()
setup(
name="project",
version="0.0.1",
python_requires=">=3.6",
author="Some cool dude",
long_description="Some cool project",
install_requires=requirements,
)
With either solution, the setup.py
file is sibling to my project
directory (I use lowercase for my project names, always), both typically being at the root of my Git repo, or under a src
subdirectory.
cd to the directory where setyp.py
and project
are, and run
pip install -e .
and now from anywhere, you can import project
or its submodules and Python will find them.
Suggested reading
Lots more details can be found about setup.py
files at setup.py examples?
Upvotes: 2
Reputation: 10709
2 errors in your code:
Project
directory is not just 1-level up. From the point of view of file1.py
, it is actually 2 levels up. See this:$ cd ..
(venv) nponcian 1$ tree
.
└── Project
├── folder1
│ └── file1.py
└── folder2
└── file2.py
(venv) nponcian 1$ cd Project/folder1/
(venv) nponcian folder1$ ls ..
folder1 folder2
(venv) nponcian folder1$ ls ../..
Project
print(sys.path)
, it would display something like this:['/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '../Project', '.']
'../Project'
, so if python started searching for the target modules in folder2, it wouldn't still find them because how would it know where exactly is '../Project'
relative to.What you need to add is the absolute path. If your problem is it might change, it is fine because we don't need a fixed absolute path. We can get the absolute path through the location of the current file being executed e.g. file1.py
and then extracting the parent directory needed. Thus, this will work regardless if the absolute paths change because the way we are getting it is always relative to file1.py
. Try this:
Project/folder1/file1.py
from pathlib import Path
import sys
sys.path.append(str(Path(__file__).parent.parent.parent)) # 1. <.parent> contains this file1.py 2. <.parent.parent> contains folder1 3. <.parent.parent.parent> contains Project
... the rest of the file
Upvotes: 6