hypermodern-python
hypermodern-python copied to clipboard
nox session doesn't find pytest installed by poetry
I'm following along the Hypermodern Python series and it seems in Chapter 2, Test automation with Nox there is a conflict between nox and poetry. Specifically when I use the example noxfile.py:
import nox
@nox.session(python=["3.8", "3.7"])
def tests(session):
session.run("poetry", "install", external=True)
session.run("pytest", "--cov")
I get the following error:
nox > Running session tests-3.8
nox > Creating virtual environment (virtualenv) using python3.8 in .nox/tests-3-8
nox > poetry install
Installing dependencies from lock file
No dependencies to install or update
- Installing testpkg (0.1.0)
nox > Program pytest not found.
nox > Session tests-3.8 failed.
(and the same for tests-3.7)
It seems that installing pytest via poetry as a dev dependency doesn't put it on the path. In the previous section of Chapter 2, pytest was invoked through poetry and this does work in the nox session as well:
session.run('poetry', 'run', 'pytest', '--cov')
Is this an issue with my setup or is it expected that pytest can't be run as a command when installed by poetry?
Interesting. Which versions of Poetry and Nox do you use?
I'm using these versions:
$ nox --version
2020.5.24
$ poetry --version
Poetry version 1.0.8
$ poetry run pytest --version
This is pytest version 5.4.3
I get a somewhat related error using the noxfile.py from Chapter 3. Now the situation is inverted: dev dependencies are install by Nox via session.install (i.e. pip) and the to-be-tested package is installed via Poetry: session.run('poetry', 'install', '--no-dev', external=True). Now invoking pytest directly works (i.e. session.run('pytest', *args)) however it doesn't find the to-be-tested package:
nox > Running session tests
nox > Creating virtual environment (virtualenv) using python3.8 in .nox/tests
nox > poetry export --dev --format=requirements.txt --output=/tmp/tmpoihujsek
nox > pip install --constraint=/tmp/tmpoihujsek pytest pytest-cov pytest-mock coverage[toml]
nox > poetry install --no-dev
Installing dependencies from lock file
No dependencies to install or update
- Installing testpkg (0.1.0)
nox > pytest --cov
==================================== test session starts =====================================
platform linux -- Python 3.8.3, pytest-5.4.3, py-1.8.1, pluggy-0.13.1
rootdir: /path/to/testpkg
plugins: cov-2.9.0, mock-3.1.1
collected 0 items / 3 errors
Coverage.py warning: Module testpkg was never imported. (module-not-imported)
Coverage.py warning: No data was collected. (no-data-collected)
WARNING: Failed to generate report: No data to report.
/path/to/testpkg/.nox/tests/lib/python3.8/site-packages/pytest_cov/plugin.py:264: PytestWarning: Failed to generate report: No data to report.
self.cov_controller.finish()
=========================================== ERRORS ===========================================
____________________________ ERROR collecting tests/test_base.py _____________________________
ImportError while importing test module '/path/to/testpkg/tests/test_base.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
tests/test_base.py:6: in <module>
import testpkg.base
E ModuleNotFoundError: No module named 'testpkg'
I suppose this is because both Nox and Poetry maintain their own virtual environments and pytest has been installed into the Nox environment while testpkg has been installed into the Poetry environment? How are they supposed to know each other?
Thank you for the detailed information!
Can you also paste the output of poetry config --list?
So far, I have not been able to reproduce this.
I have been able to reproduce this. Looks like Poetry silently falls back to creating its own virtual environment if it cannot use the virtual environment it's running in. Can you try removing both the Nox and Poetry environments for your project?
cd path/to/hypermodern-python
rm -rf .nox
poetry env remove 3.8
Specifically, I did the following to reproduce the issue:
git clone https://github.com/cjolowicz/hypermodern-python.git
cd hypermodern-python/
git switch chapter02
nox -s tests -p 3.8
docker run --rm -ti -v $(pwd):/src -w /src python:3.8.3 bash
# in the container:
pip install nox==2020.5.24
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
source $HOME/.poetry/env
nox -s tests -p 3.8 -r
The output shows that Poetry creates its own virtual environment instead of installing into the existing Nox environment:
nox > Running session tests-3.8
nox > Re-using existing virtual environment at .nox/tests-3-8.
nox > poetry install --no-dev
Creating virtualenv hypermodern-python-VsnhxLU2-py3.8 in /root/.cache/pypoetry/virtualenvs
In this case, the environment is unusable for Poetry because it was created outside of the container. The shebangs in the entrypoint scripts point to a Python interpreter located outside of the container.
nox > pytest --cov -m not e2e
nox > Session tests-3.8 raised exception FileNotFoundError(2, 'No such file or directory')
Traceback (most recent call last):
File "/usr/local/lib/python3.8/site-packages/nox/sessions.py", line 462, in execute
self.func(session)
File "/usr/local/lib/python3.8/site-packages/nox/_decorators.py", line 53, in __call__
return self.func(*args, **kwargs)
File "/src/noxfile.py", line 8, in tests
session.run("pytest", *args)
File "/usr/local/lib/python3.8/site-packages/nox/sessions.py", line 226, in run
return self._run(*args, env=env, **kwargs)
File "/usr/local/lib/python3.8/site-packages/nox/sessions.py", line 254, in _run
return nox.command.run(args, env=env, path=self.bin, **kwargs)
File "/usr/local/lib/python3.8/site-packages/nox/command.py", line 109, in run
return_code, output = popen(
File "/usr/local/lib/python3.8/site-packages/nox/popen.py", line 35, in popen
proc = subprocess.Popen(args, env=env, stdout=stdout, stderr=stderr)
File "/usr/local/lib/python3.8/subprocess.py", line 854, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "/usr/local/lib/python3.8/subprocess.py", line 1702, in _execute_child
raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: '/src/.nox/tests-3-8/bin/pytest'
nox > Session tests-3.8 failed.
# head /src/.nox/tests-3-8/bin/pytest
#!/private/tmp/hypermodern-python/.nox/tests-3-8/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from pytest import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())
I just checked and the tests run without problem on the CI server. Running locally on my machine however gives the above error. Removing the .nox and pytest environments unfortunately didn't help. It still complains that Program pytest not found. I can confirm that poetry install creates its own venv besides the already existing nox-venv. It's not completely clear to me what makes the difference between my local setup and the CI server (here the log shows that poetry doesn't create its own venv).
Thanks, some more questions then:
- ~Which OS do you use?~ (Linux, judging from your pasted output)
- Does the file
.nox/tests-3-8/bin/pytestexist? - If yes, what's the first line in that file? Is the path on that line located in the Nox environment?
- What is the output of
poetry config --list?
I'm on Ubuntu 16.04. The file .nox/tests-3-8/bin/pytest doesn't exist though. When running nox after having deleted all the venvs, I get the following output:
[...]
nox > Running session tests-3.8
nox > Creating virtual environment (virtualenv) using python3.8 in .nox/tests-3-8
nox > poetry install
Creating virtualenv testpkg-pDH_AOha-py3.8 in /home/dominik/.cache/pypoetry/virtualenvs
Installing dependencies from lock file
[...]
- Installing pytest (5.4.3)
[...]
So it seems pytest gets installed in the wrong venv (the one from poetry; why is this one created in the first place?). The output on the CI server is similar except it doesn't contain the line Creating virtualenv ... after poetry install so here poetry seems to reuse the nox-venv.
Running nox again one my local machine also skips that line but I suppose this is because it silently reuses the already existing poetry-venv.
I just figured it out (though I don't completely understand why that behavior occurred). Apparently there was a conflict with an active Miniconda environment which was automatically activated through .bashrc (its basically a blank environment). So every time I executed the nox command there was also the Miniconda environment activate. This apparently caused Poetry to create its own venv. After deactivating the Miniconda venv Poetry reuses the nox-venv. Is it because the Miniconda venv shadowed the Nox venv and Poetry realized this and thus created its own venv?
Glad you fixed it!
Poetry detects that it is running in a Conda environment via the CONDA_PREFIX environment variable. It will ignore this environment if CONDA_DEFAULT_ENV is "base", to avoid polluting Conda's global "base" env, since most users have it activated all the time. When this happens, it won't look if it is also running in an activated virtual environment.
https://github.com/python-poetry/poetry/blob/1d64e1c75cfd03d8f98533645a7815f0dbcaf421/poetry/utils/env.py#L318-L325
Well, at least that's my impression of what happened here. Not able to test this right now.
Thanks for sharing that part of the source code, it seems relevant indeed (it was the "base" environment for me).
However I still don't understand the difference between the two cases, since both times in_venv results to be False. I put the various os.environ.get inside one of my nox sessions and this is what I get:
# Miniconda "base" active
env_prefix = "/home/dominik/miniconda3"
conda_env_name = "base"
in_venv = env_prefix is not None and conda_env_name != "base"
assert not in_venv
# Miniconda "base" deactivated
env_prefix = None
conda_env_name = None
in_venv = env_prefix is not None and conda_env_name != "base"
assert not in_venv
So from there on it continues to that branch where I cannot spot a difference between the two cases anymore.
I always assumed that the Nox venv would shadow the Conda venv, and the explicit check for "base" also suggests this (i.e. the base environment is not considered an environment, that's also what the comments imply).
I think I'll open an issue at Poetry since it seems to be related to their venv checks.
Well, the os.environ.get in your Nox session won't see VIRTUAL_ENV because it is only set by session.run for the process it spawns, e.g. poetry or pytest.
From what I can tell, Poetry's behavior does seem to be at fault though: It should use VIRTUAL_ENV if it is set, no matter if there is an activated Conda base environment, or not. There might already be an issue for that, I haven't looked.
You are right, I checked with a separate script which I invoked via session.run and the variables are set as follows:
# Miniconda "base" active
env_prefix = "/home/dominik/Projects/testpkg/.nox/test"
conda_env_name = "base"
# Miniconda "base" inactive
env_prefix = "/home/dominik/Projects/testpkg/.nox/test"
conda_env_name = None
So in the first case this creates a false-positive for not in_venv.
I created a corresponding issue.
I'm having this same issue where it appears Nox is using the Poetry virtualenv, but I don't think I have the same root cause becuase I am not using miniconda (or any conda). I have been following along with Hypermodern Python using two different computers both setup in WSL from scratch exactly the same way. One works great and the other doesn't. Not sure why they are different.
To answer the questions posed above: I'm using Ubuntu 20.04 LTS .nox/... does not exist N/A poetry config --list returns
cache-dir = "/home/joe/.cache/pypoetry" experimental.new-installer = true virtualenvs.create = true virtualenvs.in-project = null virtualenvs.path = "{cache-dir}/virtualenvs" # /home/joe/.cache/pypoetry/virtualenvs
Thanks for reporting your issue!
Can you post the following?
- poetry version
- nox version
- output of "nox --verbose" in your project
- link to your repo, or gist with your noxfile
Your question helped me find the problem. The nox version was 2019.5.30 which did not match what my other computer was using. I ended up actually reinstalling my Ubuntu instance (running as WSL on Windows 10) and have been worked through the same initialization steps again. I realized that the installation of nox didn't quite work as expected. Running "pip install --user --upgrade nox" did complete the install, but then the nox keyword would return
Command 'nox' not found, but can be installed with:
sudo apt install nox
So I realized I had installed via sudo which installed the 2019.5.30 version. The underlying issue is that using the pip method to install nox did not add it to PATH, so I just needed to add
export PATH="$HOME/.local/bin:$PATH"
to my .bashrc file and reload the shell. Now it seems to work as expected.
$ poetry config virtualenvs.in-project 1
Solves this issue. https://github.com/cjolowicz/hypermodern-python/issues/101#issuecomment-711088992
For people arriving here in 2022, I've found that I can get it to work if I execute nox within a poetry shell.
I'm not able to run Nox without the warning Warning: pytest is not installed into the virtualenv, it is located at /Users/<username>/Library/Caches/pypoetry/virtualenvs/testproj-xgPlhPPj-py3.11/bin/pytest. This might cause issues! Pass external=True into run() to silence this message..
My Nox file is:
import nox
@nox.session(python=["3.8", "3.9", "3.10", "3.11"])
def tests(session: nox.Session) -> None:
session.run("poetry", "shell", external=True) # Same warning without this line
session.run("poetry", "install", external=True)
session.run("pytest")
I'm using Poetry to manage the project which is set up with something like:
poetry new testproj
cd testproj
poetry add --group dev nox pytest
poetry env use python3.11
poetry install
poetry shell
My Python installations are managed through PyEnv and I have a Conda base environment activated at all time to avoid messing up the system Python. I tried to deactivate the Conda base environment but this yields the same warning.
I know I can bypass the issue by installing the project using pip like this:
@nox.session(python=["3.8", "3.9", "3.10", "3.11"])
def tests(session: nox.Session) -> None:
session.install(".")
session.install("pytest")
session.run("pytest")
This works great excepted that it duplicates the development requirements in two places: in the Nox file and in the Project.toml one. However, I think that this last formulation is a better testing setting since the end user would install the package through pip anyway (also, this may be simpler for CI workflows, removing the Poetry install requirement).
Should I stick with this last solution or is there a better alternative for Poetry?
as per @oneextrafact, the following works
poetry run nox