Source code for venv_management.ext.drivers.pyenv_virtualenv.driver

import logging
import re
import subprocess
import sys
from pathlib import Path
from typing import List

from venv_management.driver import Driver
from venv_management.errors import ImplementationNotFound, CommandNotFound, PythonNotFound
from venv_management.utilities import parse_package_arg
from venv_management.shell import (
    sub_shell_command, get_status_output,
    remove_interactive_shell_warnings,
)
from venv_management.environment import shell_is_interactive

logger = logging.getLogger(__name__)


# A part of the error message when an invalid Python version is specified in the command line
NO_SUCH_PYTHON_PATTERN = r"is not installed in pyenv"
NO_SUCH_PYTHON_REGEX = re.compile(NO_SUCH_PYTHON_PATTERN)

DESTINATION_PATTERN = r"dest=([^,]+)"
DESTINATION_REGEX = re.compile(DESTINATION_PATTERN)


[docs]class PyEnvVirtualEnvDriver(Driver): def _check_availability(self): try: self.list_virtual_envs() except CommandNotFound as e: raise ImplementationNotFound(f"No implementation for {self.name} ; {str(e)}")
[docs] def list_virtual_envs(self) -> List[str]: """A list of virtualenv names. Returns: A list of string names in case-sensitive alphanumeric order. Raises: FileNotFoundError: If virtualenvwrapper.sh could not be located. """ list_virtual_envs_command = "pyenv virtualenvs --bare" command = sub_shell_command(list_virtual_envs_command) logger.debug(f"Running command: {command}") status, output = get_status_output(command) if status == 0: return output.splitlines(keepends=False) logger.error(output) if status == 127: # Pyenv is not installed raise CommandNotFound(f"{output}. Have you installed pyenv?") if status == 1: # The subcommand passed to Pyenv is not recognized raise CommandNotFound(f"{output}. Have you installed pyenv-virtualenv?") raise RuntimeError(output)
[docs] def make_virtual_env( self, name, *, python=None, project_path=None, packages=None, requirements_file=None, system_site_packages=False, pip=True, setuptools=True, wheel=True, ): """Make a virtual env. Args: name: The name of the virtual environment. project_path: An optional path to a project which will be associated with the new virtual environment. (not supported by pyenv-virtualenv) packages: An optional sequence of package names for packages to be installed. (not supported by pyenv-virtualenv) requirements_file: An optional path to a requirements file to be installed. (not supported by pyenv-virtualenv) python: The target interpreter for which to create a virtual environment, either the name of the executable, or full path. system_site_packages: If True, give access to the system site packages. pip: If True, or 'latest' the latest pip will be installed. If False, pip will not be installed. If 'bundled', the bundled version will be installed. If a specific version string is given, that version will be installed. setuptools: If True, or 'latest' the latest setuptools will be installed. If False, setuptools will not be installed. If 'bundled', the bundled version will be installed. If a specific version string is given, that version will be installed. wheel: If True, or 'latest' the latest pip will be installed. If False, pip will not be installed. If 'bundled', the bundled version will be installed. If a specific version string is given, that version will be installed. Returns: The Path to the root of the virtualenv, or None if the path could not be determined. Raises: CommandNotFound: If the required command could not be found. RuntimeError: If the virtualenv could not be created. """ python_arg = f"--python={python}" if python else "" system_site_packages_arg = "--system-site-packages" if system_site_packages else "" pip_arg = parse_package_arg("pip", pip) setuptools_arg = parse_package_arg("setuptools", setuptools) wheel_arg = parse_package_arg("wheel", wheel) args = " ".join( ( python_arg, system_site_packages_arg, pip_arg, setuptools_arg, wheel_arg, ) ) # Create create_command = sub_shell_command(f"pyenv virtualenv {args} {name}") logger.info(create_command) status, output = get_status_output(create_command) m = NO_SUCH_PYTHON_REGEX.search(output) if m is not None: raise PythonNotFound(f"Could not locate Python {python} ; {m.group(0)}") # Get current Python version python_version_command = sub_shell_command("pyenv version-name") status, output = get_status_output(python_version_command) if status != 0: raise RuntimeError(f"Could not run {python_version_command}") python_version = output.strip() # Activate activate_command = sub_shell_command(f"pyenv activate {name}") status, output = get_status_output(activate_command) if status != 0: raise RuntimeError(f"Could not activate virtual environment: {name}") # Get the path to the virtual environment root get_path_command = sub_shell_command(f"pyenv prefix {name}") status, output = get_status_output(get_path_command) if status != 0: raise RuntimeError(f"Could not get path for virtual environment: {name}") if output: return Path(output) raise RuntimeError(f"Could not get path for virtual environment: {name}")
[docs] def remove_virtual_env(self, name): """Remove a virtual environment. Args: name: The name of the virtual environment to remove. Raises: ValueError: If there is no environment with the given name. RuntimeError: If the virtualenv could not be removed. """ if not name: raise ValueError("The name passed to remove_virtual_env cannot be empty") if name not in self.list_virtual_envs(): raise ValueError(f"No virtualenv named {name}") command = sub_shell_command(f"pyenv uninstall -f {name}") logger.debug("command = %r", command) process = subprocess.run( command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding=sys.getdefaultencoding() ) stderr = process.stderr if shell_is_interactive(): stderr = remove_interactive_shell_warnings(stderr) if len(stderr) != 0: raise ValueError(stderr)
[docs] def resolve_virtual_env(self, name: str) -> Path: """Resolve the path to a virtual environment. Args: name: The name of the virtual environment. Returns: The path to the virtual environment in the $HOME/.pyenv/versions/<virtual_env_name> format. """ if not name: raise ValueError("The name passed to resolve_virtual_env cannot be empty") names = self.list_virtual_envs() if name not in names: raise ValueError( f"No virtual environment called {name!r} is found. " f"Found {', '.join(map(repr, names))}'" ) command = sub_shell_command(f"pyenv prefix {name}") logger.debug("command = %r", command) status, output = get_status_output(command) return Path(output)