chadlei
chadlei

Reputation: 189

ModuleNotFoundError: No module named Project when using sys.path.append()

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

Answers (3)

Elcin Guseynov
Elcin Guseynov

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

joanis
joanis

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

Niel Godfrey P. Ponciano
Niel Godfrey P. Ponciano

Reputation: 10709

2 errors in your code:

  1. The 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
  1. Even if the above works, adding a relative path as a string would literally append that string as its raw value. So if you add a print(sys.path), it would display something like this:
['/usr/lib/python38.zip', '/usr/lib/python3.8', '/usr/lib/python3.8/lib-dynload', '../Project', '.']
  • It literally added '../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.

Solution

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

Related Questions