The Hook
I was tired of my automated cron jobs and CLI scripts failing simply because I forgot to run source venv/bin/activate. Relying on external shell wrappers or manual activation is a point of failure I wanted to eliminate. Instead of managing the environment from the outside, I found a way to make the script “self-aware” and re-execute itself using the correct interpreter if it detects it’s running in the wrong context.
The Implementation
If the script detects it’s running in a global environment, it checks for a .venv. If that’s missing, it creates the environment, installs your dependencies, and then restarts itself.
import os
import sys
import pathlib
import subprocess
def bootstrap():
"""Ensures the script runs inside a local venv, creating it if necessary."""
root = pathlib.Path(__file__).parent.resolve()
venv_path = root / ".venv"
# Platform-specific executable path
if sys.platform == "win32":
venv_python = venv_path / "Scripts" / "python.exe"
else:
venv_python = venv_path / "bin" / "python"
# 1. If we are already in the venv, just return and run the script
if sys.executable == str(venv_python):
return
# 2. If venv doesn't exist, create it and install requirements
if not venv_python.exists():
print("---> Creating virtual environment...")
subprocess.run([sys.executable, "-m", "venv", str(venv_path)], check=True)
reqs = root / "requirements.txt"
if reqs.exists():
print("---> Installing dependencies...")
subprocess.run([str(venv_python), "-m", "pip", "install", "-r", str(reqs)], check=True)
# 3. Restart the script using the venv interpreter
print(f"---> Relaunching via {venv_python}...")
os.execv(str(venv_python), [str(venv_python)] + sys.argv)
# Call this at the absolute top
bootstrap()
# --- Normal script starts here ---
import pandas as pd # Example of a heavy lib that needs the venv
print("Successfully running in the isolated environment.")The “Why”
This pattern creates a “fire and forget” experience for internal tools. It removes the friction of environment management for teammates who might just want to run ./script.py without worrying about their current shell state. It’s particularly powerful for crontab entries or CI/CD runners where you want to guarantee the environment without writing extra boilerplate in your config files.