Python sibling relative import error: 'no known parent package'

2.3k views Asked by At

I want to import a module from a subpackage so I went here Relative importing modules from parent folder subfolder and since it was not working I read all the literature here on stack and found a smaller problem that I can reproduce but cannot solve.

I want to use relative imports because I don't wanna deal with sys.path etc and I don't wanna install every module of my project to be imported everywhere. I wanna make it work with relative imports.

My project structure:

project/
    __init__.py
    bar.py
    foo.py
    main.py

bar.py:

from .foo import Foo

class Bar():

    @staticmethod
    def get_foo():
        return Foo()

foo.py:

class Foo():
    pass

main.py:

from bar import Bar

def main():
    f = Bar.get_foo()

if __name__ == '__main__':
    main()

I am running the project code from terminal with python main.py and I get the following:

Traceback (most recent call last):
  File "**omitted** project/main.py", line 1, in <module>
    from bar import Bar
  File "**omitted** project/bar.py", line 1, in <module>
    from .foo import Foo
ImportError: attempted relative import with no known parent package

Why am I getting this error? It seems that bar doesn't recognize project as the parent package but:

  • __init__.py is in place
  • bar.py is being run as a module not a script since it is called from main and not from the command line (so __package__ and __name__ should be in place to help solve the relative import Relative imports for the billionth time)

Why am I getting this error? What am I getting wrong? I have worked for a while just adding the parent of cwd to the PYTHONPATH but I wanna fix this once and for all.

3

There are 3 answers

2
Mr_and_Mrs_D On BEST ANSWER

You should be running your project from the parent of project dir as:

$ python -m project.main # note no .py

This tells python that there is a package named project and inside it a module named main - then relative and absolute imports work correctly - once you change the import in main in either of

from .bar import Bar # relative
from project.bar import Bar # absolute
5
kevin41 On

I recommend looking at this thread on relative imports in python3. I was able to get your import working 2 different ways:

  1. Moving main.py to the parent directory and adjusting your import. In this case the structure is updated to a more traditional style where the package scripts are in their own directory.

Updated Structure:

main.py
project/
    __init__.py
    bar.py
    foo.py

main.py:

from project.bar import Bar

def main():
    f = Bar.get_foo()

if __name__ == '__main__':
    main()
  1. If you want to keep your current structure instead of having the package scripts in their own directory then try adding the parent directory of your package to PYTHONPATH

main.py:

import sys
import os
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.dirname(SCRIPT_DIR))
from project.bar import Bar

def main():
    f = Bar.get_foo()

if __name__ == '__main__':
    main()
0
nigh_anxiety On

@kevin41's answer is solid. I wanted to add some additional info that seemed a bit much for comments.

As a general rule, your top level script, the one being run by Python and which has __name__=="__main__", should NOT be inside any package.

By running main.py inside of package, the package itself is never imported.
Relative imports are package relative, not path relative. Python knows it is working with a package import and should use relative imports based on the __package__ special variable being set, and __package__ gets set based on using an import with a ., such as import package.bar, in which case __package__=="package"

The __package__ special variable is never set for the top level script, which is why main.py must use absolute imports. When main.py imports bar using an absolute import, the value of the __package__ special variable while bar is being processed is also None, because bar was not imported as part of a package, therefore it can't use relative imports either.

With some small changes we can observe how bar imports foo based on how main is importing bar.

bar.py

try:
    from .foo import Foo
    print("Using relative import")
except ImportError:
    from foo import Foo
    print("Using absoloute import")
print(f"{__package__=}  {__name__=}  {__file__=}")

class Bar():

    @staticmethod
    def get_foo():
        return Foo()

Output when running main.py as you have it configured:

Using absoloute import
__package__=''  __name__='bar'  __file__='c:\\Users\\...\\package\\bar.py'

Moving main.py outside of the package directory and changing the import to from package.bar import Bar, gives us the output

Using relative import
__package__='package'  __name__='package.bar'  __file__='c:\\Users\\...\\package\\bar.py'

The correct solution is to move your top level script outside of the package folder. If your application needs a primary script within the package, then that script should use relative imports and it should be imported by another top-level script outside of the package.

Example structure:

project/
    __init__.py
    bar.py
    foo.py
    main.py
app.py

main.py first line:

from .bar import Bar

app.py

from package.main import main

main()

Additional relevant posts: