Albert
Albert

Reputation: 68240

How to make `from . import utils` work

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

Answers (2)

dano
dano

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

Wan B.
Wan B.

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

Related Questions