Search code examples
pythonpython-3.xgit-submodulespython-modulepipenv

Python + Submodules: ImportError: attempted relative import with no known parent package


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.


Solution

  • 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