Address reviewer comments

This commit is contained in:
zbentley
2020-05-12 18:01:49 -04:00
parent b40ebc8644
commit bbdb3236d0
2 changed files with 75 additions and 56 deletions
+14 -49
View File
@@ -351,42 +351,6 @@ def find_a_system_python(line):
return python_entry
def find_python_installer(name, env_var):
"""
Given a python installer (pyenv or asdf), try to locate the binary for that
installer.
pyenv/asdf are not always present on PATH. Both installers also support a
custom environment variable (PYENV_ROOT or ASDF_DIR) which alows them to
be installed into a non-default location (the default/suggested source
install location is in ~/.pyenv or ~/.asdf).
For systems without the installers on PATH, and with a custom location
(e.g. /opt/pyenv), Pipenv can use those installers without modifications to
PATH, and only with their respective environment variables in a .env file.
This is desirable, since setting PATH in an .env file is annoying due to
the common need to reference the pre-existing PATH variable, which is not
supported in .env.
This function searches for installer binaries using PATH, their respective
environment variables, and their respective default install locations. This
allows Pipenv to use those installers regardless of shell configuration, so
long as PYENV_ROOT or ASDF_DIR is specified in an .env file.
"""
for candidate in (
# Look for the Python installer using the equivalent of 'which'. On
# Homebrew-installed systems, the env var may not be set, but this
# strategy will work.
find_windows_executable('', name),
# Check for explicitly set install locations (e.g. PYENV_ROOT, ASDF_DIR).
os.path.join(os.path.expanduser(os.getenv(env_var, '/dev/null')), 'bin', name),
# Check the pyenv/asdf-recommended from-source install locations
os.path.join(os.path.expanduser('~/.{}'.format(name)), 'bin', name),
):
if candidate is not None and os.path.isfile(candidate) and os.access(candidate, os.X_OK):
return candidate
def ensure_python(three=None, python=None):
# Runtime import is necessary due to the possibility that the environments module may have been reloaded.
from .environments import PIPENV_PYTHON, PIPENV_YES
@@ -394,9 +358,10 @@ def ensure_python(three=None, python=None):
if PIPENV_PYTHON and python is False and three is None:
python = PIPENV_PYTHON
def abort():
def abort(msg=''):
click.echo(
"You can specify specific versions of Python with:\n {0}".format(
"{0}\nYou can specify specific versions of Python with:\n{1}".format(
crayons.red(msg),
crayons.red(
"$ pipenv --python {0}".format(
os.sep.join(("path", "to", "python"))
@@ -431,23 +396,25 @@ def ensure_python(three=None, python=None):
err=True,
)
# check for python installers
from .installers import Pyenv, Asdf, InstallerError
from .installers import Pyenv, Asdf, InstallerError, InstallerNotFound
# prefer pyenv if both pyenv and asdf are installed as it's
# dedicated to python installs so probably the preferred
# method of the user for new python installs.
installer = None
if not PIPENV_DONT_USE_PYENV:
pyenv_path = find_python_installer('pyenv', 'PYENV_ROOT')
if pyenv_path is not None:
installer = Pyenv(pyenv_path)
try:
installer = Pyenv()
except InstallerNotFound:
pass
if installer is None and not PIPENV_DONT_USE_ASDF:
asdf_path = find_python_installer('asdf', 'ASDF_DIR')
if asdf_path is not None:
installer = Asdf(asdf_path)
try:
installer = Pyenv()
except InstallerNotFound:
pass
if not installer:
abort()
abort("Neither 'pyenv' nor 'asdf' could be found to install Python.")
else:
if SESSION_IS_INTERACTIVE or PIPENV_YES:
try:
@@ -455,9 +422,7 @@ def ensure_python(three=None, python=None):
except ValueError:
abort()
except InstallerError as e:
click.echo(fix_utf8("Something went wrong"))
click.echo(crayons.blue(e.err), err=True)
abort()
abort('Something went wrong while installing Python:\n{}'.format(e.err))
s = "{0} {1} {2}".format(
"Would you like us to install",
crayons.green("CPython {0}".format(version)),
+61 -7
View File
@@ -1,5 +1,7 @@
import operator
import re
from abc import ABCMeta, abstractmethod
from .environments import PIPENV_INSTALL_TIMEOUT
from .vendor import attr, delegator
@@ -48,6 +50,10 @@ class Version(object):
return (self.major, self.minor) == (other.major, other.minor)
class InstallerNotFound(RuntimeError):
pass
class InstallerError(RuntimeError):
def __init__(self, desc, c):
super(InstallerError, self).__init__(desc)
@@ -56,12 +62,52 @@ class InstallerError(RuntimeError):
class Installer(object):
__metaclass__ = ABCMeta
def __init__(self, cmd):
self._cmd = cmd
def __init__(self):
self._cmd = self._find_installer()
super(Installer, self).__init__()
def __str__(self):
return self._cmd
@abstractmethod
def _find_installer(self):
pass
@staticmethod
def _find_python_installer_by_name_and_env(name, env_var):
"""
Given a python installer (pyenv or asdf), try to locate the binary for that
installer.
pyenv/asdf are not always present on PATH. Both installers also support a
custom environment variable (PYENV_ROOT or ASDF_DIR) which alows them to
be installed into a non-default location (the default/suggested source
install location is in ~/.pyenv or ~/.asdf).
For systems without the installers on PATH, and with a custom location
(e.g. /opt/pyenv), Pipenv can use those installers without modifications to
PATH, if an installer's respective environment variable is present in an
environment's .env file.
This function searches for installer binaries in the following locations,
by precedence:
1. On PATH, equivalent to which(1).
2. In the "bin" subdirectory of PYENV_ROOT or ASDF_DIR, depending on the
installer.
3. In ~/.pyenv/bin or ~/.asdf/bin, depending on the installer.
"""
for candidate in (
# Look for the Python installer using the equivalent of 'which'. On
# Homebrew-installed systems, the env var may not be set, but this
# strategy will work.
find_windows_executable('', name),
# Check for explicitly set install locations (e.g. PYENV_ROOT, ASDF_DIR).
os.path.join(os.path.expanduser(os.getenv(env_var, '/dev/null')), 'bin', name),
# Check the pyenv/asdf-recommended from-source install locations
os.path.join(os.path.expanduser('~/.{}'.format(name)), 'bin', name),
):
if candidate is not None and os.path.isfile(candidate) and os.access(candidate, os.X_OK):
return candidate
raise InstallerNotFound()
def _run(self, *args, **kwargs):
timeout = kwargs.pop('timeout', delegator.TIMEOUT)
@@ -72,13 +118,14 @@ class Installer(object):
c = delegator.run(args, block=False, timeout=timeout)
c.block()
if c.return_code != 0:
raise InstallerError('faild to run {0}'.format(args), c)
raise InstallerError('failed to run {0}'.format(args), c)
return c
@abstractmethod
def iter_installable_versions(self):
"""Iterate through CPython versions available for Pipenv to install.
"""
raise NotImplementedError
pass
def find_version_to_install(self, name):
"""Find a version in the installer from the version supplied.
@@ -100,6 +147,7 @@ class Installer(object):
)
return best_match
@abstractmethod
def install(self, version):
"""Install the given version with runner implementation.
@@ -109,11 +157,14 @@ class Installer(object):
A ValueError is raised if the given version does not have a match in
the runner. A InstallerError is raised if the runner command fails.
"""
raise NotImplementedError
pass
class Pyenv(Installer):
def _find_installer(self):
return self._find_python_installer_by_name_and_env('pyenv', 'PYENV_ROOT')
def iter_installable_versions(self):
"""Iterate through CPython versions available for Pipenv to install.
"""
@@ -140,6 +191,9 @@ class Pyenv(Installer):
class Asdf(Installer):
def _find_installer(self):
return self._find_python_installer_by_name_and_env('asdf', 'ASDF_DIR')
def iter_installable_versions(self):
"""Iterate through CPython versions available for asdf to install.
"""