-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathenv_bootstrap.py
More file actions
87 lines (74 loc) · 2.54 KB
/
Copy pathenv_bootstrap.py
File metadata and controls
87 lines (74 loc) · 2.54 KB
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
# file: env_bootstrap.py
from __future__ import annotations
import hashlib
import inspect
import os
import shutil
import subprocess
import sys
from pathlib import Path
# All required packages - no external requirements.txt needed
PACKAGES = [
"selenium>=4.15.0",
"python-dotenv>=1.0.0",
"webdriver-manager>=4.0.0",
]
def _caller_dir() -> Path:
frm = inspect.stack()[2] if len(inspect.stack()) >= 3 else inspect.stack()[1]
return Path(frm.filename).resolve().parent
def _venv_python(venv_dir: Path) -> Path:
if os.name == "nt":
return venv_dir / "Scripts" / "python.exe"
return venv_dir / "bin" / "python"
def _in_that_venv(venv_dir: Path) -> bool:
try:
return Path(sys.prefix).resolve() == venv_dir.resolve()
except Exception:
return False
def _hash_packages(packages: list[str]) -> str:
h = hashlib.sha256()
h.update("\n".join(sorted(packages)).encode("utf-8"))
return h.hexdigest()
def ensure_venv(
venv_name: str = ".venv",
flag_filename: str = ".deps_ok",
) -> None:
"""
Ensures:
1) A venv named `venv_name` exists next to the caller file.
2) The current process runs using that venv's Python (re-execs if needed).
3) Installs PACKAGES only once per content-hash, tracked by `flag_filename`.
"""
caller_dir = _caller_dir()
venv_dir = caller_dir / venv_name
vpy = _venv_python(venv_dir)
# 1) Create venv if missing
if not venv_dir.exists():
subprocess.check_call([sys.executable, "-m", "venv", str(venv_dir)])
# 2) Re-exec under that venv if not already
if not _in_that_venv(venv_dir):
os.execv(str(vpy), [str(vpy), *sys.argv])
# 3) Install packages with hash flag
flag_path = caller_dir / flag_filename
current_hash = _hash_packages(PACKAGES)
flag_ok = False
try:
if flag_path.exists():
saved = flag_path.read_text(encoding="utf-8").strip()
flag_ok = saved == current_hash
except Exception:
flag_ok = False
if not flag_ok:
try:
subprocess.check_call([sys.executable, "-m", "pip", "--version"])
except subprocess.CalledProcessError:
pass
if shutil.which("pip") is None:
try:
subprocess.check_call([sys.executable, "-m", "ensurepip", "--upgrade"])
except Exception:
pass
subprocess.check_call([sys.executable, "-m", "pip", "install", *PACKAGES])
flag_path.write_text(current_hash, encoding="utf-8")
if __name__ == "__main__":
ensure_venv()