Reputation: 68240
I have the following directory structure:
some-tools-dir/
base_utils.py
other_utils.py
some-tool.py
some-other-tool.py
some-other-tools-dir/
basetools -> symlink to ../some-tools-dir
yet-another-tool.py
In other_utils.py
, I have:
import base_utils
Now, in yet-another-tool.py
, I want to do:
import basetools.other_utils
That doesn't work, because Python does not recognize basetools
as a Python package.
So I add an empty basetools/__init__.py
.
Now, in other_utils
, I get the exception:
import base_utils
ImportError: No module named base_utils
So I change that line to:
from . import base_utils
And yet-another-tool.py
works now.
However, some-tool.py
does not work anymore. It imports other_utils
, and there I get the exception:
from . import base_utils
ValueError: Attempted relative import in non-package
Now, I can add this hack/workaround to some-tools-dir/*-tool.py
:
import os, sys
__package__ = os.path.basename(os.path.dirname(os.path.abspath(__file__)))
sys.path += [os.path.dirname(os.path.dirname(os.path.abspath(__file__)))]
__import__(__package__)
And in addition, make all local imports relative in those files.
That solves the problem, I guess. However, it looks somewhat very ugly and I have to modify sys.path
. I tried several variations of this hack, however, I want to support multiple Python versions if possible, so using the module importlib
becomes complicated, esp. because I have Python 3.2, and I don't like using module imp
because it's deprecated. Also, it only seems to become more complicated.
Is there something I'm missing? This all looks ugly and too complicated for a use-case which doesn't seem to be too uncommon (for me). Is there a cleaner/simpler version of my hack?
A restriction I'm willing to make is to only support Python >=3.2, if that simplifies anything.
Upvotes: 0
Views: 13501
Reputation: 94921
(Note that this answer was built by piecing together information from this answer and this question, so go up-vote them if you like it)
This looks a bit less hacky, and at least works with Python 2.7+:
if __name__ == "__main__" and __package__ is None:
import sys, os.path as path
sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
from some_tools_dir import other_utils
I think the main reason you're finding this difficult is because it's actually unusual to have executable scripts inside of a python package. Guido van Rossum actualy calls it an "antipattern". Normally your executable lives above the root directory of the package, and then can simply use:
from some_tools_dir import other_utils
Without any fuss.
Or, if you want to execute a script that lives in the package, you actually call it as part of the package (again, from the parent dir of the package):
python -m some_tools_dir.other_utils
Upvotes: 1
Reputation: 18845
Are you be able to add the top root path into PYTHONPATH ?
If so, you can then add
__init__.py
file into some-tools-dir (and/or some-other-tools-dir)
Then from other_utils.py you do
from some-tools-dir import base_utils
And in yet-another-tool.py you do
from some-tools-dir import other_utils
You can then remove the symlink, and have proper namespacing.
Upvotes: 0