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.

4 Answers4

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
  • 151
  • 2
  • 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
  • 701
  • 2
  • 8
  • 23
0

Relative imports are only allowed within a package. A package must be self-sifficient and only relies on Standard Library or third-part packages either in global or virtual environment 'lib' folder. A package is required not to access any python mpdule around it, meaning a module inside of package must not import anything outside the top level folder of the package in a relative manner. To specifies top level folder of a package create __init__.py file at the root of it and put the following code inside of it:

# Marking the containing folder the top level folder of the package...
__package__ = ''

In your example you can have two packages: 'root_dir' and 'my_package'. So create __init__.py at 'root_dir' and you've already created __init__.py in 'my_package' and put the following inside of them:

# Marking the containing folder the top level folder of the package...
__package__ = ''

And now you can have the following relative import:

from ..my_package import first
Megacodist
  • 132
  • 5