Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

asdf-support #3888

Closed
wants to merge 13 commits into from
139 changes: 71 additions & 68 deletions pipenv/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
from .cmdparse import Script
from .environments import (
PIPENV_CACHE_DIR, PIPENV_COLORBLIND, PIPENV_DEFAULT_PYTHON_VERSION,
PIPENV_DONT_USE_PYENV, PIPENV_HIDE_EMOJIS, PIPENV_MAX_SUBPROCESS,
PIPENV_PYUP_API_KEY, PIPENV_SHELL_FANCY, PIPENV_SKIP_VALIDATION,
PIPENV_YES, SESSION_IS_INTERACTIVE, PIP_EXISTS_ACTION, PIPENV_RESOLVE_VCS,
is_type_checking
PIPENV_DONT_USE_PYENV, PIPENV_DONT_USE_ASDF, PIPENV_HIDE_EMOJIS,
PIPENV_MAX_SUBPROCESS, PIPENV_PYUP_API_KEY, PIPENV_SHELL_FANCY,
PIPENV_SKIP_VALIDATION, PIPENV_YES, SESSION_IS_INTERACTIVE, PIP_EXISTS_ACTION,
PIPENV_RESOLVE_VCS, is_type_checking
)
from .project import Project, SourceNotFound
from .utils import (
Expand Down Expand Up @@ -393,76 +393,79 @@ def abort():
),
err=True,
)
# Pyenv is installed
from .vendor.pythonfinder.environment import PYENV_INSTALLED
# Pyenv/Asdf is installed
from .vendor.pythonfinder.environment import PYENV_INSTALLED, ASDF_INSTALLED
from .runner import RunnerError, Pyenv, Asdf

if PYENV_INSTALLED and (not PIPENV_DONT_USE_PYENV) and (SESSION_IS_INTERACTIVE or PIPENV_YES):
name = "pyenv"
runner = Pyenv(name)
elif ASDF_INSTALLED and (not PIPENV_DONT_USE_ASDF) and (SESSION_IS_INTERACTIVE or PIPENV_YES):
name = "asdf"
runner = Asdf(name)
else:
abort()

if not PYENV_INSTALLED:
try:
version = runner.find_version_to_install(python)
except ValueError:
abort()
except RunnerError as e:
click.echo(fix_utf8("Something went wrong…"))
click.echo(crayons.blue(e.err), err=True)
abort()
s = "{0} {1} {2}".format(
"Would you like us to install",
crayons.green("CPython {0}".format(version)),
"with {0}?".format(name),
)
# Prompt the user to continue…
if not (PIPENV_YES or click.confirm(s, default=True)):
abort()
else:
if (not PIPENV_DONT_USE_PYENV) and (SESSION_IS_INTERACTIVE or PIPENV_YES):
from .pyenv import Runner, PyenvError

pyenv = Runner("pyenv")
# Tell the user we're installing Python.
click.echo(
u"{0} {1} {2} {3}{4}".format(
crayons.normal(u"Installing", bold=True),
crayons.green(u"CPython {0}".format(version), bold=True),
crayons.normal(u"with {0}".format(name), bold=True),
crayons.normal(u"(this may take a few minutes)"),
crayons.normal(fix_utf8("…"), bold=True),
)
)
with create_spinner("Installing python...") as sp:
try:
version = pyenv.find_version_to_install(python)
except ValueError:
abort()
except PyenvError as e:
click.echo(fix_utf8("Something went wrong…"))
c = runner.install(version)
except RunnerError as e:
sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format(
"Failed...")
)
click.echo(fix_utf8("Something went wrong…"), err=True)
click.echo(crayons.blue(e.err), err=True)
abort()
s = "{0} {1} {2}".format(
"Would you like us to install",
crayons.green("CPython {0}".format(version)),
"with pyenv?",
)
# Prompt the user to continue…
if not (PIPENV_YES or click.confirm(s, default=True)):
abort()
else:
# Tell the user we're installing Python.
click.echo(
u"{0} {1} {2} {3}{4}".format(
crayons.normal(u"Installing", bold=True),
crayons.green(u"CPython {0}".format(version), bold=True),
crayons.normal(u"with pyenv", bold=True),
crayons.normal(u"(this may take a few minutes)"),
crayons.normal(fix_utf8("…"), bold=True),
)
)
with create_spinner("Installing python...") as sp:
try:
c = pyenv.install(version)
except PyenvError as e:
sp.fail(environments.PIPENV_SPINNER_FAIL_TEXT.format(
"Failed...")
)
click.echo(fix_utf8("Something went wrong…"), err=True)
click.echo(crayons.blue(e.err), err=True)
else:
sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!"))
# Print the results, in a beautiful blue…
click.echo(crayons.blue(c.out), err=True)
# Clear the pythonfinder caches
from .vendor.pythonfinder import Finder
finder = Finder(system=False, global_search=True)
finder.find_python_version.cache_clear()
finder.find_all_python_versions.cache_clear()
# Find the newly installed Python, hopefully.
version = str(version)
path_to_python = find_a_system_python(version)
try:
assert python_version(path_to_python) == version
except AssertionError:
click.echo(
"{0}: The Python you just installed is not available on your {1}, apparently."
"".format(
crayons.red("Warning", bold=True),
crayons.normal("PATH", bold=True),
),
err=True,
)
sys.exit(1)
sp.ok(environments.PIPENV_SPINNER_OK_TEXT.format("Success!"))
# Print the results, in a beautiful blue…
click.echo(crayons.blue(c.out), err=True)
# Clear the pythonfinder caches
from .vendor.pythonfinder import Finder
finder = Finder(system=False, global_search=True)
finder.find_python_version.cache_clear()
finder.find_all_python_versions.cache_clear()
# Find the newly installed Python, hopefully.
version = str(version)
path_to_python = find_a_system_python(version)
try:
assert python_version(path_to_python) == version
except AssertionError:
click.echo(
"{0}: The Python you just installed is not available on your {1}, apparently."
"".format(
crayons.red("Warning", bold=True),
crayons.normal("PATH", bold=True),
),
err=True,
)
sys.exit(1)
return path_to_python


Expand Down
6 changes: 6 additions & 0 deletions pipenv/environments.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ def _is_env_truthy(name):
Default is to install Python automatically via pyenv when needed, if possible.
"""

PIPENV_DONT_USE_ASDF = bool(os.environ.get("PIPENV_DONT_USE_ASDF"))
"""If set, Pipenv does not attempt to install Python with asdf.

Default is to install Python automatically via asdf when needed, if possible.
"""

PIPENV_DOTENV_LOCATION = os.environ.get("PIPENV_DOTENV_LOCATION")
"""If set, Pipenv loads the ``.env`` file at the specified location.

Expand Down
87 changes: 67 additions & 20 deletions pipenv/pyenv.py → pipenv/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,12 @@ def matches_minor(self, other):
return (self.major, self.minor) == (other.major, other.minor)


class PyenvError(RuntimeError):
def __init__(self, desc, c):
super(PyenvError, self).__init__(desc)
self.out = c.out
self.err = c.err


class Runner(object):

def __init__(self, pyenv):
self._cmd = pyenv
def __init__(self, cmd):
self._cmd = cmd

def _pyenv(self, *args, **kwargs):
def _run(self, *args, **kwargs):
timeout = kwargs.pop('timeout', delegator.TIMEOUT)
if kwargs:
k = list(kwargs.keys())[0]
Expand All @@ -69,21 +62,16 @@ def _pyenv(self, *args, **kwargs):
c = delegator.run(args, block=False, timeout=timeout)
c.block()
if c.return_code != 0:
raise PyenvError('faild to run {0}'.format(args), c)
raise RunnerError('faild to run {0}'.format(args), c)
return c

def iter_installable_versions(self):
"""Iterate through CPython versions available for Pipenv to install.
"""
for name in self._pyenv('install', '--list').out.splitlines():
try:
version = Version.parse(name.strip())
except ValueError:
continue
yield version
raise NotImplementedError

def find_version_to_install(self, name):
"""Find a version in pyenv from the version supplied.
"""Find a version in runner from the version supplied.

A ValueError is raised if a matching version cannot be found.
"""
Expand All @@ -102,17 +90,76 @@ def find_version_to_install(self, name):
)
return best_match

def install(self, version):
"""Install the given version with asdf.

The version must be a ``Version`` instance representing a version
found in asdf.

A ValueError is raised if the given version does not have a match in
asdf. A RunnerError is raised if the asdf command fails.
"""
raise NotImplementedError


class RunnerError(RuntimeError):
def __init__(self, desc, c):
super(RunnerError, self).__init__(desc)
self.out = c.out
self.err = c.err


class Pyenv(Runner):

def iter_installable_versions(self):
"""Iterate through CPython versions available for Pipenv to install.
"""
for name in self._run('install', '--list').out.splitlines():
try:
version = Version.parse(name.strip())
except ValueError:
continue
yield version

def install(self, version):
"""Install the given version with pyenv.

The version must be a ``Version`` instance representing a version
found in pyenv.

A ValueError is raised if the given version does not have a match in
pyenv. A PyenvError is raised if the pyenv command fails.
pyenv. A RunnerError is raised if the pyenv command fails.
"""
c = self._pyenv(
c = self._run(
'install', '-s', str(version),
timeout=PIPENV_INSTALL_TIMEOUT,
)
return c


class Asdf(Runner):

def iter_installable_versions(self):
"""Iterate through CPython versions available for asdf to install.
"""
for name in self._run('list-all', 'python').out.splitlines():
try:
version = Version.parse(name.strip())
except ValueError:
continue
yield version

def install(self, version):
"""Install the given version with asdf.

The version must be a ``Version`` instance representing a version
found in asdf.

A ValueError is raised if the given version does not have a match in
asdf. A RunnerError is raised if the asdf command fails.
"""
c = self._run(
'install', 'python', str(version),
timeout=PIPENV_INSTALL_TIMEOUT,
)
return c