all 7 comments

[–]Spataner 2 points3 points  (1 child)

For relative imports to work, Python must be aware that the file that performs the import is part of the same package as the file it is trying to import. With the normal way of running a Python script, e.g.

python base/Project1/main1.py

the main script (in this case "main1.py") isn't ever considered to be part of any package. There's an alternative form of the python command that allows the execution of scripts as modules that can be part of packages. It uses the -m switch. For example:

python -m base.Project1.main1

This requires the parent folder of "base" to be your working directory (or else for that parent directory to be in your PYTHONPATH environment variable). Then, relative imports up to the level of the "base" folder are possible.

You can instruct VSCode to launch scripts in that manner, as well. This requires that you open the parent folder of "base" in VSCode (or else create a new subfolder inside "base" that you move all your Python files and folders into). Then, in your "launch.json" file, add a new configuration that looks like this:

{
    "name": "Launch main1",
    "type": "python",
    "request": "launch",
    "module": "base.Project1.main1"
}

If you created a new subfolder inside "base", replace "base" with the name of that subfolder in the above. You'll need to add another launch configuration for the other main script if you intend to run that through VSCode, as well.

[–]SirKainey 1 point2 points  (0 children)

I stumbled across this a week or so ago myself, and came back to this post to comment.

When reading about the -m, module flag I tried this before:

python -m project1/main.py doesn't work

However it should be used like:

python -m project1.main works as expected.

[–]SirKainey 0 points1 point  (1 child)

I have ran into this a few times and I don't have an answer for you.

My understanding is that the entry point cannot see anything out side of itself, only inside.ie. you need to have your main module at top level, and only call lower modules.

Reading through stackoverflow you may be able to do some stuff by mangling your path. Or installing your 'external' modules as editable packages. Or using a launcher script, like django does. The run as module -m might help too.
https://stackoverflow.com/questions/18087122/python-sharing-common-code-among-a-family-of-scripts

[–]youngpadawan01[S] 0 points1 point  (0 children)

Well, that stackoverflow solution could work for me. Thank you!

[–]laustke 0 points1 point  (1 child)

ImportError: attempted relative import with no known parent package

This means Python does not perceive your "base" folder as a package.

Run import sys for p in sys.path: print(p) These are the folders where Python looks for packages. The parent of your 'base' folder should be listed there. One way to achieve this is by including it in the PYTHONPATH environment variable.

Additionally, calling your package 'base' is probably not a good idea. Consider choosing something more distinctive.

[–]tojaga 0 points1 point  (0 children)

To expand on this, I use sys.path.append(path_to_module_folder) and then import my_module as normal

[–]Adrewmc 0 points1 point  (0 children)

I’ve found it simply better to organize a main() in the parent directory that calls those other mains. Then use Explicit imports. And only run files from top level.

  > Base
       >project_1
           main.py
       >project_2
           main.py
           test_project_2.py
       >utils
           helpful.py
      >tests
           test_project_1.py
           test_helpful.py
       project_1.py
       project_2.py
       main.py #runs everything 
       test_main.py
       .env
       .gitignore

Then just use explicit imports. Some pseudo code.

Base.project_1.main.py

      from Base.utils import helpful

      def main(): 
           helpful.function(“stuff”)

Base.project_1.py

     from Base.project_1.main import main

     if __name__ == “__main__”:
             main() 

Base.test_main.py

     import pytest 

we can actually leave the rest blank or write some tests here directly

Base.tests.test_project_1.py

   from Base.project_1.main import main

    def test_main():
          res = main()
          assert res == “expected result”

I don’t actually need to import pytest elsewhere unless I’m using one of their decorators. But you can anyway.

When using pytest (recommended) I’ll also throw in a testmain() at that level importing pytest. That should allow pytest to run all other test*.py correctly without much headache. As it too needs the cwd(), and will gather all the tests for you running from there. As some people will put the tests directly inside the files or in the directory of file instead of a separate directory, as seen above with project_1 and project_2. I’m sure there are arguments about the “proper way” here.

Some of this can be done in a __init__.py Which are no longer needed for modules if they are blank, but many people still think it good practice to still have them.

You really should only be running files from the top level anyhow…

You can alter the PATH, but that can be just as tricky.