是否可以在基于 conda 的 Python 安装中使用 tox?

Is it possible to use tox with conda-based Python installations?

Python 测试工具 tox 似乎是为与 virtualenv 一起工作而设计的。它也可以用于 conda/anaconda-based Python 安装吗?

虽然 tox 不能使用 conda,但您可以将 conda 用于 "install" 不同的 Python 版本,tox 可以找到它们(就像它会找到 "normal" Python 安装在这些文件夹中)。以下是在 Windows:

上测试的
  1. 您需要在根 conda 环境中通过 pip 安装 virtualenv。我怀疑这是 tox 将使用的 virtualenv。 (我必须使用 pip install virtualenv 安装 virtualenv 才能让 virtualenv 命令工作,即使 conda list 显示它已安装。)
  2. 安装要测试的 Python 版本。使用 conda create 很容易做到这一点。 tox 将在 C:\python27C:\python33 等 Windows 上自动检测 Python 二进制文件,因此使用 conda create -p C:\python27 python=2.7 等创建环境

不知道发展到什么程度,大家可以看看https://github.com/hayd/ctox

是的,您需要安装 virtualenv 的 conda 版本才能运行。

尝试执行:

conda install virtualenv

virtualenv                15.1.0                   py36_

切换到包含 tox.ini 的项目目录并执行:

tox

我让 tox 和 conda 在 Windows 中协同工作:

  • 在我使用的环境中使用 conda 安装 virtualenv tox:

    conda install virtualenv

  • Creating "Directory Junction" symlinks from C:\PythonXY 到我的实际环境路径。这绕过了 InterpreterNotFound-error:

    mklink /J C:\PythonXY C:\real\path\to\myPythonXYenv

我在 E:\Anaconda3\ 中安装了 Anaconda,在 E:\Anaconda3\envs\ 中安装了我的所有环境,例如E:\Anaconda3\envs\py27\

(请参阅下面的 script 以快速轻松地完成此操作。)

步骤 1 - 使用 conda 创建环境:

E:\dev> conda create -n py27 python=2.7 --yes
E:\dev> conda create -n py33 python=3.3 --yes
...
E:\dev> conda create -n py36 python=3.6 --yes

步骤 2 - 创建所有符号链接:

E:\dev> mklink /J C:\Python27 E:\Anaconda3\envs\py27
E:\dev> mklink /J C:\Python33 E:\Anaconda3\envs\py33
...
E:\dev> mklink /J C:\Python36 E:\Anaconda3\envs\py36

注:我是从E盘的目录调用conda create,所以不需要--prefix/-p选项为了在 E:\Anaconda3\envs\.

中安装新环境

更简单的方法:

无需为每个 environment/python 版本进行繁琐的设置,可以使用以这种方式进一步添加的 ToxEnvMatcher-class:

my_envs = os.path.join('E:\', 'Anaconda3', 'envs')
tem = ToxEnvMatcher(my_envs)
for version in '27,34,35,36'.split(','):
    tem.make(version)

编辑:为了使脚本更易于使用,我在 file 中添加了一个新部分(此处假定为 tox_with_conda.py,) 所以可以从 cmd.exe:

调用
C:\dev> python tox_with_conda.py E:\Anaconda3\envs 27 34 35 36 37

编辑2:也可以用pip安装:pip install tox_with_conda并用作:

C:\dev> python -m tox_with_conda E:\Anaconda3\envs 27 34 35 36 37

我正在使用 Python 3.6.3 和 tox 2.9.1,但我不知道 when/if 早期版本是否也能正常工作。

防御: 我认为对于某些人来说,这似乎是一个过于繁琐的过程(但实际上并非如此),或者对很多黑客来说都是如此。但请记住,能够使用 Anaconda/conda 还可以减少尝试安装库、包、++++ 所花费的时间浪费。

请注意:

  • 我将 tox 与 pytest 一起使用,没有发现对我的测试有任何影响。
  • 我的测试很简单,有可能我还没有接触到问题。
  • 可以假设,有些事情我没有想到可能与其他人有关。

class(也可用here):

from subprocess import run
from os.path import join

DEFAULT_BASE = join('C:\', 'Python')


class ToxEnvMatcher:
    """
    Utility to make conda environments work with tox.

    Conda envs might be in other locations than where `tox <https://tox.readthedocs.io>`_ expects them to be.

    A symbolic link 'Directory Junction' is created from expected location to the actual location.
    Intended for Windows to get around the ``InterpreterNotFound``-error.

    E.g.: tox expects to find Python 2.7 in ``C:\Python27``,
    but may actually be installed in another drive and location.

    Examples of use:

    .. code-block:: python

        my_envs = join('E:\', 'Anaconda3', 'envs')
        tem = ToxEnvMatcher(my_envs)
        for version in '27,34,35,36'.split(','):
            tem.make(version)

    The class is utilized through ``argsparse`` so it can also be used from cmd.exe.

    Examples of use of th of using ``ToxEnvMatcher`` from cmd.exe:

    .. code-block:: none

        E:\dev> tox_with_conda.py E:\Anaconda3\envs 27 34 35 36 37

    It's possible to use the ``-b``/``--base`` option to override the default base location (``C:\Python``):

    .. code-block:: none

        E:\dev> tox_with_conda.py E:\Anaconda3\envs 27 34 35 36 37 --base D:\Python

    :param str envs_dir: The path to where new conda environments will be created
    :param str default_base: The base of the 'default' location. Usually it's ``C:\Python``
    """
    def __init__(self, envs_dir, default_base=DEFAULT_BASE):
        self.envs_dir = envs_dir
        self.default_base = default_base

    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, self.envs_dir)

    def make(self, version):
        """
        Take version and create conda environment with symlink from 'default tox location'.

        E.g.: given version='27' and environment folder ``{self.envs_dir}``:

         - ``conda create -p {self.envs_dir}\py27 python=2.7``
         - ``mklink /J C:\Python27 {self.envs_dir}\py27``

        :param str version: A string on the form 'XY', e.g. '27' or '36'
        :return: None
        :rtype: NoneType
        """
        if len(version) != 2 or not int(version):
            raise ValueError("Parameter 'version' must be on the form 'XY', and not '{}'".format(version))
        conda_cmd = self._create_cmd_args(version)
        symlink_cmd = self._create_symlink_args(version)
        run(conda_cmd, shell=True)
        run(symlink_cmd, shell=True)

    def _get_env_folder(self, version):
        return join(self.envs_dir, 'py{}'.format(version))

    def _create_cmd_args(self, version):
        env_dir = self._get_env_folder(version)
        python_version = '.'.join(version)
        conda_create = 'conda create -p {} python={} --yes'.format(env_dir, python_version)
        return conda_create.split(' ')

    def _create_symlink_args(self, version):
        env_dir = self._get_env_folder(version)
        return 'mklink /J {}{} {}'.format(self.default_base, version, env_dir).split(' ')

使它在 cmd 中运行的添加代码是:

if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("env_dir",
                        help="The folder where conda environments should be installed.")
    parser.add_argument("versions", nargs='*',
                        help="The list of versions, formatted 'XY' where X is major and Y minor. E.g. '27 35 36'")
    parser.add_argument("-b", "--base", default=DEFAULT_BASE,
                        help="Base of the path which tox expects to find Python installed. "
                             "Default: {}.".format(DEFAULT_BASE))
    args = parser.parse_args()

    print('env_dir: ', args.env_dir)
    print('versions: ', args.versions)
    print('--base: ', args.base)

    tem = ToxEnvMatcher(args.env_dir, default_base=args.base)
    for version in args.versions:
        tem.make(version)

tox-conda 插件现在应该可以弥补这个差距,但需要积极使用 conda 的贡献者来测试和改进它。

来自自述文件:

tox-conda is a plugin that provides integration with the conda package and environment manager for the tox automation tool. It's like having your cake and eating it, too!

By default, tox creates isolated environments using [virtualenv](https://virtualenv.pypa.io] and installs dependencies from pip.

In contrast, when using the tox-conda plugin tox will use conda to create environments, and will install specified dependencies from conda. This is useful for developers who rely on conda for environment management and package distribution but want to take advantage of the features provided by tox for test automation.

要安装该插件,它需要与 tox 一起安装在同一虚拟环境中。要创建包含 toxtox-conda 的虚拟环境,这就足够了:

$ python3 -m venv toxbase
$ toxbase/bin/pip install tox tox-conda
[...]
Successfully installed tox-3.13.2 tox-conda-0.2.0
$ toxbase/bin/tox --version
3.13.1 imported from /home/ob/tmp/toxbase/lib/python3.6/site-packages/tox/__init__.py
registered plugins:
    tox-conda-0.2.0 at /home/ob/tmp/toxbase/lib/python3.6/site-packages/tox_conda/plugin.py

从那时起,tox 可以用作命令行工具,并通过在 toxbase virtualenv 中升级它来保持最新状态。另一种更自动化的方法是使用 pipx