Skip to content

Commit dd1eaa9

Browse files
authored
Install pypi dependencies and run all commands from activated anaconda envs (#71)
Co-authored-by: Antoine DECHAUME <[email protected]>
1 parent 8b47540 commit dd1eaa9

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

tests/test_conda.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,34 @@ def index_of(m):
2323
), result.output()
2424

2525
assert result.outlines[-4] == "True"
26+
27+
28+
def test_conda_run_command(cmd, initproj):
29+
"""Check that all the commands are run from an activated anaconda env.
30+
31+
This is done by looking at the CONDA_PREFIX environment variable which contains
32+
the environment name.
33+
This variable is dumped to a file because commands_{pre,post} do not redirect
34+
their outputs.
35+
"""
36+
env_name = "foobar"
37+
initproj(
38+
"pkg-1",
39+
filedefs={
40+
"tox.ini": """
41+
[tox]
42+
skipsdist=True
43+
[testenv:{}]
44+
commands_pre = python -c "import os; open('commands_pre', 'w').write(os.environ['CONDA_PREFIX'])"
45+
commands = python -c "import os; open('commands', 'w').write(os.environ['CONDA_PREFIX'])"
46+
commands_post = python -c "import os; open('commands_post', 'w').write(os.environ['CONDA_PREFIX'])"
47+
""".format( # noqa: E501
48+
env_name
49+
)
50+
},
51+
)
52+
result = cmd("-v", "-e", env_name)
53+
result.assert_success()
54+
55+
for filename in ("commands_pre", "commands_post", "commands"):
56+
assert open(filename).read().endswith(env_name)

tests/test_conda_env.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_install_deps_no_conda(newconfig, mocksession):
6262
assert len(pcalls) >= 1
6363
call = pcalls[-1]
6464
cmd = call.args
65-
assert cmd[1:4] == ["-m", "pip", "install"]
65+
assert cmd[6:9] == ["-m", "pip", "install"]
6666

6767

6868
def test_install_conda_deps(newconfig, mocksession):
@@ -97,8 +97,8 @@ def test_install_conda_deps(newconfig, mocksession):
9797
assert conda_cmd[7:9] == ["pytest", "asdf"]
9898

9999
pip_cmd = pcalls[-1].args
100-
assert pip_cmd[1:4] == ["-m", "pip", "install"]
101-
assert pip_cmd[4:6] == ["numpy", "astropy"]
100+
assert pip_cmd[6:9] == ["-m", "pip", "install"]
101+
assert pip_cmd[9:11] == ["numpy", "astropy"]
102102

103103

104104
def test_install_conda_no_pip(newconfig, mocksession):

tox_conda/plugin.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,27 @@ def get_py_version(envconfig, action):
4848
return "python={}".format(version)
4949

5050

51+
class CondaRunWrapper:
52+
"""A functor that execute a command via conda run.
53+
54+
It wraps popen so the command is executed in the context of an activated env.
55+
"""
56+
57+
CONDA_RUN_CMD_PREFIX = "{conda_exe} run --no-capture-output -p {envdir}"
58+
59+
def __init__(self, venv, popen):
60+
self.__envdir = venv.envconfig.envdir
61+
self.__conda_exe = venv.envconfig.conda_exe
62+
self.__popen = popen
63+
64+
def __call__(self, cmd_args, **kwargs):
65+
conda_run_cmd_prefix = self.CONDA_RUN_CMD_PREFIX.format(
66+
conda_exe=self.__conda_exe, envdir=self.__envdir
67+
)
68+
cmd_args = conda_run_cmd_prefix.split() + cmd_args
69+
return self.__popen(cmd_args, **kwargs)
70+
71+
5172
@hookimpl
5273
def tox_addoption(parser):
5374
parser.add_testenv_attribute(
@@ -87,7 +108,7 @@ def tox_configure(config):
87108
# This is a pretty cheesy workaround. It allows tox to consider changes to
88109
# the conda dependencies when it decides whether an existing environment
89110
# needs to be updated before being used
90-
for _, envconfig in config.envconfigs.items():
111+
for envconfig in config.envconfigs.values():
91112
# Make sure the right environment is activated. This works because we're
92113
# creating environments using the `-p/--prefix` option in `tox_testenv_create`
93114
envconfig.setenv["CONDA_DEFAULT_ENV"] = envconfig.setenv["TOX_ENV_DIR"]
@@ -161,6 +182,9 @@ def tox_testenv_create(venv, action):
161182
pass
162183
venv.envconfig.config.interpreters.get_executable(venv.envconfig)
163184

185+
# this will force commands and commands_{pre,post} to be executed via conda run
186+
venv.popen = CondaRunWrapper(venv, venv.popen)
187+
164188
return True
165189

166190

@@ -210,8 +234,10 @@ def tox_testenv_install_deps(venv, action):
210234
# to be present when we call pip install
211235
venv.envconfig.deps = venv.envconfig.deps[: -1 * num_conda_deps]
212236

213-
# Install dependencies from pypi here
237+
# Install dependencies from pypi via conda run
238+
action.via_popen = CondaRunWrapper(venv, action.via_popen)
214239
tox.venv.tox_testenv_install_deps(venv=venv, action=action)
240+
215241
# Restore for the config file
216242
venv.envconfig.deps = saved_deps
217243
return True

0 commit comments

Comments
 (0)