5

I have a python project that uses poetry and tox. It has source code, tests and scripts (juptext notebooks). I can't import the dev dependencies in the scripts, but I can in the tests.

When I came across this problem, I created the following minimal example. At first, it didn't work, then I fiddled around with it, and now it's working. So I stripped the project that has the actual problem down so it's indistinguishable other than the project name, location, virtual env, and .git directory, but that's still not working.

UPDATE deleting all build artifacts and the virtualenv for the minimal example makes it stop working again

UPDATE adding the line scripts: poetry install to the tox commands fixed only the minimal example

The source code, tests and scripts are in the following layout

foo
  +--foo
  |  +--__init__.py
  |
  +--tests
  |  +--__init__.py
  |  +--test_foo.py
  |
  +--scripts
  |  +--foo_script.py
  |
  +--pyproject.toml
  +--tox.ini

The files are either empty or as follows:

foo_script.py

import requests

test_foo.py

import requests
import pytest

def test():
    assert True

pyproject.toml

[tool.poetry]
name = "foo"
version = "0.1.0"
description = ""
authors = ["foo maker"]

[tool.poetry.dependencies]
python = "^3.7"
requests = "*"

[tool.poetry.dev-dependencies]
pytest = "^4.6"

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

tox.ini

[tox]
envlist = test, scripts
isolated_build = true
skipsdist = true

[testenv]
basepython = python3.7
whitelist_externals =
    pytest
    bash
commands =
    test: pytest
    scripts: bash -c 'python3 scripts/*.py'

When I run tox, I get

test run-test-pre: PYTHONHASHSEED='4126239415'
test run-test: commands[0] | pytest
============================= test session starts ==============================
platform linux -- Python 3.6.9, pytest-5.2.1, py-1.8.0, pluggy-0.13.0
cachedir: .tox/test/.pytest_cache
rootdir: /home/#######/foo
collected 1 item                                                               

tests/test_foo.py .                                                      [100%]

============================== 1 passed in 0.09s ===============================
scripts run-test-pre: PYTHONHASHSEED='4126239415'
scripts run-test: commands[0] | bash -c 'python3 scripts/*.py'
Traceback (most recent call last):
  File "scripts/foo_script.py", line 1, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'
ERROR: InvocationError for command /bin/bash -c 'python3 scripts/*.py' (exited with code 1)
___________________________________ summary ____________________________________
  test: commands succeeded
ERROR:   scripts: commands failed
joel
  • 6,359
  • 2
  • 30
  • 55
  • Do you have `pytest` installed globally? The _tox_ _testenv_ named `test` is supposed to call the command `pytest`, but it is not declared anywhere as a dependency as far as I can tell. So this _testenv_ should fail. – sinoroc Feb 03 '20 at 14:30
  • @sinoroc apparently so. wasn't aware of that – joel Feb 03 '20 at 15:47
  • As far as I can tell, _tox_ can't recognize _poetry_'s _dev-dependencies_ (I am not sure why _poetry_ invented its own thing here). A more common pattern that you might want to follow is to use a _test_ _extra_ instead to make sure that test dependencies such as _pytest_ are installed in the _tox environments, like explained in this answer: https://stackoverflow.com/a/59522588/11138259 – sinoroc Feb 03 '20 at 17:32

4 Answers4

0

I believe something like the following should work:

pyproject.toml

[tool.poetry]
name = "foo"
version = "0.1.0"
description = ""
authors = ["foo maker"]

[tool.poetry.dependencies]
python = "^3.7"
requests = "*"
#
pytest = { version = "^4.6", optional = true }

[tool.poetry.extras]
test = ["pytest"]

# [tool.poetry.dev-dependencies]
# use 'test' extra instead

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"

tox.ini

[tox]
envlist = test, scripts
isolated_build = true

[testenv]
basepython = python3.7
whitelist_externals =
    pytest
    bash
extras =
    test
commands =
    test: pytest
    scripts: bash -c 'for f in scripts/*.py; do python "$f"; done'
sinoroc
  • 18,409
  • 2
  • 39
  • 70
0

Assuming you have installed poetry and tox and pytest are dependencies in your pyproject.yml (notice poetry run, see https://python-poetry.org/docs/cli/#run):

[tox]
envlist = py37
isolated_build = True
skipsdist = True

[testenv]
whitelist_externals = poetry
commands=
    poetry run pytest

optionally you can make the install happen on running the tests by changing the last bit to (but then you will need to have tox installed outside of poetry, which could cause you issues down the line)

commands=
    poetry install
    poetry run pytest

Also depending on your root folder and where the tests are you can configure the path for tox to change directory to by adding

changedir = tests

In which case the whole file would look like this if you are in directory foo executing tox:

[tox]
envlist = py37
isolated_build = True
skipsdist = True

[testenv]
whitelist_externals = poetry
commands=
    poetry run pytest
changedir = tests
Henrik Lindgren
  • 615
  • 5
  • 9
  • @JoelB I downvoted. Even though I understand the idea in both of your suggestions (I know it's in the poetry FAQ as well), I believe this way of doing (calling poetry in the commands) is somewhat counter-productive, or at least against tox's philosophy. More details in this [discussion](https://github.com/python-poetry/poetry/issues/1941). One obvious example is that it will make it impossible (or at least very difficult) to test the project against different versions of a dependency, like in this [question](https://stackoverflow.com/q/59377071/11138259). – sinoroc Feb 13 '20 at 11:02
  • Fair enough, thanks for leaving an explanation, I understand the reasoning. – Henrik Lindgren Feb 14 '20 at 13:39
0

This is an easy fix. First, run rm -rf .tox to delete the .tox directory, probably hasn't installed the files you wanted.

Here is an example of my tox.ini.

[tox]
isolated_build = true
skipsdist = true
envlist = py39

[testenv]
deps = -rrequirements-dev.txt
whitelist_externals =
    poetry
skip_install = true
commands =
    python -m pytest tests/

As you can see in the tox.ini file I have a separate requirements for dev. This can be generated from poetry via the commands below.

poetry export --format=requirements.txt --without-hashes --with dev --output=requirements.txt

poetry export --format=requirements.txt --without-hashes --output=requirements-dev.txt

Don't ask me why but the deps in [testenv] are concatenated out, as you can see with deps = -rrequirements-dev.txt, would also change your command to python -m pytest /tests

Ciao.

Jenobi
  • 368
  • 4
  • 12
-1

First I need to install the dependencies with poetry install. Then append poetry run to the beginning of the command to enable the dependencies. Also running python scripts like that will just run the first, passing the name of the others as args to the first program. Instead use for f in scripts/*.py; do python "$f"; done (see here)

All together

poetry install
poetry run bash -c 'for f in scripts/*.py; do python "$f"; done'
joel
  • 6,359
  • 2
  • 30
  • 55
  • Doesn't it create a _poetry_ virtual environment from within the _tox_ virtual environment? – sinoroc Feb 02 '20 at 12:51
  • @sinoroc i don't know, but it's what the poetry docs suggest https://python-poetry.org/docs/faq/#is-tox-supported – joel Feb 03 '20 at 23:40
  • I know, I have seen this in the doc as well. The whole thing just doesn't make any sense to me. _tox_ already takes care of installing the project and its dependencies into a virtual environment. So there is no need to get _poetry_ involved at this point, except to build the _sdist_, which is exactly what the `isolated_build = true` does thanks to PEP517 and PEP518. – sinoroc Feb 04 '20 at 10:45
  • @sinoroc i get your point, though I personally feel that poetry should be in charge of the dependencies not tox, as I wouldn't use tox in all cases, but I would use poetry – joel Feb 04 '20 at 11:13
  • 1
    True. Depends what you want to use _tox_ for. I am not entirely sure your use case requires _tox_ at all. In general _tox_ is used to test against multiple versions of Python. And also against multiple versions of a dependency, and this is where _poetry_ definitely stands in the way of _tox_, see [this question](https://stackoverflow.com/q/59377071/11138259) and [this issue](https://github.com/python-poetry/poetry/issues/1941) for example. In those cases _poetry_ reinstalls the project and the dependencies which cancels the work previously done by _tox_. – sinoroc Feb 04 '20 at 11:22