'Module not recognising root directory for Python imports
I have a Python project that uses the MicroKernel pattern where I want each of the modules to be completely independent. I import each of the modules into the kernel and that works fine. However, when I am in a module I want the root of the module to be the module dir. This is the part that is not working.
Project structure;
.
├── requirements.txt
├── ...
├── kernel
│ ├── config.py
│ ├── main.py
│ ├── src
│ │ ├── __init__.py
│ │ ├── ...
│ └── test
│ ├── __init__.py
│ ├── ...
├── modules
│ └── img_select
│ ├── __init__.py
│ ├── config.py
│ ├── main.py
│ └── test
│ ├── __init__.py
│ └── test_main.py
If I import from main import somefunction
in modules/img_select/test/test_main.py
I get the following error:
ImportError: cannot import name 'somefunction' from 'main' (./kernel/main.py)
So it clearly does not see the modules/img_select
as the root of the module, which leads to the following question:
How can I set the root for imports in a module?
Some additional info, I did add the paths with sys.path in the config files; kernel/config.py;
import os
import sys
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
MODULES_DIR = os.path.join(ROOT_DIR, '../modules')
sys.path.insert(0, os.path.abspath(MODULES_DIR))
modules/img_select/config.py;
import os
import sys
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.abspath(ROOT_DIR))
And my python version is 3.7.3
I do realise that there are a lot of excellent resources out there, but I have tried most approaches and can't seem to get it to work.
Solution 1:[1]
I'm not sure what main
you are trying to import from. I think python is confused from the pathing as well. How does test_main.py
choose which main to run? Typically when you have a package (directory with __init__.py
) you import from the package and not individual modules.
# test_main.py
# If img_select is in the path and has __init__.py
from img_select.main import somefunction
If img_select
does not have __init__.py
and you have img_select
in the path then you can import from main.
# test_main.py
# If img_select is in the path without __init__.py
from main import somefunction
In your case I do not know how you are trying to indicate which main.py
to import from. How are you importing and calling the proper config.py
?
You might be able to get away with changing the current directory with os.chdir
. I think your main problem is that img_select
is a package with __init__.py
. Python doesn't like to use from main import ...
when main is in a package. Python is expecting from img_select.main import ...
.
Working Directory
If you are in the directory modules/img_select/test/
and call python test_main.py
then this directory is known as your working directory. Your working directory is wherever you call python
. If you are in the top level directory (where requirements.txt lives) and call python modules/img_select/test/test_main.py
then the top level directory is the working directory. Python uses this working directory as path.
If kernel
has an __init__.py
then python will find kernel
from the top level directory. If kernel
is not a package then you need add the kernel
directory to the path in order for python to see kernel/main.py
. One way is to modify sys.path
or PYTHONPATH like you suggested. However, if your working directory is modules/img_select/test/
then you have to go up several directories to find the correct path.
# test_main.py
import sys
TEST_DIR = os.path.dirname(__file__) # modules/img_select/test/
IMG_DIR = os.path.dirname(TEST_DIR)
MOD_DIR = os.path.dirname(IMG_DIR)
KERNEL_DIR = os.path.join(os.path.dirname(MOD_DIR), 'kernel')
sys.path.append(KERNEL_DIR)
from main import somefunction
If your top level directory (where requirements.txt lives) is your working directory then you still need to add kernel
to the path.
# modules/img_select/test/test_main.py
import sys
sys.path.append('kernel')
As you can see this can change depending on your working directory, and you would have to modify every running file manually. You can get around this with abspath like you are doing. However, every file needs the path modified. I do not recommend manually changing the path.
Libraries
Python pathing can be a pain. I suggest making a library.
You just make a setup.py
file to install the kernel
or other packages as a library. The setup.py
file should be at the same level as requirements.txt
# setup.py
"""
setup.py - Setup file to distribute the library
See Also:
* https://github.com/pypa/sampleproject
* https://packaging.python.org/en/latest/distributing.html
* https://pythonhosted.org/an_example_pypi_project/setuptools.html
"""
from setuptools import setup, Extension, find_packages
setup(name='kernel',
version='0.0.1',
# Specify packages (directories with __init__.py) to install.
# You could use find_packages(exclude=['modules']) as well
packages=['kernel'], # kernel needs to have __init__.py
include_package_data=True,
)
The kernel
directory needs an __init__.py
. Install the library as editable if you are still working on it. Call pip install -e .
in the top level directory that has the setup.py
file.
After you install the library python will have copied or linked the kernel
directory into its site-packages
path. Now your test_main.py
file just needs to import kernel correctly
# test_main.py
from kernel.main import somefunction
somefunction()
Customizing init.py
Since kernel now has an __init__.py
you can control the functions available from importing kernel
# __init__.py
# The "." indicates a relative import
from .main import somefunction
from .config import ...
try:
from .src.mymodule import myfunc
except (ImportError, Exception):
def myfunc(*args, **kwargs):
raise EnvironmentError('Function not available. Missing dependency "X".')
After changing the __init__.py
you can import from kernel instead of kernel.main
# test_main.py
from kernel import somefunction
somefunction()
Solution 2:[2]
If you delete the NumPy (any library) from the site manager and save that folder in another location then use:
import sys
sys.path.append("/home/shubhangi/numpy") # path of numpy dir (which is removed from site manager and paste into another directory)
from numpy import __init__ as np
import numpy as np
arr = np.array([1, 2, 3, 4, 5])
print(arr)
print(type(arr))
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | RiveN |