2016-07-08 17:06:37 -04:00
|
|
|
"""Python virtual environment manager for xonsh."""
|
2016-05-20 16:07:16 -04:00
|
|
|
import os
|
2016-07-15 17:09:45 -07:00
|
|
|
import sys
|
2016-02-11 11:47:16 +03:00
|
|
|
import venv
|
2016-06-15 20:53:13 -04:00
|
|
|
import shutil
|
2016-07-08 17:06:37 -04:00
|
|
|
import builtins
|
2016-07-20 16:05:44 -04:00
|
|
|
import collections
|
2016-07-20 14:50:22 -04:00
|
|
|
import collections.abc
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-05-22 00:16:20 +02:00
|
|
|
from xonsh.platform import ON_POSIX, ON_WINDOWS, scandir
|
2016-02-13 22:05:38 +03:00
|
|
|
|
2016-07-20 14:50:22 -04:00
|
|
|
VirtualEnvironment = collections.namedtuple('VirtualEnvironment', ['env', 'bin'])
|
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
class EnvironmentInUse(Exception): pass
|
|
|
|
|
|
|
|
class NoEnvironmentActive(Exception): pass
|
|
|
|
|
2016-07-20 14:50:22 -04:00
|
|
|
class Vox(collections.abc.Mapping):
|
2016-07-20 15:20:37 -04:00
|
|
|
"""API access to Vox and virtual environments, in a dict-like format.
|
2016-07-20 15:30:49 -04:00
|
|
|
|
2016-07-20 15:20:37 -04:00
|
|
|
Makes use of the VirtualEnvironment namedtuple:
|
2016-07-20 16:05:44 -04:00
|
|
|
|
2016-07-20 15:20:37 -04:00
|
|
|
1. ``env``: The full path to the environment
|
|
|
|
2. ``bin``: The full path to the bin/Scripts directory of the environment
|
|
|
|
"""
|
2016-07-20 14:50:22 -04:00
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
if not builtins.__xonsh_env__.get('VIRTUALENV_HOME'):
|
|
|
|
home_path = os.path.expanduser('~')
|
|
|
|
self.venvdir = os.path.join(home_path, '.virtualenvs')
|
|
|
|
builtins.__xonsh_env__['VIRTUALENV_HOME'] = self.venvdir
|
|
|
|
else:
|
|
|
|
self.venvdir = builtins.__xonsh_env__['VIRTUALENV_HOME']
|
|
|
|
|
|
|
|
def create(self, name):
|
2016-07-20 15:20:37 -04:00
|
|
|
"""Create a virtual environment in $VIRTUALENV_HOME with python3's ``venv``.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name : str
|
|
|
|
Virtual environment name
|
2016-07-20 14:50:22 -04:00
|
|
|
"""
|
|
|
|
env_path = os.path.join(self.venvdir, name)
|
|
|
|
venv.create(env_path, with_pip=True)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _binname():
|
|
|
|
if ON_WINDOWS:
|
|
|
|
return 'Scripts'
|
|
|
|
elif ON_POSIX:
|
|
|
|
return 'bin'
|
|
|
|
else:
|
|
|
|
raise OSError('This OS is not supported.')
|
|
|
|
|
|
|
|
def __getitem__(self, name):
|
2016-07-20 15:20:37 -04:00
|
|
|
"""Get information about a virtual environment.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name : str or Ellipsis
|
|
|
|
Virtual environment name or absolute path. If ... is given, return
|
|
|
|
the current one (throws a KeyError if there isn't one).
|
|
|
|
"""
|
2016-07-20 14:50:22 -04:00
|
|
|
if name is ...:
|
|
|
|
env_path = builtins.__xonsh_env__['VIRTUAL_ENV']
|
|
|
|
elif os.path.isabs(name):
|
|
|
|
env_path = name
|
|
|
|
else:
|
|
|
|
env_path = os.path.join(self.venvdir, name)
|
|
|
|
bin_dir = self._binname()
|
|
|
|
bin_path = os.path.join(env_path, bin_dir)
|
|
|
|
# Actually check if this is an actual venv or just a organizational directory
|
|
|
|
# eg, if 'spam/eggs' is a venv, reject 'spam'
|
|
|
|
if not os.path.exists(bin_path):
|
|
|
|
raise KeyError()
|
|
|
|
return VirtualEnvironment(env_path, bin_path)
|
|
|
|
|
|
|
|
def __iter__(self):
|
2016-07-20 15:20:37 -04:00
|
|
|
"""List available virtual environments found in $VIRTUALENV_HOME.
|
|
|
|
"""
|
|
|
|
# FIXME: Handle subdirs--this won't discover eg ``spam/eggs``
|
2016-07-20 14:50:22 -04:00
|
|
|
for x in scandir(self.venvdir):
|
|
|
|
if x.is_dir():
|
|
|
|
yield x.name
|
|
|
|
|
|
|
|
def __len__(self):
|
2016-07-20 15:20:37 -04:00
|
|
|
"""Counts known virtual environments, using the same rules as iter().
|
|
|
|
"""
|
2016-07-20 14:50:22 -04:00
|
|
|
l = 0
|
|
|
|
for _ in self:
|
|
|
|
l += 1
|
|
|
|
return l
|
|
|
|
|
|
|
|
def active(self):
|
2016-07-20 15:20:37 -04:00
|
|
|
"""Get the name of the active virtual environment.
|
|
|
|
|
|
|
|
You can use this as a key to get further information.
|
|
|
|
|
|
|
|
Returns None if no environment is active.
|
2016-07-20 14:50:22 -04:00
|
|
|
"""
|
|
|
|
if 'VIRTUAL_ENV' not in builtins.__xonsh_env__:
|
|
|
|
return
|
|
|
|
env_path = builtins.__xonsh_env__['VIRTUAL_ENV']
|
|
|
|
if env_path.startswith(self.venvdir):
|
|
|
|
name = env_path[len(self.venvdir):]
|
|
|
|
if name[0] == '/':
|
|
|
|
name = name[1:]
|
|
|
|
return name
|
|
|
|
else:
|
|
|
|
return env_path
|
|
|
|
|
|
|
|
def activate(self, name):
|
|
|
|
"""
|
|
|
|
Activate a virtual environment.
|
2016-07-20 15:20:37 -04:00
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name : str
|
|
|
|
Virtual environment name or absolute path.
|
2016-07-20 14:50:22 -04:00
|
|
|
"""
|
|
|
|
env = builtins.__xonsh_env__
|
|
|
|
env_path, bin_path = self[name]
|
|
|
|
if 'VIRTUAL_ENV' in env:
|
|
|
|
self.deactivate()
|
|
|
|
|
|
|
|
type(self).oldvars = {'PATH': env['PATH']}
|
|
|
|
env['PATH'].insert(0, bin_path)
|
|
|
|
env['VIRTUAL_ENV'] = env_path
|
|
|
|
if 'PYTHONHOME' in env:
|
|
|
|
type(self).oldvars['PYTHONHOME'] = env.pop('PYTHONHOME')
|
|
|
|
|
|
|
|
def deactivate(self):
|
|
|
|
"""
|
2016-07-20 15:20:37 -04:00
|
|
|
Deactive the active virtual environment. Returns the name of it.
|
2016-07-20 14:50:22 -04:00
|
|
|
"""
|
|
|
|
env = builtins.__xonsh_env__
|
|
|
|
if 'VIRTUAL_ENV' not in env:
|
2016-07-20 15:02:56 -04:00
|
|
|
raise NoEnvironmentActive('No environment currently active.')
|
2016-07-20 14:50:22 -04:00
|
|
|
|
|
|
|
env_path, bin_path = self[...]
|
|
|
|
env_name = self.active()
|
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
if hasattr(type(self), 'oldvars'):
|
|
|
|
for k,v in type(self).oldvars.items():
|
|
|
|
env[k] = v
|
|
|
|
del type(self).oldvars
|
2016-07-20 14:50:22 -04:00
|
|
|
|
|
|
|
env.pop('VIRTUAL_ENV')
|
|
|
|
|
|
|
|
return env_name
|
|
|
|
|
|
|
|
def __delitem__(self, name):
|
|
|
|
"""
|
2016-07-20 15:20:37 -04:00
|
|
|
Permanently deletes a virtual environment.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name : str
|
|
|
|
Virtual environment name or absolute path.
|
2016-07-20 14:50:22 -04:00
|
|
|
"""
|
|
|
|
env_path = self[name].env
|
|
|
|
try:
|
|
|
|
if self[...].env == env_path:
|
2016-07-20 15:02:56 -04:00
|
|
|
raise EnvironmentInUse('The "%s" environment is currently active.' % name)
|
2016-07-20 14:50:22 -04:00
|
|
|
except KeyError:
|
|
|
|
# No current venv, ... fails
|
|
|
|
pass
|
|
|
|
shutil.rmtree(env_path)
|
|
|
|
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-07-20 14:21:52 -04:00
|
|
|
class VoxHandler:
|
2016-02-11 11:47:16 +03:00
|
|
|
"""Vox is a virtual environment manager for xonsh."""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
"""Ensure that $VIRTUALENV_HOME is defined and declare the available vox commands"""
|
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
self.vox = Vox()
|
2016-02-11 11:47:16 +03:00
|
|
|
|
|
|
|
self.commands = {
|
2016-02-13 21:19:56 +03:00
|
|
|
('new',): self.create_env,
|
|
|
|
('activate', 'workon', 'enter'): self.activate_env,
|
|
|
|
('deactivate', 'exit'): self.deactivate_env,
|
|
|
|
('list', 'ls'): self.list_envs,
|
2016-07-15 14:42:54 -07:00
|
|
|
('remove', 'rm', 'delete', 'del'): self.remove_envs,
|
2016-02-13 21:19:56 +03:00
|
|
|
('help', '-h', '--help'): self.show_help
|
2016-02-11 11:47:16 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
def __call__(self, args, stdin=None):
|
|
|
|
"""Call the right handler method for a given command."""
|
|
|
|
|
|
|
|
if not args:
|
2016-02-13 21:19:56 +03:00
|
|
|
self.show_help()
|
2016-02-11 11:47:16 +03:00
|
|
|
return None
|
|
|
|
|
|
|
|
command_name, params = args[0], args[1:]
|
|
|
|
|
|
|
|
try:
|
|
|
|
command = [
|
|
|
|
self.commands[aliases] for aliases in self.commands
|
|
|
|
if command_name in aliases
|
|
|
|
][0]
|
|
|
|
|
|
|
|
command(*params)
|
|
|
|
|
|
|
|
except IndexError:
|
|
|
|
print('Command "%s" doesn\'t exist.\n' % command_name)
|
|
|
|
self.print_commands()
|
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
def create_env(self, name):
|
2016-02-12 16:17:15 +03:00
|
|
|
"""Create a virtual environment in $VIRTUALENV_HOME with python3's ``venv``.
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-02-13 21:17:02 +03:00
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name : str
|
|
|
|
Virtual environment name
|
2016-02-11 11:47:16 +03:00
|
|
|
"""
|
|
|
|
print('Creating environment...')
|
2016-07-20 15:02:56 -04:00
|
|
|
self.vox.create(name)
|
2016-06-15 20:53:13 -04:00
|
|
|
msg = 'Environment {0!r} created. Activate it with "vox activate {0}".\n'
|
|
|
|
print(msg.format(name))
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-02-16 19:35:32 +03:00
|
|
|
def activate_env(self, name):
|
2016-02-11 11:47:16 +03:00
|
|
|
"""Activate a virtual environment.
|
|
|
|
|
2016-02-13 21:17:02 +03:00
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name : str
|
|
|
|
Virtual environment name
|
2016-02-11 11:47:16 +03:00
|
|
|
"""
|
2016-07-20 15:02:56 -04:00
|
|
|
|
|
|
|
try:
|
|
|
|
self.vox.activate(name)
|
|
|
|
except KeyError:
|
2016-02-11 11:47:16 +03:00
|
|
|
print('This environment doesn\'t exist. Create it with "vox new %s".\n' % name)
|
|
|
|
return None
|
|
|
|
else:
|
2016-07-20 15:02:56 -04:00
|
|
|
print('Activated "%s".\n' % name)
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
def deactivate_env(self):
|
2016-02-11 11:47:16 +03:00
|
|
|
"""Deactive the active virtual environment."""
|
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
if self.vox.active() is None:
|
2016-02-11 11:47:16 +03:00
|
|
|
print('No environment currently active. Activate one with "vox activate".\n')
|
|
|
|
return None
|
2016-07-20 15:02:56 -04:00
|
|
|
env_name = self.vox.deactivate()
|
2016-02-11 11:47:16 +03:00
|
|
|
print('Deactivated "%s".\n' % env_name)
|
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
def list_envs(self):
|
2016-02-11 11:47:16 +03:00
|
|
|
"""List available virtual environments."""
|
|
|
|
|
2016-05-15 21:59:28 +02:00
|
|
|
try:
|
2016-07-20 15:02:56 -04:00
|
|
|
envs = list(self.vox.keys())
|
2016-05-15 21:59:28 +02:00
|
|
|
except PermissionError:
|
2016-07-20 15:02:56 -04:00
|
|
|
print('No permissions on VIRTUALENV_HOME'.format(venv_home))
|
2016-05-15 21:59:28 +02:00
|
|
|
return None
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
if not envs:
|
2016-05-15 21:59:28 +02:00
|
|
|
print('No environments available. Create one with "vox new".\n')
|
2016-02-11 11:47:16 +03:00
|
|
|
return None
|
|
|
|
|
|
|
|
print('Available environments:')
|
2016-07-20 15:02:56 -04:00
|
|
|
print('\n'.join(envs))
|
2016-02-11 11:47:16 +03:00
|
|
|
|
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
def remove_envs(self, *names):
|
2016-07-15 14:42:54 -07:00
|
|
|
"""Remove virtual environments.
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-02-13 21:17:02 +03:00
|
|
|
Parameters
|
|
|
|
----------
|
2016-07-15 14:42:54 -07:00
|
|
|
names : list
|
|
|
|
list of virtual environment names
|
2016-02-11 11:47:16 +03:00
|
|
|
"""
|
2016-07-15 14:42:54 -07:00
|
|
|
for name in names:
|
2016-07-20 15:02:56 -04:00
|
|
|
try:
|
|
|
|
del self.vox[name]
|
|
|
|
except EnvironmentInUse:
|
2016-07-15 17:09:45 -07:00
|
|
|
print('The "%s" environment is currently active. In order to remove it, deactivate it first with "vox deactivate %s".\n' % (name, name),
|
|
|
|
file=sys.stderr)
|
2016-07-15 14:42:54 -07:00
|
|
|
return
|
2016-07-20 15:02:56 -04:00
|
|
|
else:
|
|
|
|
print('Environment "%s" removed.' % name)
|
2016-07-15 14:42:54 -07:00
|
|
|
print()
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-02-13 21:19:56 +03:00
|
|
|
def show_help(self):
|
2016-02-11 11:47:16 +03:00
|
|
|
"""Show help."""
|
|
|
|
|
|
|
|
print(self.__doc__, '\n')
|
|
|
|
self.print_commands()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def print_commands():
|
|
|
|
"""Print available vox commands."""
|
|
|
|
|
|
|
|
print("""Available commands:
|
|
|
|
vox new <env>
|
|
|
|
Create new virtual environment in $VIRTUALENV_HOME
|
|
|
|
|
|
|
|
vox activate (workon, enter) <env>
|
|
|
|
Activate virtual environment
|
|
|
|
|
|
|
|
vox deactivate (exit)
|
|
|
|
Deactivate current virtual environment
|
|
|
|
|
|
|
|
vox list (ls)
|
|
|
|
List all available environments
|
|
|
|
|
2016-07-15 14:42:54 -07:00
|
|
|
vox remove (rm, delete, del) <env> <env2> ...
|
|
|
|
Remove virtual environments
|
2016-02-11 11:47:16 +03:00
|
|
|
|
|
|
|
vox help (-h, --help)
|
|
|
|
Show help
|
|
|
|
""")
|
2016-07-20 15:45:14 -04:00
|
|
|
|
|
|
|
|
|
|
|
def _vox(args, stdin=None):
|
|
|
|
"""Runs Vox environment manager."""
|
|
|
|
vox = VoxHandler()
|
|
|
|
return vox(args, stdin=stdin)
|
|
|
|
|
|
|
|
aliases['vox'] = _vox
|