Python sibling relative import error: 'no known parent package'
Asked Answered
C

3

5

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.

Carroty answered 18/10, 2023 at 13:34 Comment(0)
S
7

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
Subdue answered 19/10, 2023 at 6:57 Comment(2)
Let me think about it but I think this might be the best solutions since it keeps the project structure intact and doesn't use sys.pathCarroty
This is the actual pattern for running a python script from inside a package @UmbertoFontanazzaSubdue
O
4

@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:

Ousel answered 18/10, 2023 at 15:16 Comment(0)
S
1

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()
Seer answered 18/10, 2023 at 14:26 Comment(5)
Instead of editing sys.path, it would be better to set the PYTHONPATH environment variable before running the script. If you still want to edit sys.path, then instead of appending to the end, it would be better to use sys.path.insert(1, os.path.dirname(SCRIPT_DIR)) so that the local directory is early in the search path, instead of last, where another installed package of the same name might be imported instead of the local file.Ousel
What is generating the error in my example though?Carroty
@UmbertoFontanazza main.py is not importing bar as part of a package because the import statement doesn't include a . As a result bar cannot import foo using a relative import, as relative imports are only valid within a package.Ousel
@UmbertoFontanazza read through the "Accessing a module NOT through its containing package" section in the top-voted answer to the question you linked. That covers exactly this problem.Bombshell
Editing sys.path can lead to subtle bugs and is only useful in very special cases (when you have no control on a library's position in the file system). Moving the main out of the package breaks encapsulation. The correct solution is simply using the -m switch when running a script inside a package (running from the package parent dir)Subdue

© 2022 - 2024 — McMap. All rights reserved.