drhagen
drhagen

Reputation: 9572

How does a tox environment set its sys.path

I have a Python project that requires some compilation. tox makes the sdist and the bdist_wheel successfully. But when tox runs the tests, the source code module is always the one that get imported, causing the tests to fail (because there are no binaries there). This happens because the root of the project is always the first folder on sys.path and I cannot figure out how this happens.

project
├── foo
│   ├── __init__.py
│   └── bar.c <-- turns into bar.so in bdist_wheel
├── tests
│   ├── __init__.py
│   └── test_bar.py
├── tox.ini
└── setup.py

tox.ini

[tox]
envlist =
    py37

[testenv]
changedir = {toxinidir}/tests
setenv =
    PYTHONPATH = {envsitepackagesdir}
commands = {envpython} -m pytest --cov=bar --cov-report=html

Note that I set changedir to ensure that the project root is not the working directory. I also set PYTHONPATH to only the tox environment's site-packages. I also force the tox environment's Python to be run.

Nevertheless, this is sys.path I see if I print it out during test running:

[
  '/home/me/foo', # Bad, bad, bad
  '/home/me/foo/tests', # Good, this is the CWD
  '/home/me/foo/.tox/py37/lib/python3.7/site-packages', # Good
  '/home/me/foo/.tox/py37/lib/python37.zip', # Umm, ok, this doesn't exist
  '/home/me/foo/.tox/py37/lib/python3.7', # Good
  '/home/me/foo/.tox/py37/lib/python3.7/lib-dynload', # Probably good
  '/home/me/anaconda/envs/foo/lib/python3.7',  # Bad, why is this here?
]

The biggest offender is /home/me/foo which causes the source module foo to be loaded instead of the one installed in tox.

Another problematic one is /home/me/anaconda/envs/foo/lib/python3.7. I would prefer that tox not fall back on the system interpreter if something is not found in the environment.

How does tox choose this these paths and how do I control it to be better behaved?

Upvotes: 5

Views: 3406

Answers (1)

drhagen
drhagen

Reputation: 9572

This is not caused by tox; it is caused by pytest. As described in the pytest documentation, there is known to be a bad interaction between pytest and tools like tox that install the distribution in an isolated environment.

The reason for this is that pytest modifies sys.path so that all folders containing test packages (test folders with __init__.py in them) are added to it. Because the foo/tests directory contains a __init__.py file, pytest adds foo/ to sys.path so that the tests package can be imported with the normal import statement.

Even removing __init__.py may not be enough. Running pytest as python -m pytest in the foo directory will cause the foo directory to be added to sys.path. Running pytest with the pytest command line tool will avoid this.

The recommended solution is to move all source to a dedicated src directory. This ensures that the source packages will never be on sys.path even if the project root is.

Recommended project structure

project
├── src
│   └── foo
│       ├── __init__.py
│       └── bar.c
├── tests
│   ├── __init__.py
│   └── test_bar.py
├── tox.ini
└── setup.py

Upvotes: 5

Related Questions