3

I've been struggling with relative imports on python.

My project structure looks like this:

root_dir
├── main.py
├── Pipfile
├── Pipfile.lock
├── unit_tests
│   ├── __init__.py
│   ├── test_first.py
│   └── test_second.py
└── my_package
    ├── __init__.py
    ├── first.py
    ├── second.py
    └── third.py

I want to import a set of functions from a file that is located in my_package into test_first.py.

According to the official documentation the syntax should be the following:

from ..my_package import first

When I do that, I get the following exception:

Traceback (most recent call last):
  File "/home/user/dev/root_dir/my_package/unit_tests/first.py", line 8, in <module>
    from ..my_package import first
ImportError: attempted relative import with no known parent package

I have also tried to import the file by using the following syntax:

from root_dir.my_package import first

When I do that, I get the following exception:

ModuleNotFoundError: No module named 'root_dir'

It is important to mention that I am using pipenv to handle the virtual environment. Any idea why this is happening?

Thanks.

3 Answers3

2

First, I'll give you the code that worked for me. Then, I'll give a brief explanation. Here's a short function that should let you import from the root directory.

Solution

import os, sys

def access_root_dir(depth = 1):
    current_dir = os.path.dirname(os.path.realpath(__file__))
    parent_dir = os.path.dirname(current_dir)
    args: list = [parent_dir]
    for _ in range(depth):
        args.append('..')
    
    rel_path = os.path.join(*args)
    sys.path.append(rel_path) 

# At the top of test_first.py
access_root_dir(2)
import root_dir   

Since the root directory is a grandparent of test_first.py, you need to go to depth 2:

Explanation

I encountered the same problem in one of my projects and found that imports from a parent directory were fixed by altering the sys.path. Appending an absolute path to the root directory with something like sys.path.append(abs_path_to_root) doesn't seem to work unless everything is part of a package. Python won't access parent directories in imports by default.

# Case 1: Relative path to parent - works
current_dir = os.path.dirname(os.path.realpath(__file__))
relative_path = os.path.join(current_dir, '..')
sys.path.append(relative_path)
import parent_module_name # This works for me (Python 3.9)

# Case 2: Absolute path to parent - Doesn't work 
current_dir = os.path.dirname(os.path.realpath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.append(parent_dir)
import parent_module_name # raises ModuleNotFoundError
Unique Divine
  • 111
  • 1
  • 5
1

I Think the issue is that your root directory is not considered a python module.

In order to do the relative import you should add "__init__.py" to your root directory.

https://docs.python.org/3/reference/import.html

1

The current working directory is not a package. Use os.getcwd() to see what it is, but it is likely going to be root_dir.

In that case, you can import top-level packages directly by name without a relative import; you should directly import from my_package, like so:

from my_package import first

...or:

from my_package.first import do_something

Replicated file tree:

root_dir
├── main.py
├── my_package
│   ├── __init__.py
│   └── first.py
└── unit_tests
    ├── __init__.py
    └── test_first.py

Contents of main.py:

from unit_tests import test_first

if __name__ == "__main__":
    test_first.test_something()

If you attempt to do a relative import, i.e.,: from ..my_package import first you'll get:

ImportError: attempted relative import beyond top-level package
Thomas
  • 591
  • 1
  • 8
  • 20