Skip to content

Instantly share code, notes, and snippets.

@LyleScott
Last active June 25, 2019 13:14
Show Gist options
  • Save LyleScott/ce29bed49afc8bdd29302c8157e41bca to your computer and use it in GitHub Desktop.
Save LyleScott/ce29bed49afc8bdd29302c8157e41bca to your computer and use it in GitHub Desktop.
A little Python 3.x script i wrote to help me manage local virtual environments
#!/usr/bin/env python3
"""
A custom utility to manage Python virtualenvs. Requires Python 3.6+
I've written it mainly as a wrapper around venv, but it's pluggable.
https://ls3.io // lyle@ls3.io // https://www.linkedin.com/in/lylescott
USAGE
venvs.py list [-r]
venvs.py new <env-name>
venvs.py rm <env-name>
venvs.py use <env-name>
To use more easily, I'd recommend:
- moving this script to /usr/local/bin/venvs
- chmod +x /usr/local/bin/venvs (makes executable)
EXAMPLE OUTPUT
>>> venvs.py list -r (by most recently ACCESSED)
>>> venvs.py list (by VENV name in alphabetical order)
VENV PYTHON CREATED MODIFIED ACCESSED
analytics-example Python 3.6.5 2019-03-07 15:10:24 2019-03-07 15:10:24 2019-06-24 20:16:43
api-loader Python 3.7.3 2019-03-08 13:03:13 2019-03-08 13:03:13 2019-06-24 20:16:43
kafka-to-python Python 3.7.0 2019-05-31 16:32:02 2019-05-31 16:32:02 2019-06-24 20:16:43
>>> venvs.py active
lyle1 @ /Users/lyle/.venvs/lyle1 using Python 3.7.3
>>> $(venvs use api-loader)
"""
import os
import shutil
import stat
import subprocess
import sys
import tempfile
import urllib3
from datetime import datetime
VENV_TOOL = '/Users/lyle/.pyenv/versions/3.7.3/bin/python3'
VENV_TOOL_ARGS = '-m venv'
VENV_PATH = '/Users/lyle/.venvs'
COL_WIDTH = 20
def _usage(exit=True, return_code=1):
print(f'USAGE')
executable = os.path.basename(sys.argv[0])
print(f'{executable} list')
print(f'{executable} new <env-name>')
print(f'{executable} rm <env-name>')
print(f'{executable} use <env-name>')
print(f'{executable} update (redownloads gist and replaces self with it)')
if exit:
sys.exit(return_code)
def _bail_if_not_exists(path: str) -> bool:
if not os.path.exists(path):
print(f'`{path}` does not exist')
sys.exit(0)
def new_venv():
try:
venv_env = sys.argv[2]
except IndexError:
_usage(return_code=1)
venv_env_path = os.path.join(VENV_PATH, venv_env)
print(f'Creating venv...')
print(f'VENV_TOOL = {VENV_TOOL} {VENV_TOOL_ARGS}')
print(f'VENV_PATH = {venv_env_path}')
print()
if os.path.exists(venv_env_path):
to_delete_or_not = input(f'The path `{venv_env_path}` exists! '
'[d]elete or [c]ontinue?\nd/C: ')
if to_delete_or_not.lower() == 'y':
print(f'Deleting `{venv_env_path}`', end=' ')
shutil.rmtree(venv_env_path)
print('...Done\n')
print('Creating venv...')
cmd = ' '.join((VENV_TOOL, VENV_TOOL_ARGS, venv_env_path)).split()
out = subprocess.run(cmd, capture_output=True, check=True)
print(out)
print(f'\n. {venv_env_path}')
def list_venv():
by_recent = '-r' in sys.argv
sort_key = os.path.getmtime if by_recent else os.path.basename
results = sorted(os.scandir(VENV_PATH), key=sort_key)
files = []
longest_name = 0
for thing in results:
if not os.path.isdir(thing):
continue
files.append(thing)
longest_name = max(longest_name, len(thing.name))
print(' '.join((
'VENV'.ljust(longest_name + 1),
'PYTHON'.ljust(COL_WIDTH - 5),
'CREATED'.ljust(COL_WIDTH),
'MODIFIED'.ljust(COL_WIDTH),
'ACCESSED'.ljust(COL_WIDTH),
)))
for thing in files:
DATETIME_FMT = '%Y-%m-%d %H:%M:%S'
t_fmt = lambda x: datetime.fromtimestamp(x).strftime(DATETIME_FMT) # noqa: E731
stat = thing.stat()
access_t = t_fmt(stat.st_atime)
create_t = t_fmt(stat.st_ctime)
modified_t = t_fmt(stat.st_mtime)
cmd = [os.path.join(thing.path, 'bin', 'python'), '--version']
out = subprocess.run(cmd, capture_output=True, check=True)
print(
thing.name.ljust(longest_name + 1),
out.stdout.decode('utf8').strip().ljust(COL_WIDTH - 5),
create_t.ljust(COL_WIDTH),
modified_t.ljust(COL_WIDTH),
access_t.ljust(COL_WIDTH),
)
def rm_venv():
try:
venv_env = sys.argv[2]
except IndexError:
_usage(return_code=1)
venv_env_path = os.path.join(VENV_PATH, venv_env)
_bail_if_not_exists(venv_env_path)
to_delete_or_not = input(f'Delete `{venv_env_path}`? [y]es or [n]o?\nN/y: ')
if to_delete_or_not.lower() == 'y':
print(f'Deleting `{venv_env_path}`', end=' ')
shutil.rmtree(venv_env_path)
print('...Done\n')
def use_venv():
try:
venv_env = sys.argv[2]
except IndexError:
_usage(return_code=1)
venv_env_path = os.path.join(VENV_PATH, venv_env, 'bin', 'activate')
_bail_if_not_exists(venv_env_path)
print(f'source {venv_env_path}')
def active_venv():
virtual_env = os.environ.get('VIRTUAL_ENV')
if virtual_env is None:
print('No venv is currently active')
sys.exit(0)
venv_name = os.path.basename(virtual_env)
cmd = [os.path.join(virtual_env, 'bin', 'python'), '--version']
out = subprocess.run(cmd, capture_output=True, check=True)
py_version = out.stdout.decode("utf8").strip()
print(f'{venv_name} @ {virtual_env} using {py_version}')
def update():
# Download the gist of this script to a temp file.
urllib3.disable_warnings()
http = urllib3.PoolManager()
url = 'https://gist.githubusercontent.com/LyleScott/ce29bed49afc8bdd29302c8157e41bca/raw/'
r = http.request('GET', url, preload_content=False)
fd, file_path = tempfile.mkstemp()
with open(file_path, 'wb') as out_file:
while True:
data = r.read(1024)
if not data:
break
out_file.write(data)
self_file = os.path.abspath(__file__)
# Copy file from tempfile to final destination (the current path of script).
shutil.copyfile(file_path, self_file)
# Make executable.
st = os.stat(self_file)
os.chmod(self_file, st.st_mode | stat.S_IEXEC)
def _main():
try:
cmd = sys.argv[1]
except IndexError:
_usage()
try:
globals()[f'{cmd}_venv']()
except KeyError:
try:
globals()[cmd]()
except KeyError:
_usage()
if __name__ == '__main__':
_main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment