Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
204 lines (176 sloc) 8.76 KB
"""Set up batch file shortcuts for simplified virtual environment workflow.
For more information, see this blog post:
http://mahugh.com/2017/04/02/python-virtual-environments/
Copyright (C) 2017 Doug Mahugh. All Rights Reserved. MIT License.
"""
import datetime
import os
import sys
import pip
def installed_not_in_req(): #------------------------------------------------<<<
"""Return list of installed packages that are not in the current
requirements.txt file."""
if not os.path.isfile('requirements.txt'):
return [] # no requirements.txt file
installed = []
for distro in pip.get_installed_distributions():
requirement = str(distro.as_requirement()).lower()
nameonly = requirement.split('=')[0]
if not nameonly in ['pip', 'setuptools']:
installed.append(requirement)
reqs = []
for line in open('requirements.txt', 'r').readlines():
this_req = line.lower().strip()
if 'git+https://github.com' in this_req:
# special handling here for packages installed from github, since
# they don't appear with samy syntax in requirements.txt and pip
nameonly = this_req.split('=')[-1].lower()
# remove from installed any same-named package ...
installed = [_ for _ in installed if not _.split('=')[0] == nameonly]
else:
reqs.append(this_req)
missing = []
for pkg in installed:
if not pkg in reqs:
missing.append(pkg)
return missing
def same_env(folder, venv): #------------------------------------------------<<<
"""Returns True if specified folder is the home folder for (above) the
specified venv folder, or a subfolder under it."""
home_folder = venv[:-4] # remove '\env' from end of venv folder
return folder.startswith(home_folder)
def setup_script(): #--------------------------------------------------------<<<
"""Create batch files for simple virtual environment workflow at the Windows
command prompt."""
print(' creating virtual environment batch files '.center(80, '-'))
# Batch files are created in the Scripts subfolder under the current Python
# executable's file location. This *should* be in the Windows path, and
# a warning message is displayed if this is not true.
python_interpreter = sys.executable # full path to Python interpreter
python_folder = os.path.split(python_interpreter)[0]
scripts_folder = os.path.join(python_folder, 'Scripts')
print(' folder: ' + scripts_folder)
verify_path(scripts_folder) # verify destination folder is in search path
# NE.bat - New Environment
# creates new virtual environment in env subfolder of current folder
write_ne_bat(scripts_folder)
# AE.bat - Activate Environment
# activates virtual environment from current env subfolder, after
# deactivating any currently active virtual environment
write_ae_bat(scripts_folder)
# DE.bat - Deactivate Environment
# deactivates currently activated virtual environment, if any
write_de_bat(scripts_folder)
# SE.bat - Show Environment
# shows current virtual environment status and current folder's relationship
write_se_bat(scripts_folder)
def show_status(): #---------------------------------------------------------<<<
"""Display current virtual environment status."""
print(' VIRTUAL ENVIRONMENT STATUS '.center(80, '-'))
active_ve = venv_folder()
if active_ve:
print('Active environment: ' + active_ve)
pydetails = sys.version.split(' ')[0] + ' ' + \
'(32-bit)' if '32 bit' in sys.version else '' + \
'(64-bit)' if '64 bit' in sys.version else ''
print(' Python version: ' + pydetails)
else:
print('Active environment: NONE')
print(80*'-')
current_folder = os.getcwd()
print(' Current folder: ' + current_folder)
if active_ve:
# if an environment is active, check whether we're under it
if same_env(current_folder, active_ve):
print(20*' ' + 'current folder matches active virtual environment')
if os.path.isfile('requirements.txt'):
# there is a requirements.txt in current folder, check accuracy
missing_reqs = installed_not_in_req()
if missing_reqs:
print(80*'-')
print('*** WARNING *** ' + \
'installed packages not found in requirements.txt:')
print(','.join(missing_reqs))
else:
print(20*' ' + 'CURRENT FOLDER NOT IN ACTIVE VIRTUAL ENVIRONMENT')
if active_ve == current_folder + '\\env':
return # we're in home folder of active environment
# If we're in a different folder, check whether it has a virtual
# environment under it, and display a message if so
if os.path.isfile(current_folder + '\\env\\Scripts\\activate.bat'):
print(20*' ' +\
'current folder has a virtual environment, AE=activate')
def venv_folder(): #---------------------------------------------------------<<<
"""Returns the location of the currently active virtual environment, or
None if no virtual environment is active."""
# sys.prefix is the active virtual environment's home directory
# sys.base_prefix is the location of the global Python interpreter
if sys.prefix == sys.base_prefix:
return None
else:
return sys.prefix
def verify_path(python_scripts_folder): #------------------------------------<<<
"""Verifies that the specified folder is in the current search path.
Displays a confirmation message if true, WARNING message if false.
"""
# note that we ignore case, and we also strip a trailing backslash off
# the search paths (because that is irrelevant, and the existence of the
# trailing slash is inconsistent across Python versions)
windows_search_path = os.environ['PATH']
search_paths = windows_search_path.split(';') # assume Windows ; separator
if not python_scripts_folder.lower().rstrip('\\') in \
[_.lower().rstrip('\\') for _ in search_paths]:
print('*WARNING*: ' +
'Scripts folder is not in current Windows search path!',
file=sys.stderr)
else:
print('confirmed: ' +
'Scripts folder is in current Windows search path')
def write_ae_bat(folder): #--------------------------------------------------<<<
"""Writes the ae.bat "activate environment" batch file."""
print(' writing: ae.bat (Activate Environment)')
timestamp = str(datetime.datetime.now())[:19]
content = '\n'.join( \
['@REM batch file created by venv_setup.py ' + timestamp,
'@call deactivate >nul 2>&1',
'@env\\Scripts\\activate'])
open(os.path.join(folder, 'ae.bat'), 'w').write(content)
def write_de_bat(folder): #--------------------------------------------------<<<
"""Writes the de.bat "deactivate environment" batch file."""
print(' writing: de.bat (Deactivate Environment)')
timestamp = str(datetime.datetime.now())[:19]
content = '\n'.join( \
['@REM batch file created by venv_setup.py ' + timestamp,
'@deactivate'])
open(os.path.join(folder, 'de.bat'), 'w').write(content)
def write_ne_bat(folder): #--------------------------------------------------<<<
"""Writes the ne.bat "new environment" batch file."""
print(' writing: ne.bat (New Environment)')
timestamp = str(datetime.datetime.now())[:19]
content = '\n'.join( \
['@REM batch file created by venv_setup.py ' + timestamp,
'@ECHO ' + ' creating Python virtual environment '.center(54, '-'),
'@ECHO project home folder: %cd%',
'@ECHO virtual environment: %cd%\\env',
'@ECHO copying files ...',
'@python -m venv env',
'@ECHO *** COMPLETED *** ' + 'use AE to activate, DE to de-activate'])
open(os.path.join(folder, 'ne.bat'), 'w').write(content)
def write_se_bat(folder): #--------------------------------------------------<<<
"""Writes the se.bat "show environment" batch file."""
print(' writing: se.bat (Show Environment)')
timestamp = str(datetime.datetime.now())[:19]
# we include the full path to venv_setup.py, to avoid launching a program
# of the same name that happens to be in the current folder at runtime
filename = os.path.realpath(sys.argv[0])
# note the 'status' argument on the venv_setup.py command line, which
# causes execution of show_status() instead of setup_script() at runtime
content = '\n'.join( \
['@REM batch file created by venv_setup.py ' + timestamp,
'@python ' + filename + ' status'])
open(os.path.join(folder, 'se.bat'), 'w').write(content)
if __name__ == '__main__':
if 'status' in [_.lower() for _ in sys.argv]:
show_status()
else:
setup_script()