evalstate's picture
download
raw
5.04 kB
#!/usr/bin/env python3
"""Create an isolated Python environment for SageMaker work. Cross-platform.
Idempotent. Prefers uv, falls back to the stdlib venv module.
Handles the platform difference in venv layout: the interpreter lives at
`<venv>/bin/python` on macOS/Linux and `<venv>\\Scripts\\python.exe` on Windows.
The path it prints is the correct one for the host OS.
Usage:
python setup_env.py [VENV_DIR=.venv] [PYTHON_VERSION=3.12]
"""
from __future__ import annotations
import os
import shutil
import subprocess
import sys
from pathlib import Path
# 3.10-3.12 is the safe zone for modern boto3 + awscli. 3.13+ may work but ML
# libs lag on wheel availability.
SUPPORTED_MIN = (3, 10)
SUPPORTED_MAX = (3, 12)
IS_WINDOWS = os.name == "nt"
def log(msg: str) -> None:
print(f"[setup_env] {msg}", file=sys.stderr, flush=True)
def parse_version(v: str) -> tuple[int, int]:
try:
major, minor = (int(p) for p in v.split(".")[:2])
return (major, minor)
except ValueError:
log(f"ERROR: invalid Python version '{v}' (expected e.g. 3.12)")
sys.exit(1)
def venv_python(venv_dir: Path) -> Path:
"""Path to the interpreter inside a venv, per platform."""
if IS_WINDOWS:
return venv_dir / "Scripts" / "python.exe"
return venv_dir / "bin" / "python"
def interpreter_version(python_path: Path) -> str | None:
if not python_path.exists():
return None
proc = subprocess.run(
[str(python_path), "-c",
"import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"],
capture_output=True, text=True,
)
return proc.stdout.strip() if proc.returncode == 0 else None
def find_base_python(version: str) -> str | None:
"""Locate a base interpreter for the requested version for `python -m venv`."""
# Try `pythonX.Y` (Unix) / `pythonX.Y.exe`, then the Windows `py` launcher.
candidate = shutil.which(f"python{version}")
if candidate:
return candidate
if IS_WINDOWS and shutil.which("py"):
probe = subprocess.run(["py", f"-{version}", "--version"], capture_output=True, text=True)
if probe.returncode == 0:
return f"py -{version}" # sentinel; expanded by caller
return None
def main() -> int:
venv_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(".venv")
python_version = sys.argv[2] if len(sys.argv) > 2 else "3.12"
ver = parse_version(python_version)
if not (SUPPORTED_MIN <= ver <= SUPPORTED_MAX):
lo = ".".join(map(str, SUPPORTED_MIN))
hi = ".".join(map(str, SUPPORTED_MAX))
log(f"ERROR: Python {python_version} outside supported range {lo}-{hi}")
log("Use 3.10, 3.11, or 3.12.")
return 1
installer = "uv" if shutil.which("uv") else "venv"
if installer == "venv":
log("uv not found — falling back to python + venv. Consider: https://docs.astral.sh/uv/")
py = venv_python(venv_dir)
# Reuse existing env only if Python version matches.
current = interpreter_version(py)
if current == python_version:
log(f"Env exists at {venv_dir} with Python {current} — reusing")
else:
if current is not None:
log(f"Env at {venv_dir} uses Python {current} (wanted {python_version}) — recreating")
shutil.rmtree(venv_dir)
if installer == "uv":
uv = shutil.which("uv")
if subprocess.run([uv, "venv", "--python", python_version, str(venv_dir)]).returncode != 0:
log("ERROR: `uv venv` failed.")
return 1
else:
base = find_base_python(python_version)
if not base:
log(f"ERROR: python{python_version} not found on PATH.")
log("Install via pyenv/asdf/brew/system package manager, the Windows installer, or install uv.")
return 1
base_cmd = base.split() if base.startswith("py ") else [base]
if subprocess.run([*base_cmd, "-m", "venv", str(venv_dir)]).returncode != 0:
log("ERROR: `python -m venv` failed.")
return 1
log(f"Created env at {venv_dir}")
requirements = Path(__file__).resolve().parent.parent / "requirements.txt"
if not requirements.is_file():
log(f"ERROR: requirements.txt not found at {requirements}")
return 1
log(f"Installing from {requirements}")
if installer == "uv":
uv = shutil.which("uv")
rc = subprocess.run(
[uv, "pip", "install", "--python", str(py), "--upgrade", "-r", str(requirements)]
).returncode
else:
subprocess.run([str(py), "-m", "pip", "install", "--upgrade", "pip"])
rc = subprocess.run(
[str(py), "-m", "pip", "install", "--upgrade", "-r", str(requirements)]
).returncode
if rc != 0:
log("ERROR: dependency install failed.")
return 1
log(f"Done. Invoke directly: {py} <script>")
return 0
if __name__ == "__main__":
sys.exit(main())

Xet Storage Details

Size:
5.04 kB
·
Xet hash:
98df6634007bf3677c0ad8c2688d141ce1baaf986adc92a202c1531553b74da1

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.