Importing a local file within a module, in its entirety, in the fewest possible lines

Viewed 71

Say that I'm writing a custom PIP package which is structured like so:

.
|    setup.py
│
└─── src
|   |    __init__.py
|   |    useful_functions_i.py
|   |    useful_functions_ii.py
|   |    useful_strings.py
|
└─── tests
    |    unit_tests.py

Say that useful_strings looks something like this:

USEFUL_STRING_A = "useful-string-a"
USEFUL_STRING_B = "useful-string-b"
...

And useful_functions_i and useful_functions_ii contain functions which do things like:

def a_useful_function(a_string):
    if a_string == useful_strings.USEFUL_STRING_XYZ:
        return True
    return False

How should I import useful_strings into useful_functions_i and useful_functions_ii? (This isn't quite as straightforward a question as it might appear.)

If I use:

import useful_strings

That will work well if I try to run some of the code in src locally; it will raise an exception if I run pip install . and try to use the codebase as a PIP package.

The next alternative is:

from . import useful_strings

This works well if I try to use the codebase as a PIP package. The problem comes when I run the unit tests. I use PyTest; my pytest.ini starts with:

[pytest]
pythonpath = src
testpaths = tests
...

And, if I were writing a unit test for a_useful_function() as defined above, the corresponding test script would contain the following import syntax:

from src.useful_functions_i import a_useful_function

When running a unit test under these conditions, the from . import useful_strings syntax raises an exception.

I came across a workaround on another answer on this site. (I forget which one.) It looks like this:

if __package__:
    from . import useful_strings
else:
    import useful_strings

This does work, and it's a solution that's peppered throughout the repos I'm currently working on. But it feels hacky, repetitive and unpythonic. Is there a better way of doing this? (It also makes PyLint complain about import orders, and, while I can live with that, it's becoming an irritation.)

1 Answers

I think it's only running your code "locally" (I think this is the term you're using to say when you're developing/running from the command line) that's the problem. I'd use the following syntax to import packages:

from . import useful_strings

but you need to make sure Python knows that you're in a package when you run the code. So running something like:

python -m src.entrypoint

(i.e. instead of python src/entrypoint.py) and you should be okay.

I'd also suggest using "src-layout" or "flat-layout", as indicated in the setuptools docs. Creating a conftest.py file in the root can help pytest find the source code as well, so tests could import my_package instead of referring to src as well, see https://docs.pytest.org/en/latest/explanation/pythonpath.html for more details or search for PYTHONPATH in In pytest, what is the use of conftest.py files?.

Related