ImportError: How to import objects from any file located anywhere in a Python project

If you get import errors like ImportError: attempted relative import with no known parent package or ImportError: Attempted relative import in non-package, then you are running into Python’s relative import restrictions. Ugh! Python’s import system can be confusing, even for people who have experience with Python.

If you are working on a web project that uses a framework like FastAPI (or something similar), then you probably won’t run into import errors because the project files are usually organized to handle imports properly. However, if you are working on an analytics or data science project and your files and folders are not organized to handle Python imports properly, then you will need to find a solution to resolve relative imports.

When you import a module, Python will search for that module in the following locations:

  1. sys.modules: This is a cache of all modules that have been previously imported.
  2. Built-in modules: If the name isn’t found in sys.modules, then Python will proceed to search through a list of built-in modules. These are modules that come pre-installed with Python and can be found in the Python Standard Library.
  3. sys.path: If the name still isn’t found in the built-in modules, Python then searches for it in a list of directories defined by sys.path. This list usually includes the current directory, which is the directory from which the script was run.

So how do you get imports to work from anywhere? Let’s use this project directory as an example:

project_root/
├── main.py
├── shared/
│   └── constants.py
├── compute/
│   └── calculations.py
└── alter/
    └── modifications.py

Let’s say that you have some constant values in the constants.py file that you want to import into the calculations.py and modifications.py files. You can modify sys.path at runtime so that it contains the parent directory for constants.py. For example, you can add the following code at the top of the calculations.py and modifications.py files:

1
import os
2
import sys
3

4
# Convert the relative path to the shared directory to an absolute path.
5
# (i.e. Get the parent directory for constants.py).
6
SHARED_DIR_PATH = os.path.abspath("../shared")
7
# Add the parent directory to sys.path.
8
sys.path.append(SHARED_DIR_PATH)
9

10
# Verify that the shared directory is in sys.path.
11
print("sys.path:", sys.path)
12

13
# Now you can import any values from constants.py into your module.
14
from constants import cdn_url, cdn_folder
15

16
# The rest of your code goes here...

This is another, slightly altered, option that references the relative path to the file instead of the relative path to the file’s parent directory:

1
import os
2
import sys
3

4
# Define the relative path to the file.
5
CONSTANTS_FILE_PATH = "../shared/constants.py"
6
# Get the absolute path to the parent directory.
7
CONSTANTS_PARENT_DIR = os.path.dirname(os.path.abspath(CONSTANTS_FILE_PATH))
8
# Add the parent directory to sys.path.
9
sys.path.append(CONSTANTS_PARENT_DIR)
10

11
# Verify that the shared directory is in sys.path.
12
print("sys.path:", sys.path)
13

14
# Now you can import any values from constants.py into your module.
15
from constants import cdn_url, cdn_folder
16

17
# The rest of your code goes here...

So you can work around Python’s relative import restrictions by adding the corresponding parent directory to sys.path. No more relative import errors! Yay!

P.S.

  • The following package might be worth checking out or the code might provide ideas if you want to create your own package: force-relative-import
  • You can also look at some other ideas that are shared in this StackOverflow post: Relative imports in Python 3

Sources: