Why do these Python import scenarios work (or not) the way they do?

Viewed 219

Suppose a repository has a directory called ./example with an __init__.py file to signal that it is a package. The tests are in ./tests, which does not have an __init__.py file:

.
|-- README.md
|-- example
|   |-- __init__.py
|   `-- func.py
|-- setup.py
`-- tests
    `-- test_hello.py

./example/func.py is pretty simple:

def hello():
    return "hello!"

If I run an interactive interpreter in the root directory I can import example:

$ python
>>> import example
>>> example.hello()
'hello!'

I can also put a script in the root directory that imports example:

$ cat > temp.py
import example
print(example.hello())
^D

$ python temp.py
hello!

Here's where things get odd. If I run pytest in the root directory with no arguments it fails to import example:

$ pytest
ImportError while importing test module '/Users/gregwilson/example/tests/test_hello.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../conda/envs/stjs/lib/python3.9/importlib/__init__.py:127: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
tests/test_hello.py:1: in <module>
    import example
E   ModuleNotFoundError: No module named 'example'

However, if I run python -m pytest in the root directory it works:

$ python -m pytest
collected 1 item
tests/test_hello.py .

If I now add an empty file tests/__init__.py, the tests work both ways:

$ touch tests/_init__.py

$ python -m pytest
collected 1 item
tests/test_hello.py .

$ pytest
collected 1 item
tests/test_hello.py .

My questions are:

  1. Why does pytest fail but python -m pytest work before I add ./tests/__init__.py?
  2. Why does adding an __init__.py file to tests make pytest on its own work?

But wait, it gets weirder. I can make a directory ./temp and add a file ./temp/temp.py that imports example and tries to use it:

$ mkdir temp

$ cat > temp/temp.py
import example
print(example.hello())
^D

Running that directly doesn't work:

$ python temp/temp.py
ModuleNotFoundError: No module named 'example'

Running it with python -m does:

$ python -m temp.temp
hello!

But unlike the tests case, adding an __init__.py file to ./temp doesn't make it work:

$ touch temp/__init__.py

$ python temp/temp.py
ModuleNotFoundError: No module named 'example'

I'd be grateful if someone could explain what's going on here.

0 Answers
Related