pyscalpel.venv

This module provides reimplementations of Python virtual environnements scripts

This is designed to be used internally, but in the case where the user desires to dynamically switch venvs using this, they should ensure the selected venv has the dependencies required by Scalpel.

  1"""
  2This module provides reimplementations of Python virtual environnements scripts
  3
  4This is designed to be used internally, 
  5but in the case where the user desires to dynamically switch venvs using this,
  6they should ensure the selected venv has the dependencies required by Scalpel.
  7"""
  8
  9import os
 10import sys
 11import glob
 12import subprocess
 13
 14_old_prefix = sys.prefix
 15_old_exec_prefix = sys.exec_prefix
 16
 17# Python's virtualenv's activate/deactivate ported from the bash script to Python code.
 18# https://docs.python.org/3/library/venv.html#:~:text=each%20provided%20path.-,How%20venvs%20work%C2%B6,-When%20a%20Python
 19
 20# pragma: no cover
 21
 22
 23def deactivate() -> None:  # pragma: no cover
 24    """Deactivates the current virtual environment."""
 25    if "_OLD_VIRTUAL_PATH" in os.environ:
 26        os.environ["PATH"] = os.environ["_OLD_VIRTUAL_PATH"]
 27        del os.environ["_OLD_VIRTUAL_PATH"]
 28    if "_OLD_VIRTUAL_PYTHONHOME" in os.environ:
 29        os.environ["PYTHONHOME"] = os.environ["_OLD_VIRTUAL_PYTHONHOME"]
 30        del os.environ["_OLD_VIRTUAL_PYTHONHOME"]
 31    if "VIRTUAL_ENV" in os.environ:
 32        del os.environ["VIRTUAL_ENV"]
 33
 34    sys.prefix = _old_prefix
 35    sys.exec_prefix = _old_exec_prefix
 36
 37
 38def activate(path: str | None) -> None:  # pragma: no cover
 39    """Activates the virtual environment at the given path."""
 40    deactivate()
 41
 42    if path is None:
 43        return
 44
 45    virtual_env = os.path.abspath(path)
 46    os.environ["_OLD_VIRTUAL_PATH"] = os.environ.get("PATH", "")
 47    os.environ["VIRTUAL_ENV"] = virtual_env
 48
 49    old_pythonhome = os.environ.pop("PYTHONHOME", None)
 50    if old_pythonhome:
 51        os.environ["_OLD_VIRTUAL_PYTHONHOME"] = old_pythonhome
 52
 53    if os.name == "nt":
 54        site_packages_paths = os.path.join(virtual_env, "Lib", "site-packages")
 55    else:
 56        site_packages_paths = glob.glob(
 57            os.path.join(virtual_env, "lib", "python*", "site-packages")
 58        )
 59
 60    if not site_packages_paths:
 61        raise RuntimeError(
 62            f"No 'site-packages' directory found in virtual environment at {virtual_env}"
 63        )
 64
 65    site_packages = site_packages_paths[0]
 66    sys.path.insert(0, site_packages)
 67    sys.prefix = virtual_env
 68    sys.exec_prefix = virtual_env
 69
 70
 71def install(*packages: str) -> int:  # pragma: no cover
 72    """Install a Python package in the current venv.
 73
 74    Returns:
 75        int: The pip install command exit code.
 76    """
 77    pip = os.path.join(sys.prefix, "bin", "pip")
 78    return subprocess.call([pip, "install", "--require-virtualenv", "--", *packages])
 79
 80
 81def uninstall(*packages: str) -> int:  # pragma: no cover
 82    """Uninstall a Python package from the current venv.
 83
 84    Returns:
 85        int: The pip uninstall command exit code.
 86    """
 87    pip = os.path.join(sys.prefix, "bin", "pip")
 88    return subprocess.call(
 89        [pip, "uninstall", "--require-virtualenv", "-y", "--", *packages]
 90    )
 91
 92
 93def create(path: str) -> int:  # pragma: no cover
 94    """Creates a Python venv on the given path
 95
 96    Returns:
 97        int: The `python3 -m venv` command exit code.
 98    """
 99    return subprocess.call(["python3", "-m", "venv", "--", path])
100
101
102def create_default() -> str:  # pragma: no cover
103    """Creates a default venv in the user's home directory
104        Only creates it if the directory doesn't already exist
105
106    Returns:
107        str: The venv directory path.
108    """
109    scalpel_venv = os.path.join(os.path.expanduser("~"), ".scalpel", "venv_default")
110    # Don't recreate the venv if it alreay exists
111    if not os.path.exists(scalpel_venv):
112        os.makedirs(scalpel_venv, exist_ok=True)
113        create(scalpel_venv)
114    return scalpel_venv
def deactivate() -> None:
24def deactivate() -> None:  # pragma: no cover
25    """Deactivates the current virtual environment."""
26    if "_OLD_VIRTUAL_PATH" in os.environ:
27        os.environ["PATH"] = os.environ["_OLD_VIRTUAL_PATH"]
28        del os.environ["_OLD_VIRTUAL_PATH"]
29    if "_OLD_VIRTUAL_PYTHONHOME" in os.environ:
30        os.environ["PYTHONHOME"] = os.environ["_OLD_VIRTUAL_PYTHONHOME"]
31        del os.environ["_OLD_VIRTUAL_PYTHONHOME"]
32    if "VIRTUAL_ENV" in os.environ:
33        del os.environ["VIRTUAL_ENV"]
34
35    sys.prefix = _old_prefix
36    sys.exec_prefix = _old_exec_prefix

Deactivates the current virtual environment.

def activate(path: str | None) -> None:
39def activate(path: str | None) -> None:  # pragma: no cover
40    """Activates the virtual environment at the given path."""
41    deactivate()
42
43    if path is None:
44        return
45
46    virtual_env = os.path.abspath(path)
47    os.environ["_OLD_VIRTUAL_PATH"] = os.environ.get("PATH", "")
48    os.environ["VIRTUAL_ENV"] = virtual_env
49
50    old_pythonhome = os.environ.pop("PYTHONHOME", None)
51    if old_pythonhome:
52        os.environ["_OLD_VIRTUAL_PYTHONHOME"] = old_pythonhome
53
54    if os.name == "nt":
55        site_packages_paths = os.path.join(virtual_env, "Lib", "site-packages")
56    else:
57        site_packages_paths = glob.glob(
58            os.path.join(virtual_env, "lib", "python*", "site-packages")
59        )
60
61    if not site_packages_paths:
62        raise RuntimeError(
63            f"No 'site-packages' directory found in virtual environment at {virtual_env}"
64        )
65
66    site_packages = site_packages_paths[0]
67    sys.path.insert(0, site_packages)
68    sys.prefix = virtual_env
69    sys.exec_prefix = virtual_env

Activates the virtual environment at the given path.

def install(*packages: str) -> int:
72def install(*packages: str) -> int:  # pragma: no cover
73    """Install a Python package in the current venv.
74
75    Returns:
76        int: The pip install command exit code.
77    """
78    pip = os.path.join(sys.prefix, "bin", "pip")
79    return subprocess.call([pip, "install", "--require-virtualenv", "--", *packages])

Install a Python package in the current venv.

Returns: int: The pip install command exit code.

def uninstall(*packages: str) -> int:
82def uninstall(*packages: str) -> int:  # pragma: no cover
83    """Uninstall a Python package from the current venv.
84
85    Returns:
86        int: The pip uninstall command exit code.
87    """
88    pip = os.path.join(sys.prefix, "bin", "pip")
89    return subprocess.call(
90        [pip, "uninstall", "--require-virtualenv", "-y", "--", *packages]
91    )

Uninstall a Python package from the current venv.

Returns: int: The pip uninstall command exit code.

def create(path: str) -> int:
 94def create(path: str) -> int:  # pragma: no cover
 95    """Creates a Python venv on the given path
 96
 97    Returns:
 98        int: The `python3 -m venv` command exit code.
 99    """
100    return subprocess.call(["python3", "-m", "venv", "--", path])

Creates a Python venv on the given path

Returns: int: The python3 -m venv command exit code.

def create_default() -> str:
103def create_default() -> str:  # pragma: no cover
104    """Creates a default venv in the user's home directory
105        Only creates it if the directory doesn't already exist
106
107    Returns:
108        str: The venv directory path.
109    """
110    scalpel_venv = os.path.join(os.path.expanduser("~"), ".scalpel", "venv_default")
111    # Don't recreate the venv if it alreay exists
112    if not os.path.exists(scalpel_venv):
113        os.makedirs(scalpel_venv, exist_ok=True)
114        create(scalpel_venv)
115    return scalpel_venv

Creates a default venv in the user's home directory Only creates it if the directory doesn't already exist

Returns: str: The venv directory path.