2016-07-20 16:56:39 -04:00
|
|
|
"""Python virtual environment manager for xonsh."""
|
2021-11-26 23:37:35 +05:30
|
|
|
import os.path
|
|
|
|
import subprocess
|
|
|
|
import tempfile
|
|
|
|
import typing as tp
|
|
|
|
from pathlib import Path
|
2016-07-20 16:56:39 -04:00
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
import xonsh.cli_utils as xcli
|
2016-08-04 19:19:28 -04:00
|
|
|
import xontrib.voxapi as voxapi
|
2021-10-13 19:32:06 +05:30
|
|
|
from xonsh.built_ins import XSH
|
2021-11-26 23:37:35 +05:30
|
|
|
from xonsh.dirstack import pushd_fn
|
|
|
|
from xonsh.platform import ON_WINDOWS
|
2022-03-27 17:12:52 +05:30
|
|
|
from xonsh.tools import XonshError
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-08-04 19:19:28 -04:00
|
|
|
__all__ = ()
|
2016-07-21 14:36:46 -04:00
|
|
|
|
2016-08-04 22:31:45 -04:00
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
def venv_names_completer(command, alias: "VoxHandler", **_):
|
|
|
|
envs = alias.vox.keys()
|
|
|
|
from xonsh.completers.path import complete_dir
|
2016-07-21 00:26:13 -04:00
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
yield from envs
|
2019-04-26 11:11:11 -04:00
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
paths, _ = complete_dir(command)
|
|
|
|
yield from paths
|
2016-07-21 13:54:58 -04:00
|
|
|
|
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
def py_interpreter_path_completer(xsh, **_):
|
|
|
|
for _, (path, is_alias) in xsh.commands_cache.all_commands.items():
|
|
|
|
if not is_alias and ("/python" in path or "/pypy" in path):
|
|
|
|
yield path
|
2016-07-21 13:54:58 -04:00
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
|
2021-11-26 23:37:35 +05:30
|
|
|
_venv_option = xcli.Annotated[
|
|
|
|
tp.Optional[str],
|
|
|
|
xcli.Arg(metavar="ENV", nargs="?", completer=venv_names_completer),
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
class VoxHandler(xcli.ArgParserAlias):
|
|
|
|
"""Vox is a virtual environment manager for xonsh."""
|
|
|
|
|
|
|
|
def build(self):
|
|
|
|
"""lazily called during dispatch"""
|
|
|
|
self.vox = voxapi.Vox()
|
|
|
|
parser = self.create_parser(prog="vox")
|
|
|
|
|
2021-12-22 03:20:18 +05:30
|
|
|
parser.add_command(self.new, aliases=["create"])
|
2021-10-13 19:32:06 +05:30
|
|
|
parser.add_command(self.activate, aliases=["workon", "enter"])
|
|
|
|
parser.add_command(self.deactivate, aliases=["exit"])
|
|
|
|
parser.add_command(self.list, aliases=["ls"])
|
|
|
|
parser.add_command(self.remove, aliases=["rm", "delete", "del"])
|
2021-11-26 23:37:35 +05:30
|
|
|
parser.add_command(self.info)
|
|
|
|
parser.add_command(self.runin)
|
|
|
|
parser.add_command(self.runin_all)
|
|
|
|
parser.add_command(self.toggle_ssp)
|
|
|
|
parser.add_command(self.wipe)
|
|
|
|
parser.add_command(self.project_set)
|
|
|
|
parser.add_command(self.project_get)
|
2021-12-22 03:20:18 +05:30
|
|
|
parser.add_command(self.upgrade)
|
2021-11-26 23:37:35 +05:30
|
|
|
|
2016-07-21 15:18:44 -04:00
|
|
|
return parser
|
2016-07-21 00:26:13 -04:00
|
|
|
|
2021-12-22 03:20:18 +05:30
|
|
|
def hook_pre_add_argument(self, param: str, func, flags, kwargs):
|
|
|
|
if func.__name__ in {"new", "upgrade"}:
|
|
|
|
if ON_WINDOWS and param == "symlinks":
|
|
|
|
# copies by default on windows
|
|
|
|
kwargs["default"] = False
|
|
|
|
kwargs["action"] = "store_true"
|
|
|
|
kwargs["help"] = "Try to use symlinks rather than copies"
|
|
|
|
flags = ["--symlinks"]
|
|
|
|
return flags, kwargs
|
|
|
|
|
|
|
|
def hook_post_add_argument(self, action, param: str, **_):
|
|
|
|
if param == "interpreter":
|
|
|
|
action.completer = py_interpreter_path_completer
|
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
def new(
|
|
|
|
self,
|
|
|
|
name: xcli.Annotated[str, xcli.Arg(metavar="ENV")],
|
2021-12-22 03:20:18 +05:30
|
|
|
interpreter: "str|None" = None,
|
2021-11-18 00:21:18 +05:30
|
|
|
system_site_packages=False,
|
2021-12-22 03:20:18 +05:30
|
|
|
symlinks=True,
|
2021-11-26 23:37:35 +05:30
|
|
|
without_pip=False,
|
2021-11-18 00:21:18 +05:30
|
|
|
activate=False,
|
2021-11-26 23:37:35 +05:30
|
|
|
temporary=False,
|
|
|
|
packages: xcli.Annotated[tp.Sequence[str], xcli.Arg(nargs="*")] = (),
|
|
|
|
requirements: xcli.Annotated[tp.Sequence[str], xcli.Arg(action="append")] = (),
|
|
|
|
link_project_dir=False,
|
2022-02-21 10:21:31 +01:00
|
|
|
prompt: "str|None" = None,
|
2021-10-13 19:32:06 +05:30
|
|
|
):
|
|
|
|
"""Create a virtual environment in $VIRTUALENV_HOME with python3's ``venv``.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name : str
|
|
|
|
Virtual environment name
|
2021-11-26 23:37:35 +05:30
|
|
|
interpreter: -p, --interpreter
|
2021-10-13 19:32:06 +05:30
|
|
|
Python interpreter used to create the virtual environment.
|
|
|
|
Can be configured via the $VOX_DEFAULT_INTERPRETER environment variable.
|
2021-11-18 00:21:18 +05:30
|
|
|
system_site_packages : --system-site-packages, --ssp
|
2021-10-13 19:32:06 +05:30
|
|
|
If True, the system (global) site-packages dir is available to
|
|
|
|
created environments.
|
2021-12-22 03:20:18 +05:30
|
|
|
symlinks : --copies
|
|
|
|
Try to use copies rather than symlinks.
|
2021-11-26 23:37:35 +05:30
|
|
|
without_pip : --without-pip, --wp
|
|
|
|
Skips installing or upgrading pip in the virtual environment
|
2021-11-18 00:21:18 +05:30
|
|
|
activate : -a, --activate
|
2021-10-13 19:32:06 +05:30
|
|
|
Activate the newly created virtual environment.
|
2021-11-26 23:37:35 +05:30
|
|
|
temporary: -t, --temp
|
|
|
|
Create the virtualenv under a temporary directory.
|
|
|
|
packages: -i, --install
|
|
|
|
Install one or more packages (by repeating the option) after the environment is created using pip
|
|
|
|
requirements: -r, --requirements
|
|
|
|
The argument value is passed to ``pip -r`` to be installed.
|
|
|
|
link_project_dir: -l, --link, --link-project
|
|
|
|
Associate the current directory with the new environment.
|
2022-02-21 10:21:31 +01:00
|
|
|
prompt: --prompt
|
|
|
|
Provides an alternative prompt prefix for this environment.
|
2021-10-13 19:32:06 +05:30
|
|
|
"""
|
2021-11-26 23:37:35 +05:30
|
|
|
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out("Creating environment...")
|
2021-11-26 23:37:35 +05:30
|
|
|
|
|
|
|
if temporary:
|
|
|
|
path = tempfile.mkdtemp(prefix=f"vox-env-{name}")
|
|
|
|
name = os.path.join(path, name)
|
|
|
|
|
2018-12-20 20:27:47 -08:00
|
|
|
self.vox.create(
|
2021-10-13 19:32:06 +05:30
|
|
|
name,
|
|
|
|
system_site_packages=system_site_packages,
|
2021-12-22 03:20:18 +05:30
|
|
|
symlinks=symlinks,
|
2021-11-26 23:37:35 +05:30
|
|
|
with_pip=(not without_pip),
|
2021-10-13 19:32:06 +05:30
|
|
|
interpreter=interpreter,
|
2022-02-21 10:21:31 +01:00
|
|
|
prompt=prompt,
|
2018-12-20 20:27:47 -08:00
|
|
|
)
|
2021-11-26 23:37:35 +05:30
|
|
|
if link_project_dir:
|
|
|
|
self.project_set(name)
|
|
|
|
|
|
|
|
if packages:
|
|
|
|
self.runin(name, ["pip", "install", *packages])
|
|
|
|
|
|
|
|
if requirements:
|
|
|
|
|
|
|
|
def _generate_args():
|
|
|
|
for req in requirements:
|
|
|
|
yield "-r"
|
|
|
|
yield req
|
|
|
|
|
|
|
|
self.runin(name, ["pip", "install"] + list(_generate_args()))
|
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
if activate:
|
2021-11-26 23:37:35 +05:30
|
|
|
self.activate(name)
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out(f"Environment {name!r} created and activated.\n")
|
2020-08-28 13:56:24 +02:00
|
|
|
else:
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out(
|
2021-10-13 19:32:06 +05:30
|
|
|
f'Environment {name!r} created. Activate it with "vox activate {name}".\n'
|
2020-08-28 15:55:10 +02:00
|
|
|
)
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
def activate(
|
|
|
|
self,
|
2021-11-26 23:37:35 +05:30
|
|
|
name: _venv_option = None,
|
|
|
|
no_cd=False,
|
2021-10-13 19:32:06 +05:30
|
|
|
):
|
|
|
|
"""Activate a virtual environment.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name
|
|
|
|
The environment to activate.
|
|
|
|
ENV can be either a name from the venvs shown by ``vox list``
|
|
|
|
or the path to an arbitrary venv
|
2021-11-26 23:37:35 +05:30
|
|
|
no_cd: -n, --no-cd
|
|
|
|
Do not change current working directory even if a project path is associated with ENV.
|
2021-10-13 19:32:06 +05:30
|
|
|
"""
|
|
|
|
|
|
|
|
if name is None:
|
|
|
|
return self.list()
|
2016-07-20 15:02:56 -04:00
|
|
|
|
|
|
|
try:
|
2021-10-13 19:32:06 +05:30
|
|
|
self.vox.activate(name)
|
2016-07-20 15:02:56 -04:00
|
|
|
except KeyError:
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error(
|
|
|
|
f'This environment doesn\'t exist. Create it with "vox new {name}"',
|
2019-04-26 11:11:11 -04:00
|
|
|
)
|
2021-10-13 19:32:06 +05:30
|
|
|
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out(f'Activated "{name}".\n')
|
2021-11-26 23:37:35 +05:30
|
|
|
if not no_cd:
|
|
|
|
project_dir = self._get_project_dir(name)
|
|
|
|
if project_dir:
|
|
|
|
pushd_fn(project_dir)
|
|
|
|
|
|
|
|
def deactivate(self, remove=False, force=False):
|
2021-10-13 19:32:06 +05:30
|
|
|
"""Deactivate the active virtual environment.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
2021-11-18 00:21:18 +05:30
|
|
|
remove: -r, --remove
|
2021-10-13 19:32:06 +05:30
|
|
|
Remove the virtual environment after leaving it.
|
2021-11-26 23:37:35 +05:30
|
|
|
force: -f, --force-removal
|
|
|
|
Remove the virtual environment without prompt
|
2021-10-13 19:32:06 +05:30
|
|
|
"""
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
if self.vox.active() is None:
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error(
|
2019-04-26 11:11:11 -04:00
|
|
|
'No environment currently active. Activate one with "vox activate".\n',
|
|
|
|
)
|
2016-07-20 15:02:56 -04:00
|
|
|
env_name = self.vox.deactivate()
|
2021-10-13 19:32:06 +05:30
|
|
|
if remove:
|
2021-11-26 23:37:35 +05:30
|
|
|
self.vox.force_removals = force
|
2020-08-28 13:56:24 +02:00
|
|
|
del self.vox[env_name]
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out(f'Environment "{env_name}" deactivated and removed.\n')
|
2020-08-28 13:56:24 +02:00
|
|
|
else:
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out(f'Environment "{env_name}" deactivated.\n')
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
def list(self):
|
2016-02-11 11:47:16 +03:00
|
|
|
"""List available virtual environments."""
|
|
|
|
|
2016-05-15 21:59:28 +02:00
|
|
|
try:
|
2016-07-21 00:26:13 -04:00
|
|
|
envs = sorted(self.vox.keys())
|
2016-05-15 21:59:28 +02:00
|
|
|
except PermissionError:
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error("No permissions on VIRTUALENV_HOME")
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2016-07-20 15:02:56 -04:00
|
|
|
if not envs:
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error(
|
2019-04-26 11:11:11 -04:00
|
|
|
'No environments available. Create one with "vox new".\n',
|
|
|
|
)
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out("Available environments:")
|
|
|
|
self.out("\n".join(envs))
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2021-10-13 19:32:06 +05:30
|
|
|
def remove(
|
|
|
|
self,
|
|
|
|
names: xcli.Annotated[
|
2022-01-18 20:24:31 +05:30
|
|
|
tp.List[str],
|
2021-10-13 19:32:06 +05:30
|
|
|
xcli.Arg(metavar="ENV", nargs="+", completer=venv_names_completer),
|
|
|
|
],
|
2021-11-26 23:37:35 +05:30
|
|
|
force=False,
|
2021-10-13 19:32:06 +05:30
|
|
|
):
|
|
|
|
"""Remove virtual environments.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
names
|
|
|
|
The environments to remove. ENV can be either a name from the venvs shown by vox
|
|
|
|
list or the path to an arbitrary venv
|
2021-11-26 23:37:35 +05:30
|
|
|
force : -f, --force
|
|
|
|
Delete virtualenv without prompt
|
2021-10-13 19:32:06 +05:30
|
|
|
"""
|
2021-11-26 23:37:35 +05:30
|
|
|
self.vox.force_removals = force
|
2021-10-13 19:32:06 +05:30
|
|
|
for name in names:
|
2016-07-20 15:02:56 -04:00
|
|
|
try:
|
2016-07-21 00:36:35 -04:00
|
|
|
del self.vox[name]
|
2016-08-04 19:19:28 -04:00
|
|
|
except voxapi.EnvironmentInUse:
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error(
|
2021-10-13 19:32:06 +05:30
|
|
|
f'The "{name}" environment is currently active. '
|
|
|
|
'In order to remove it, deactivate it first with "vox deactivate".\n',
|
2019-04-26 11:11:11 -04:00
|
|
|
)
|
2020-08-23 20:38:03 +02:00
|
|
|
except KeyError:
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error(f'"{name}" environment doesn\'t exist.\n')
|
2016-07-20 15:02:56 -04:00
|
|
|
else:
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out(f'Environment "{name}" removed.')
|
|
|
|
self.out()
|
2016-02-11 11:47:16 +03:00
|
|
|
|
2021-11-26 23:37:35 +05:30
|
|
|
def _in_venv(self, env_dir: str, command: str, *args, **kwargs):
|
2022-03-27 17:12:52 +05:30
|
|
|
env = {**XSH.env.detype(), "VIRTUAL_ENV": env_dir}
|
|
|
|
|
|
|
|
bin_path = os.path.join(env_dir, self.vox.sub_dirs[0])
|
|
|
|
env["PATH"] = os.pathsep.join([bin_path, env["PATH"]])
|
|
|
|
|
2021-11-26 23:37:35 +05:30
|
|
|
for key in ("PYTHONHOME", "__PYVENV_LAUNCHER__"):
|
2022-03-27 17:12:52 +05:30
|
|
|
env.pop(key, None)
|
2021-11-26 23:37:35 +05:30
|
|
|
|
|
|
|
try:
|
|
|
|
return subprocess.check_call(
|
2022-01-18 20:24:31 +05:30
|
|
|
[command] + list(args), shell=bool(ON_WINDOWS), env=env, **kwargs
|
2021-11-26 23:37:35 +05:30
|
|
|
)
|
|
|
|
# need to have shell=True on windows, otherwise the PYTHONPATH
|
|
|
|
# won't inherit the PATH
|
|
|
|
except OSError as e:
|
|
|
|
if e.errno == 2:
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error(f"Unable to find {command}")
|
2021-11-26 23:37:35 +05:30
|
|
|
raise
|
|
|
|
|
|
|
|
def runin(
|
|
|
|
self,
|
|
|
|
venv: xcli.Annotated[
|
|
|
|
str,
|
|
|
|
xcli.Arg(completer=venv_names_completer),
|
|
|
|
],
|
2022-01-18 20:24:31 +05:30
|
|
|
args: xcli.Annotated[tp.Sequence[str], xcli.Arg(nargs="...")],
|
2021-11-26 23:37:35 +05:30
|
|
|
):
|
|
|
|
"""Run the command in the given environment
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
venv
|
|
|
|
The environment to run the command for
|
|
|
|
args
|
|
|
|
The actual command to run
|
|
|
|
|
|
|
|
Examples
|
|
|
|
--------
|
|
|
|
vox runin venv1 black --check-only
|
|
|
|
"""
|
|
|
|
env_dir = self._get_env_dir(venv)
|
|
|
|
if not args:
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error("No command is passed")
|
2021-11-26 23:37:35 +05:30
|
|
|
self._in_venv(env_dir, *args)
|
|
|
|
|
|
|
|
def runin_all(
|
|
|
|
self,
|
2022-01-18 20:24:31 +05:30
|
|
|
args: xcli.Annotated[tp.Sequence[str], xcli.Arg(nargs="...")],
|
2021-11-26 23:37:35 +05:30
|
|
|
):
|
|
|
|
"""Run the command in all environments found under $VIRTUALENV_HOME
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
args
|
|
|
|
The actual command to run with arguments
|
|
|
|
"""
|
|
|
|
errors = False
|
|
|
|
for env in self.vox:
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out("\n%s:" % env)
|
2021-11-26 23:37:35 +05:30
|
|
|
try:
|
|
|
|
self.runin(env, *args)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
errors = True
|
2022-03-27 17:12:52 +05:30
|
|
|
self.err(e)
|
|
|
|
self.parser.exit(errors)
|
2021-11-26 23:37:35 +05:30
|
|
|
|
|
|
|
def _sitepackages_dir(self, venv_path: str):
|
|
|
|
env_python = self.vox.get_binary_path("python", venv_path)
|
|
|
|
if not os.path.exists(env_python):
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error("no virtualenv active")
|
2021-11-26 23:37:35 +05:30
|
|
|
|
|
|
|
return Path(
|
|
|
|
subprocess.check_output(
|
|
|
|
[
|
|
|
|
str(env_python),
|
|
|
|
"-c",
|
|
|
|
"import distutils; \
|
|
|
|
print(distutils.sysconfig.get_python_lib())",
|
|
|
|
]
|
|
|
|
).decode()
|
|
|
|
)
|
|
|
|
|
|
|
|
def _get_env_dir(self, venv=None):
|
2022-03-27 17:12:52 +05:30
|
|
|
venv = venv or ...
|
2021-11-26 23:37:35 +05:30
|
|
|
try:
|
|
|
|
env_dir = self.vox[venv].env
|
|
|
|
except KeyError:
|
|
|
|
# check whether the venv is a valid path to an environment
|
2022-03-27 17:12:52 +05:30
|
|
|
if (
|
|
|
|
isinstance(venv, str)
|
|
|
|
and os.path.exists(venv)
|
|
|
|
and os.path.exists(self.vox.get_binary_path("python", venv))
|
2021-11-26 23:37:35 +05:30
|
|
|
):
|
|
|
|
return venv
|
2022-03-27 17:12:52 +05:30
|
|
|
raise XonshError("No virtualenv is found")
|
2021-11-26 23:37:35 +05:30
|
|
|
return env_dir
|
|
|
|
|
|
|
|
def toggle_ssp(self):
|
|
|
|
"""Controls whether the active virtualenv will access the packages
|
|
|
|
in the global Python site-packages directory."""
|
|
|
|
# https://virtualenv.pypa.io/en/legacy/userguide.html#the-system-site-packages-option
|
|
|
|
env_dir = self._get_env_dir() # current
|
|
|
|
site = self._sitepackages_dir(env_dir)
|
|
|
|
ngsp_file = site.parent / "no-global-site-packages.txt"
|
|
|
|
if ngsp_file.exists():
|
|
|
|
ngsp_file.unlink()
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out("Enabled global site-packages")
|
2021-11-26 23:37:35 +05:30
|
|
|
else:
|
|
|
|
with ngsp_file.open("w"):
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out("Disabled global site-packages")
|
2021-11-26 23:37:35 +05:30
|
|
|
|
|
|
|
def project_set(
|
|
|
|
self,
|
|
|
|
venv: _venv_option = None,
|
|
|
|
project_path=None,
|
|
|
|
):
|
|
|
|
"""Bind an existing virtualenv to an existing project.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
venv
|
|
|
|
Name of the virtualenv, while the default being currently active venv.
|
|
|
|
project_path
|
|
|
|
Path to the project, while the default being current directory.
|
|
|
|
"""
|
|
|
|
env_dir = self._get_env_dir(venv) # current
|
|
|
|
|
|
|
|
project = os.path.abspath(project_path or ".")
|
|
|
|
if not os.path.exists(env_dir):
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error(f"Environment '{env_dir}' doesn't exist.")
|
2021-11-26 23:37:35 +05:30
|
|
|
if not os.path.isdir(project):
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error(f"{project} does not exist")
|
2021-11-26 23:37:35 +05:30
|
|
|
|
|
|
|
project_file = self._get_project_file()
|
|
|
|
project_file.write_text(project)
|
|
|
|
|
|
|
|
def _get_project_file(
|
|
|
|
self,
|
|
|
|
venv=None,
|
|
|
|
):
|
|
|
|
env_dir = Path(self._get_env_dir(venv)) # current
|
|
|
|
return env_dir / ".project"
|
|
|
|
|
|
|
|
def _get_project_dir(self, venv=None):
|
|
|
|
project_file = self._get_project_file(venv)
|
|
|
|
if project_file.exists():
|
|
|
|
project_dir = project_file.read_text()
|
|
|
|
if os.path.exists(project_dir):
|
|
|
|
return project_dir
|
|
|
|
|
|
|
|
def project_get(self, venv: _venv_option = None):
|
|
|
|
"""Return a virtualenv's project directory.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
venv
|
|
|
|
Name of the virtualenv under $VIRTUALENV_HOME, while default being currently active venv.
|
|
|
|
"""
|
|
|
|
project_dir = self._get_project_dir(venv)
|
|
|
|
if project_dir:
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out(project_dir)
|
2021-11-26 23:37:35 +05:30
|
|
|
else:
|
|
|
|
project_file = self._get_project_file(venv)
|
2022-03-27 17:12:52 +05:30
|
|
|
raise self.Error(
|
2021-11-26 23:37:35 +05:30
|
|
|
f"Corrupted or outdated: {project_file}\nDirectory: {project_dir} doesn't exist."
|
|
|
|
)
|
|
|
|
|
|
|
|
def wipe(self, venv: _venv_option = None):
|
|
|
|
"""Remove all installed packages from the current (or supplied) env.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
venv
|
2021-12-22 03:20:18 +05:30
|
|
|
name of the venv. Defaults to currently active venv
|
2021-11-26 23:37:35 +05:30
|
|
|
"""
|
|
|
|
env_dir = self._get_env_dir(venv)
|
|
|
|
pip_bin = self.vox.get_binary_path("pip", env_dir)
|
|
|
|
all_pkgs = set(
|
|
|
|
subprocess.check_output([pip_bin, "freeze", "--local"])
|
|
|
|
.decode()
|
|
|
|
.splitlines()
|
|
|
|
)
|
2021-12-07 01:12:26 +05:30
|
|
|
pkgs = {p for p in all_pkgs if len(p.split("==")) == 2}
|
2021-11-26 23:37:35 +05:30
|
|
|
ignored = sorted(all_pkgs - pkgs)
|
2021-12-07 01:12:26 +05:30
|
|
|
to_remove = {p.split("==")[0] for p in pkgs}
|
2021-11-26 23:37:35 +05:30
|
|
|
if to_remove:
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out("Ignoring:\n %s" % "\n ".join(ignored))
|
|
|
|
self.out("Uninstalling packages:\n %s" % "\n ".join(to_remove))
|
2021-11-26 23:37:35 +05:30
|
|
|
return subprocess.run([pip_bin, "uninstall", "-y", *to_remove])
|
|
|
|
else:
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out("Nothing to remove")
|
2021-11-26 23:37:35 +05:30
|
|
|
|
|
|
|
def info(self, venv: _venv_option = None):
|
|
|
|
"""Prints the path for the supplied env
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
venv
|
|
|
|
name of the venv
|
|
|
|
"""
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out(self.vox[venv or ...])
|
2021-11-26 23:37:35 +05:30
|
|
|
|
2021-12-22 03:20:18 +05:30
|
|
|
def upgrade(
|
|
|
|
self,
|
|
|
|
name: _venv_option = None,
|
|
|
|
interpreter: "str|None" = None,
|
|
|
|
symlinks=True,
|
|
|
|
with_pip=False,
|
|
|
|
):
|
|
|
|
"""Upgrade the environment directory to use this version
|
|
|
|
of Python, assuming Python has been upgraded in-place.
|
|
|
|
|
|
|
|
WARNING: If a virtual environment was created with symlinks or without PIP, you must
|
|
|
|
specify these options again on upgrade.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name
|
|
|
|
Name or the path to the virtual environment
|
|
|
|
interpreter: -p, --interpreter
|
|
|
|
Python interpreter used to create the virtual environment.
|
|
|
|
Can be configured via the $VOX_DEFAULT_INTERPRETER environment variable.
|
|
|
|
symlinks : --copies
|
|
|
|
Try to use copies rather than symlinks.
|
|
|
|
with_pip : --without-pip, --wp
|
|
|
|
Skips installing or upgrading pip in the virtual environment
|
|
|
|
"""
|
|
|
|
venv = self.vox.upgrade(
|
|
|
|
name or ..., symlinks=symlinks, with_pip=with_pip, interpreter=interpreter
|
|
|
|
)
|
2022-03-27 17:12:52 +05:30
|
|
|
self.out(venv)
|
2021-12-22 03:20:18 +05:30
|
|
|
|
2016-07-21 00:05:17 +02:00
|
|
|
|
2022-04-17 13:36:08 +05:30
|
|
|
XSH.aliases["vox"] = VoxHandler(threadable=False)
|