Notice that pip freeze
for the invoking environment remains clean.
And this is what it looks like on disk:
import sys | |
import uuid | |
import subprocess | |
import os | |
from subprocess import CalledProcessError | |
from typing import List | |
class RunContext: | |
# venv_folder is the directory in which the venv dir will be created | |
# python_executable is the python executable to use to create the venv, defaults to _invoking_ python executable | |
def __init__(self, venv_folder: str, python_executable: str = sys.executable): | |
random_guid = str(uuid.uuid4()) | |
self.venv_dir = os.path.join(venv_folder, random_guid) | |
# create venv | |
subprocess.run([python_executable, "-m", "venv", self.venv_dir], check=True) # this will throw CalledProcessError if it fails | |
# now save the python executable path | |
if os.name == "nt": | |
self.executable = os.path.join(self.venv_dir, "Scripts", f"python.exe") | |
else: | |
self.executable = os.path.abspath(os.path.join(self.venv_dir, "bin", f"python")) | |
# probably should double check the path assumption on macs | |
def run_command(self, command: List[str]): | |
command.insert(0, self.executable) | |
subprocess.run(command, check=True) # again, if anything goes wrong, we'll know. wrapper should handle CalledProcessError on failure | |
if __name__ == "__main__": | |
target_dir = "venv_dir" # this is bad, but for the sake of example this is just going to create a venv in the current directory/venv_dir | |
run1 = RunContext(target_dir) | |
try: | |
run1.run_command(["-m", "pip", "install", "requests"]) | |
except CalledProcessError as e: | |
print(f"Failed to install requests into environment {run1.venv_dir} using executable {run1.executable}: {e}") | |