Skip to content

Instantly share code, notes, and snippets.

@jasco
Last active March 20, 2021 11:31
Show Gist options
  • Save jasco/bb59d4ad514057fe1da4fae5c760440c to your computer and use it in GitHub Desktop.
Save jasco/bb59d4ad514057fe1da4fae5c760440c to your computer and use it in GitHub Desktop.
Rebuild virtualenv symlinks after python upgrade

After upgrading Python, the virtualenv Python symlinks sometimes break. This will manifest as an error when invoking Python within the target virtualenv. This script walks through each virtualenv directory searching for broken links. It will recreate the virtualenv if necessary. It is intended to work with a virtualenvwrapper managed environment in which all virtual environments are centralized under a specific directory.

Assumptions:

  • Virtual environments collected under $HOME/.virtualenvs/
  • GNU compatible find is called gfind.
  • virtualenv command is located at /usr/local/bin/virtualenv

Warning, for reasons I do not currently remember, it looks like I convert python3.6 to the python3. Also that pypy virtualenvs are skipped.

#!/usr/bin/env python3
# For each directory in ~/.virtualenvs check for bad links
# When found, determine python executable linked, delete bad symlinks, reinvoke virtualenv with python flavor.
import os
import sys
try:
from subprocess import run
except ImportError:
print("Unable to load subprocess.run. Requires at least Python 3.5+")
sys.exit(1)
VENV_PATH = os.path.join(os.environ['HOME'], '.virtualenvs')
VIRTUALENV_EXEC = '/usr/local/bin/virtualenv'
GFIND_EXEC = 'gfind'
def get_venvs():
return [os.path.join(VENV_PATH, o) for o in os.listdir(VENV_PATH) if os.path.isdir(os.path.join(VENV_PATH,o))]
def badlink(fpath):
def islink(link_path):
return os.path.islink(link_path)
def islinkvalid(link_path):
resolved_path = os.readlink(link_path)
return os.path.exists(resolved_path)
return islink(fpath) and not islinkvalid(fpath)
def has_badlinks(venv_path):
for dirpath, _, file_list in os.walk(venv_path):
for fname in file_list:
fpath = os.path.join(dirpath, fname)
if badlink(fpath):
return True
return False
def print_badlinks(venv_path):
for dirpath, _, file_list in os.walk(venv_path):
for fname in file_list:
fpath = os.path.join(dirpath, fname)
if badlink(fpath):
print(fpath)
def get_python_exec(path):
full_path = os.path.join(path, 'bin/python')
#print(full_path, os.path.islink(full_path))
#print(os.path.join(path, '.python'), os.path.islink(os.path.join(path, '.python')))
if os.path.islink(full_path):
return os.readlink(full_path)
elif os.path.isfile(full_path):
return 'python'
else:
return 'unknown'
for venv in get_venvs():
needs_update = has_badlinks(venv)
pyexec = get_python_exec(venv)
print('CHECKING {}...'.format(venv))
if not pyexec:
print('SKIPPING {} because python version not found'.format(venv))
continue
if pyexec == 'unknown':
print('SKIPPING {} because python version is unknown'.format(venv))
continue
if needs_update:
print('UPDATING {} with exec {}...'.format(venv, pyexec))
if pyexec.endswith('pypy'):
print("skipping pypy")
continue
if pyexec == 'python3.6':
print ('Using python3 rather than python3.6 for {}'.format(venv))
pyexec = 'python3'
# Remove dead links
run([GFIND_EXEC, venv, '-type', 'l', '-xtype', 'l', '-delete'], check=True)
# Recreate venv
run([VIRTUALENV_EXEC, '-p', pyexec, venv], check=True)
print('SUCCESS {}'.format(venv))
else:
print('OK {}'.format(venv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment