1

I've read several links about this but haven't been able to find something that addresses my situation.

Here's my directory structure:

- app

  - __init.py
  - main.py

  - foo
    - __init.py
    - foo_library.py

  - bar
    - __init.py
    -  bar_library.py

My application starts in main.py which in turn imports / calls all the other files.

However, it seems that I must import all files relative to main.py.

E.g inside foo/foo_library, if I want to import bar/bar_library, then I can't do from ..bar import bar_library, or import app.bar.bar_library.

Instead I must do import bar.bar_library.py - i.e the path that would work if I was importing it from main.py instead of foo/foo_library.py.

Is there any way to use import app.bar.bar_library or from ..bar import bar_library?

Ali
  • 261,656
  • 265
  • 575
  • 769
  • Try `relative imports` from [these answers](https://stackoverflow.com/questions/7505988/importing-from-a-relative-path-in-python) – Leo Qi Mar 22 '21 at 20:14
  • top level scripts like `main.py` are not part of the package and should be placed in some other script directory. The package directory should be available on PYTHONPATH somewhere. Then, `main.py` will find the package based on normal python import rules. Normally I use [setuptools](https://setuptools.readthedocs.io/en/latest/userguide/index.html) to make a `setup.py` and install modules. Things are changing wtih [PEP 517](https://www.python.org/dev/peps/pep-0517/) so more reading may be required. – tdelaney Mar 22 '21 at 20:16
  • @LeonisSupreme I assume you mean `from ..bar import bar_library`? That doesn't work either – Ali Mar 22 '21 at 20:18
  • @LeonisSupreme -relative imports are fine within a package, but top level scripts like `main.py` aren't part of packages, so they don't work. – tdelaney Mar 22 '21 at 20:18
  • In your example, the package is `app`. Python adds the script directory to PYTHONPATH, so if you move `main.py` to the same directory that `app` is in, you can then do `import app` etc... in `main.py` and you can use relative imports within the package modules. – tdelaney Mar 22 '21 at 20:20
  • @tdelaney So if I moved `main.py` one level up and added the directory containing packages (called `app`) to sys.path, then `import app.foo.bar` would work from everywhere? – Ali Mar 22 '21 at 20:21
  • @tdelaney `main.py` *is* inside `app` right now but i can't do `app.bar.bar_library` inside `app.foo.foo_library` – Ali Mar 22 '21 at 20:22
  • @ClickUpvote - move `main.py` up, but don't add anything to `sys.path` - python will do that part for you. Then `import app.foo`. Now `foo_libary` can do `from ..bar import bar_library` because they are both part of the `app` package. – tdelaney Mar 22 '21 at 20:23
  • To be clear, I have `app/main.py` and inside `app/foo/foo_library.py` I want to import `app/bar/bar_library.py` – Ali Mar 22 '21 at 20:23
  • 1
    But `main.py` is not part of the package. It can import `foo` and `bar` because python adds the `app` directory to the python path. But if you do it this way, `foo` and `bar` are separate packages and you can't do relative imports with separate packages. You could have the bar code `import foo.whatever` (that is, use foo as a top level package), but that would mess up others who `import app` and try to use them as subpackages. – tdelaney Mar 22 '21 at 20:26
  • @tdelaney So I would move `main.py` up, then `import app.foo` inside `main.py`? And I'd also have to do `import app.bar` inside main.py if I wanted to import `bar_library` inside `foo_library`? – Ali Mar 22 '21 at 20:26
  • @tdelaney Actually, turns out I can't move main.py outside as I have a lot of other things going on (requirements.txt, .env, etc). Is there any way to make it work while keeping main.py where it is, e.g by adding the paths to sys.path? – Ali Mar 22 '21 at 20:32
  • If you write a `setup.py` and generate a distribution, you can mark `main.py` as an entry point. The installer will create a dummy `main.py` in the PATH that imports `app.main` and runs it. Its basically the same as if you ran `python3 -m app.main`. Now, since `main` is part of the package, it can do relative imports too. – tdelaney Mar 22 '21 at 20:42

1 Answers1

2

Relative imports only work within a single package. In your example, you have a package app with subpackages foo and bar. As long as a script or module imports app (e.g., import app), then modules in foo and bar can do relative imports.

Scripts are not in packages. This is the same model used generally with executables where the operating system is tasked with finding static dynamic libraries in a common LIB directory. So main.py has no inherent knowledge of the app package.

When python runs a script, it adds its path the sys.path so any module or directory in that path can be imported. In your case, main.py can import foo and import bar. But if you do that, foo and bar are separate packages and relative imports won't work between them.

You can solve the problem by moving main.py up one directory so that it can import app. More generally, you could move main.py into a script directory in your source tree and wrap your package + scripts into an installable distribution.

Another popular option when making a package installable, is to add entry point declarations to setup.py. The installer will add small dummy files in the operation system path that essentaily do python3 -m app.main to run the module within the package. Done this way, main.py is a part of the package and can do relative imports.

tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • @ClickUpvote - there are several tricks. At the top of `main.py` you could add `sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))` which would add main's grandparent to the path, making `import app` work. – tdelaney Mar 22 '21 at 20:47