Last active
June 25, 2019 13:14
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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