You cannot cross the package boundary with a relative import: relative imports happen within the same package (sub-packages allowed, of course). It also doesn’t make sense in most configurations to import from the directory where your entry scripts (ie. main.py) are located.
The parlance you use combined with the behaviour you seem to expect makes me suspect there might be a more fundamental misunderstanding here. In import statements, Python expects packages and modules, not directories and files. As such, “import …mymod” does not import the file mymod.py from the parent directory but instead imports module “mymod” from the parent package.
This is an important abstraction to understand, and it also explains why what you’re trying doesn’t work: in my example, there’s a package called “world” and a package called “engine”. These are two separate packages, both of which are used by the main.py script (scripts are never part of a package, by the way). The fact that they happen to both be in the same directory really doesn’t matter at this point; as long as both packages are available in sys.path. (Which they are, in this case, since sys.path conveniently includes the current directory.)
As such, when you do “import …renderer_base” inside the “engine.renderers.planet_renderer” module, Python just resolves that to the canonical module name “engine.renderer_base”. If you try to go back up one more level, Python errors because there is no higher level in the “engine” package.
Just putting init.py in the directory of main.py doesn’t work because Python simply never encounters it; it would only work if the whole thing was a giant package that you imported as a whole, in which case the package root would be one level higher. (Of course, then the placement of the main.py script inside the package would seem rather odd, since one would usually only find modules inside of packages, rather than scripts.)
If there are two independent package trees and you want to import one from the other, you should use absolute imports (ie. “import a.b” without dot prefix) and put the other package on sys.path (not usually necessary if it happens to be in the current directory). This is why I used “import world.planet” inside your engine package: I’m really telling Python to search along sys.path for the separate “world” package and import from that.
This isn’t different when we’re talking about stand-alone modules. If you defined a module “utils” by placing a file utils.py alongside main.py, this is really a separate module and is searched along the path like any other module or package. So, it should be imported using “import utils” and not “import …utils” if it isn’t part of the package you’re trying to import it into.
Of course, an easy solution is to just put everything inside a giant package and place main.py outside of that, as you indicated in your original post. This seems a common approach, be it less modular.
I guess understanding more about the nature of the module you’re trying to import is necessary for me to judge the best course of action here. Is it a completely stand-alone module you’re trying to import, that you don’t think has place in a particular package? Then you’re absolutely fine placing it inside main.py, but you should use an absolute import like “import utils”.