-
Notifications
You must be signed in to change notification settings - Fork 0
/
auto_venv.py
139 lines (105 loc) · 4.63 KB
/
auto_venv.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
"""Automatically Creates Virtual Environment and Installs Requirements."""
__version__ = "0.1.0"
def init(filename: str, requires: list[str], fancy=False, quiet=False, dot_pth=False) -> None:
"""Create virtual environment and install dependencies.
Args:
filename (str) : calling script's filename including full path.
requires (list[str]) : list of requirements to install
fancy (bool, optional) : fancy/ANSI printout. (Defaults to False)
quiet (bool, optional) : quiet (no) messages. (Defaults to False)
dot_pth (bool, optional) : create .pth link to the .venv (Defaults to False)
"""
import getpass
import os
import subprocess
import site
import sys
import uuid
from pathlib import Path
def run(*args, **kwargs) -> None:
"""
_summary_
"""
try:
return subprocess.run(*args, **kwargs)
except KeyboardInterrupt:
sys.stderr.write("\nCancelled!\n")
sys.exit()
def msg(s: str) -> None:
"""
_summary_
Args:
s (str): string to print.
"""
if quiet:
return
sys.stderr.write(s + "\n")
if "AUTO_VENV_FANCY" in os.environ:
fancy = (os.environ.get("AUTO_VENV_FANCY", "False").lower() in ("1", "y", "yes", "true"))
if "AUTO_VENV_QUIET" in os.environ:
quiet = (os.environ.get("AUTO_VENV_QUIET", "False").lower() in ("1", "y", "yes", "true"))
if "AUTO_VENV_DOT_PTH" in os.environ:
dot_pth = (os.environ.get("AUTO_VENV_DOT_PTH", "False").lower() in ("1", "y", "yes", "true"))
prefix = "│ " if fancy else ""
arrow = "➜" if fancy else ">"
check = "✓" if fancy else "*"
# Name of the virtual environment
venv_name = ".venv"
username = getpass.getuser()
unique_id = uuid.uuid5(uuid.NAMESPACE_DNS, f"{username}:{filename}")
venv_fullname = f"{venv_name}_{unique_id}_{username}_{Path(filename).name}"
venv_root = Path(os.environ.get("VENV_AUTO_ROOT", "/tmp"))
# Path to the virtual environment
venv_path = venv_root / venv_fullname
site_packages_path = venv_path / "lib" / f"python{sys.version_info.major}.{sys.version_info.minor}" / "site-packages"
sys.path = [path for path in sys.path if "-packages" not in path] + [str(site_packages_path)]
bin_path = venv_path / "bin"
os.environ["PATH"] += os.pathsep + str(bin_path)
msg("╭" + "─" * 80 if fancy else "-" * 80)
all_requirements_met = False
# Create the virtual environment if it doesn't exist
if venv_path.exists():
msg(f"{prefix}{arrow} Virtual environment already exists ...")
msg(f"{prefix} '{venv_path}'")
result = run(
[f"{bin_path}/python", "-m", "pip", "freeze"],
capture_output = True,
text = True
)
all_packages = [package.lower() for package in result.stdout.strip("\n").split("\n")]
all_requirements_met = set(requires).issubset(set(all_packages))
if all_requirements_met:
msg(f"{prefix}{arrow} All requirements are met.")
else:
msg(f"{prefix}{arrow} First run or some requirements were not found. Reinstalling ...")
msg(f"{prefix}{arrow} Creating virtual environment ...")
msg(f"{prefix} '{venv_path}'")
run([sys.executable, "-m", "venv", "" if all_requirements_met else "--clear", venv_path], check=True, start_new_session=True)
if dot_pth:
# Create .pth to point to this venv
pth_file = Path(f"{site.USER_SITE}") / f"{venv_fullname.removeprefix(".")}.pth"
msg(f"{prefix}{arrow} Setting up PYTHONPATH ...")
msg(f"{prefix} '{pth_file}'")
pth_file.parent.mkdir(exist_ok=True, parents=True)
pth_file.write_text(f"{site_packages_path}")
msg(f"{prefix}{arrow} Upgrading 'pip' ...")
run(
[f"{bin_path}/python", "-m", "pip", "install", "--upgrade", "pip"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
start_new_session=True
)
# Install the required packages
msg(f"{prefix}{arrow} Installing requirements:")
for req in requires:
run(
[f"{bin_path}/python", "-m", "pip", "install", req],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
start_new_session=True
)
msg(f"{prefix} {check} {req}")
msg(f"{prefix}{arrow} Continuing with the script ...")
msg("╰" + "─" * 80 if fancy else "-" * 80)