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

1

7

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.)

Jarodjarosite answered 23/8, 2022 at 14:47 Comment(2)
how are you expecting this package to be used? e.g. if I do a pip install ... what would I import to access a_useful_functionOverdevelop
@SamMason Either: (A) from my_package.useful_functions import a_useful_function, or (B) I put an from .useful_functions import a_useful_function in __init__.py and call from my_package import a_useful_function in the external code.Jarodjarosite
O
0

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?.

Overdevelop answered 25/8, 2022 at 12:41 Comment(2)
Thank you for a valiant effort, but I'm not a fan of this answer: (1) Typing out python -m src.entrypoint every time I want to run my code is arguably more cumbersome than my current solution, and it doesn't address the PyTest edgecase. (2) Am I not using src-layout already? (3) I know a conftest.py file can be a useful trick in some cases, but, in my situation, it doesn't help, and actually breaks some tests that had been working.Jarodjarosite
(1) my shell lets me just hit the "up" key or use the reverse search features to easily rerun commands, i.e. it's mostly two key presses. (2) you'd want your code in src/my_package and not directly under src. maybe see what other established projects do, e.g. github.com/python-attrs/attrs (3) yup, it's likely to change imports, but having test code be more similar to idiomatic usage often helps, e.g. you don't have to remember to do something different for tests / users can refer to tests to see how to use the library.Overdevelop

© 2022 - 2024 — McMap. All rights reserved.