Source code for venv_tools

# -*- coding: utf-8 -*-
"""
venv_tools
~~~~~~~~~~

A bunch of tools for using venvs (and virtualenvs) from python.

:copyright: (c) 2014 by James Tocknell.
:license: BSD, see LICENSE for more details.
"""
import os
import os.path
import tempfile
import shutil
from shlex import split
import subprocess
import warnings

from ._utils import (
    pathprepend, get_default_venv_builder, is_venv, BIN_DIR, PYTHON_FILENAME,
    abspath_python_exe,
)

from ._version import get_versions
__version__ = get_versions()['version']
del get_versions


DEFAULT_INSTALL_COMMAND = "{python} -m pip install {package}"


[docs]class Venv(object): """ Context manager around activating and deactivating a venv. `Venv` sets a number of environment variables which are equivalent to running `bin/activate`. It can create a venv if `venv_builder` is given. .. warning:: Creating or activating a venv inside a venv can be "interesting", with the results varying between different python versions (and different venv tools). The safest method appears to be using virtualenv, however this should not be relied upon. A warning will be raised if it is detected that this is running inside a venv. :param str env_dir: The absolute path to the venv. :param venv_builder: An object which creates a venv. It must define a method `create` which takes one argument, `env_dir`, and creates a venv at that path. Any additional keywords passed to `Venv` will be passed to the object. :type venv_builder: `venv.EnvBuilder or similar` """ def __init__(self, env_dir, venv_builder=None, **kwargs): self._env_dir = env_dir self._venv_builder = venv_builder self._kwargs = kwargs self._install_command = DEFAULT_INSTALL_COMMAND self._old_venv = None self._python_home = None self._old_path = None def __enter__(self): if not is_venv(self.env_dir): raise RuntimeError( "{} is not a venv/virtualenv.".format(self.env_dir)) self._old_venv = os.environ.get("VIRTUAL_ENV", None) if self._old_venv is not None: warn_str = "Inside virtualenv {virtualenv}.".format( virtualenv=self._old_venv ) warnings.warn(warn_str) self._old_path = os.environ["PATH"] self._python_home = os.environ.get("PYTHONHOME", None) if self._venv_builder: venv = self._venv_builder(**self._kwargs) venv.create(self.env_dir) pathprepend(os.path.join(self.env_dir, BIN_DIR), "PATH") if self._python_home is not None: os.environ.pop("PYTHONHOME") os.environ["VIRTUAL_ENV"] = self.env_dir return self def __exit__(self, exc_type, exc_value, traceback): os.environ["PATH"] = self._old_path if self._python_home is not None: os.environ["PYTHONHOME"] = self._python_home os.environ.pop("VIRTUAL_ENV") if self._old_venv is not None: os.environ["VIRTUAL_ENV"] = self._old_venv @property def python_exe(self): """ Path to python interpreter """ return os.path.join(self.env_dir, BIN_DIR, PYTHON_FILENAME) @property def env_dir(self): """ The path to the virtual environment """ return self._env_dir @property def install_command(self): """ The command to install a python package in the virtualenv. Must be a format string with python and package. """ return self._install_command @install_command.setter def install_command(self, new_cmd): self._install_command = new_cmd
[docs] def call_python_file(self, filename): """ Call a python file with the python interpreter associated with this virtualenv. """ return subprocess.check_output( [self.python_exe, filename], stderr=subprocess.STDOUT )
[docs] def call_python_module(self, module_name, *args): """ Call a python module with the python interpreter associated with this virtualenv. """ return subprocess.check_output( [self.python_exe, '-m', module_name].append(args), stderr=subprocess.STDOUT )
[docs] def call_python_code(self, code): """ Call some python code with the python interpreter associated with this virtualenv. """ return subprocess.check_output( [self.python_exe, '-c', code], stderr=subprocess.STDOUT )
[docs] def install_package(self, package): """ Install a python package into this virtualenv. """ cmd = split( self.install_command.format( python=self.python_exe, package=package ) ) return subprocess.check_output(cmd, stderr=subprocess.STDOUT)
[docs]class TemporaryVenv(object): """ Context manager around creating a temporary venv. `TemporaryVenv` handles the creation and removal of a temporary venv. By default it will try to use the venv tools in the standard library, and fall back to using `virtualenv <http://www.virtualenv.org/>`_. .. note:: The tool used to create the venv depends on the arguments given. `venv_builder` overrules `use_virtualenv` which overrules the defaults. If `path_to_python_exe` is given, then it is passed to the venv builder, which is chosen as above with the addition that the default will be a tool that supports using a specific python executable (most likely virtualenv). .. note:: If you plan on using pip, you need the argument `with_pip`, as both `EnvBuilder` in the standard library, and the virtualenv builder included in `venv_tools` (to try to present a similar interface to `EnvBuilder`) default to not installing pip. .. warning:: Creating or activating a venv inside a venv can be "interesting", with the results varying between different python versions (and different venv tools). The safest method appears to be using virtualenv, however this should not be relied upon. A warning will be raised if it is detected that this is running inside a venv. :param str python_exe: The path to the python executable (relative or absolute), or the name of the executable on the system path. :param bool use_virtualenv: Use virtualenv instead of the default to create the venv. :param venv_builder: An object which creates a venv. It must define a method `create` which takes one argument, `env_dir`, and creates a venv at that path. Any additional keywords passed to `Venv` will be passed to the object. :type venv_builder: `venv.EnvBuilder or similar` """ def __init__( self, venv_builder=None, use_virtualenv=False, python_exe=None, **kwargs ): path_to_python_exe = abspath_python_exe(python_exe) self._kwargs = kwargs self._venv_builder = ( venv_builder or get_default_venv_builder(use_virtualenv, path_to_python_exe) ) self._path_to_python_exe = path_to_python_exe self.env_dir = None # needed for venv which wants to create dir self._kwargs["clear"] = True def __enter__(self): self.env_dir = tempfile.mkdtemp() if self._path_to_python_exe: self._kwargs["path_to_python_exe"] = self._path_to_python_exe venv = self._venv_builder(**self._kwargs) venv.create(self.env_dir) return self.env_dir def __exit__(self, exc_type, exc_value, traceback): shutil.rmtree(self.env_dir)