0

I am trying to write a library (sort of) for my project. I would like to keep it as modularized as possible. I have a main.py in the root directory and I have two packages required for running the main. I am calling them package1 and package2. The packages in each of them will inherit from their own base packages. The overall tree is as following:

.
├── main.py
├── package1
│   ├── base_package1.py
│   ├── __init__.py
│   └── main_package1.py
└── package2
    ├── base_package2.py
    ├── __init__.py
    └── main_package2.py

In the file for each package, I am also writing the code to test them. But to test package 2, I also need package 1. My question is about how to import package 1 to package 2. main.py is currently empty. I will share the contents of the remaining files here.

# package1/__init__.py

from .main_package1 import *
# package1/base_package1.py

class BasePackage1:
    def __init__(self):
        print("init base package 1")
# package1/main_package1.py

from base_package1 import BasePackage1

class MainPackage1(BasePackage1):
    def __init__(self):
        super().__init__()
        print("init main package 1")

if __name__ == "__main__":
    # Test MainPackage1
    pkg = MainPackage1()
# package2/base_package2.py

class BasePackage2:
    def __init__(self):
        print("init base package 2")
# package2/main_package2.py

from base_package2 import BasePackage2

class MainPackage2(BasePackage2):
    def __init__(self):
        super().__init__()
        print("init main package 2")

if __name__ == "__main__":
    # Option 1
    import sys
    sys.path.append("../")
    from package1 import MainPackage1

    # Error:
    # ModuleNotFoundError: No module named 'base_package1'

    # Option 2
    from ..package1 import MainPackage1

    # Error:
    # ImportError: attempted relative import with no known parent package

What is the correct way to do this? Is there a standard way to do this?


Update: I have found a solution that works for now and posted it as an answer below. If you have a better solution, please post it.

Gautam Sreekumar
  • 536
  • 1
  • 9
  • 20
  • 1
    Your tree is fine for *development*, but for distribution, `main.py` should be installed as a script (say `/usr/local/bin/main.py`) and the packages as libraries (say `/usr/local/lib/python/site-packages/package{1,2}`. – chepner Jul 10 '22 at 17:46
  • @chepner Thank you. I may not be deploying the whole thing as a package. This is for a research project that would be on Github. But I want it to be clean and easy-to-use. – Gautam Sreekumar Jul 10 '22 at 17:53
  • 1
    try looking at the answers from the question: https://stackoverflow.com/questions/16981921/relative-imports-in-python-3 – Aking Jul 10 '22 at 17:53

2 Answers2

0

In package2/base_package2.py if you want to reference package1.base_package1 you can import that directly as your current reference point is where main.py is. Absolute imports (those which don't start with a dot) start from your working directory, so you can import package1 from anywhere in package2 and it will be the same.

The reason why you get

ImportError: attempted relative import with no known parent package

is because you are running package2/base_package.py as a main file, which would run as its own script. However, you have provided three files with paths given as package2/base_package.py. I'm assuming that the ones with __main__ are actually main_package1 and main_package2 respectively.

Ray
  • 311
  • 1
  • 3
  • 14
  • I don't know if I understood your comment fully. The `__main__`'s are in `main_package1.py` and `main_package2.py`. I tried `from package1.main_package1 import MainPackage1` in `main_package2.py`, but got the error: `ModuleNotFoundError: No module named 'package1'`. – Gautam Sreekumar Jul 10 '22 at 17:57
  • 1
    In your question, you don;t specify the contents of `package1/main_package1.py` and instead list 3 codeblocks with titles of `# package2/base_package2.py`. – Ray Jul 10 '22 at 19:22
  • Thanks for pointing it out, @Ray. I had copy-pasted those headers. I have updated the question. – Gautam Sreekumar Jul 11 '22 at 14:33
0

After trying a few other things, I found a solution that seems to be work. I will post it here but will not select it as the accepted answer in case someone comes up with a better and more elegant solution.

In package2/main_package2.py, I did the following import:

import sys
sys.path.append("../package1")
from main_package1 import MainPackage1
Gautam Sreekumar
  • 536
  • 1
  • 9
  • 20
  • Modifying `sys.path` is almost always a bad idea, as `..` could reference anywhere depending on where you run the script. – Ray Jul 10 '22 at 19:21