How to use tox 4 with pyenv and poetry?

How to use tox 4 with pyenv and poetry?

If you're a Python developer who uses tox, pyenv, and poetry in your projects, you may have encountered an issue when trying to use tox>=4, tox-pyenv>=1.1.0, and pyenv-win>=3.1.1 (or the Linux version of pyenv) together. This issue is caused by tox 4 deprecating a hook function called tox_get_python_executable, which is not yet fixed by tox-pyenv.

Fortunately, there is a workaround that can help you use these tools together without any issues. In this article, we'll walk you through the steps to use tox 4 with pyenv and poetry, and show you how to implement this trick to mitigate the deprecation issue.

from tox import hookimpl as tox_hookimpl
ImportError: cannot import name 'hookimpl' from 'tox' (/root/tox/lib/python3.8/site-packages/tox/init.py)

Poetry

I use poetry 1.4.0 (latest) at the moment.

Add tox, pytest, coverage, pytest-randomly to dev dependencies group. You are free to add more tools and make them available for tox.

[tool.poetry.group.dev.dependencies]
tox = "^4.0.0"  # don't forget to specify min constraint as 4
pytest = "^7.2.2"
coverage = "^7.2.1"
pytest-randomly = "^3.12.0"

Install required python versions with pyenv

I want to test my code with pythons 3.8...3.11.

# first, install all versions locally
$ pyenv install 3.8 3.9 3.10 3.11
# second, activate ALL versions globally.
# note: on Windows you may need to specify patch versions, e.g. 3.8.16
$ pyenv global 3.8 3.9 3.10 3.11

The last command makes python3.8, python3.9, python3.10, and python3.11 available in PATH. Note: that python will point to whatever the first version is specified in this list, in my case 3.8.

$ which python python3.8 python3.9 python3.10 python3.11
/opt/.pyenv/shims/python
/opt/.pyenv/shims/python3.8
/opt/.pyenv/shims/python3.9
/opt/.pyenv/shims/python3.10
/opt/.pyenv/shims/python3.11
$ python --version 
Python 3.8.16
$ python3 --version
Python 3.8.16
$ python3.11 --version
Python 3.11.3

tox.ini

Here is my example of a tox.ini which I use with poetry, pyenv, tox>=4. All dependencies, including dev dependencies (like pytest, coverage, and such) are installed in poetry dev group.

[tox]
min_version = 4.0
# enumerate versions you want to use
env_list = py3{8,9,10,11}
skip_missing_interpreters = False

[testenv]
# in my case I run pytest to execute all tests
commands = pytest
# we are going to reuse pyenv/coverage tools from poetry, so
# allow tox to use them. Or, specify `deps` with a list of dependencies.
allowlist_externals =
    pytest
    coverage

###############################
# for every environment, override base_python with a shim name that pyenv created, like the following:
[testenv:py38]
base_python = python3.8

[testenv:py39]
base_python = python3.9

# in my setup I use python3.10 to calculate coverage
[testenv:py310]
base_python = python3.10
commands =
    coverage run --branch -m pytest --junitxml={tox_root}/junit.xml
    coverage report
    coverage xml -o {tox_root}/coverage.xml


[testenv:py311]
base_python = python3.11

Result

# run tox like this. You can run envs in parallel! 
$ poetry run tox run-parallel
# on CI I recommend to run tox like this:
$ poetry run tox run-parallel --parallel-no-spinner --skip-missing-interpreters=false
py311: OK ✔ in 1 minute 6.53 seconds
py39: OK ✔ in 1 minute 11.61 seconds
py38: OK ✔ in 1 minute 12.83 seconds
  py38: OK (72.83=setup[23.98]+cmd[48.85] seconds)
  py39: OK (71.61=setup[24.60]+cmd[47.01] seconds)
  py310: OK (102.03=setup[24.44]+cmd[75.93,0.71,0.95] seconds)
  py311: OK (66.53=setup[24.50]+cmd[42.03] seconds)
  congratulations :) (102.22 seconds)

# see coverage.xml and junit.xml in the root folder (nearby tox.ini)

Did you find this article valuable?

Support Bohdan Vanieiev by becoming a sponsor. Any amount is appreciated!