'Import module inside another module in Python

I'm having a hard time trying to separate some utility code as a module. I have the the following file structure:

scripts/
      |
      +-- toggle_keyboard.py
      PyUtils/
            |
            +-- sistema.py
            +-- strings.py
            +-- test_sistema.py

Sistema.py needs a class in strings.py, so it begins with:

from strings import String

And the Pytest test_sistema.py needs sistema.py, so it starts with:

import sistema as sis

Now, this is all fine when I run the tests with PyTest. However, I can't use this module in toggle_keyboard.py. I start it with:

import PyUtils.sistema as sis

And that gets me no compilation errors. However, when I run it, this is what I get:

Traceback (most recent call last):
  File "toggle_keyboard.py", line 2, in <module>
    import PyUtils.sistema as sis
  File "/home/xxxxxx/scripts/PyUtils/sistema.py", line 2, in <module>
    from strings import String
ModuleNotFoundError: No module named 'strings'

Searching for similar problems online, I found that making use of "relative imports" could solve the problem. Indeed, I was able to run toggle_keyboard.py when I changed sistema.py with this:

from .strings import String

However, the test doesn't run anymore. When I execute PyTest now, I get:

Traceback:
/usr/lib/python3.6/importlib/__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
PyUtils/test_sistema.py:1: in <module>
    import sistema as sis
PyUtils/sistema.py:2: in <module>
    from .strings import String
E   ImportError: attempted relative import with no known parent package

What would be a solution to make both the main script and the module's test to work?



Solution 1:[1]

In your sistema.py file try:

from PyUtils.strings import String

and in test_sistema.py

import PyUtils.sistema as sis

and ensure your PyUtils directory contains an __init__.py file and that should fix the issue.

An alternative approach although one that is typically not recommended: Add the top 3 lines from below in any file in the scripts dir that imports from the PyUtils dir.

toggle_keyboard.py


import sys
from pathlib import Path  
sys.path.insert(0, Path(__file__).parent / "PyUtils")   

from PyUtils.systema as sis
from PyUtils.strings import String
...

then the files in PyUtils can go back to:

from strings import String
import sistema as sis

You would still want to have the __init__.py in PyUtils dir.

And another approach would be to catch exceptions on the imports and try alternatives like so....

sistema.py

try:
    from strings import String
except ImportError:
    from PyUtils.strings import String

test_sistema.py

try:
   import sistema.py as sis
except ImportError:
   import PyUtils.sistema as sis

This is actually the better option in your situation.

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