Skip to content
This repository was archived by the owner on Jul 14, 2023. It is now read-only.

Commit 0ee5bb1

Browse files
committed
dont fail if the version is available
If the python version is shown by `pyenv versions`, use $PYENV_VERSION to enable the version and retry the operation. Fixes #3
1 parent aad7fc1 commit 0ee5bb1

File tree

2 files changed

+113
-23
lines changed

2 files changed

+113
-23
lines changed

circle.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ machine:
1414
dependencies:
1515
override:
1616
- pip -V
17-
- pip install -U 'pip<8.0' 'virtualenv<14.0' ipdb tox .
17+
- pip install -U 'pip<8.0' 'virtualenv<14.0' ipdb tox
18+
- pip install -e .
1819
- pyenv local $TOX_PY35 $TOX_PY34 $TOX_PY33 $TOX_PY32 $TOX_PY27 $TOX_PY26 $TOX_PYPY
1920

2021
test:

tox_pyenv.py

Lines changed: 111 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ def tox_get_python_executable(envconfig):
2828
2929
"""
3030

31+
import logging
32+
import ntpath
33+
import os
34+
import re
35+
import subprocess
36+
37+
from distutils.version import LooseVersion
38+
39+
import py
40+
from tox import hookimpl as tox_hookimpl
41+
3142
# __about__
3243
__title__ = 'tox-pyenv'
3344
__summary__ = ('tox plugin that makes tox use `pyenv which` '
@@ -41,13 +52,9 @@ def tox_get_python_executable(envconfig):
4152
# __about__
4253

4354

44-
import logging
45-
import subprocess
46-
47-
import py
48-
from tox import hookimpl as tox_hookimpl
49-
5055
LOG = logging.getLogger(__name__)
56+
PYTHON_VERSION_RE = re.compile(r'^(?:python|py)([\d\.]{1,5})$',
57+
flags=re.IGNORECASE)
5158

5259

5360
class ToxPyenvException(Exception):
@@ -65,33 +72,114 @@ class PyenvWhichFailed(ToxPyenvException):
6572
"""Calling `pyenv which` failed."""
6673

6774

68-
@tox_hookimpl
69-
def tox_get_python_executable(envconfig):
75+
class NoSuitableVersionFound(ToxPyenvException):
76+
77+
"""Could not a find a python version that satisfies requirement."""
78+
79+
80+
def _get_pyenv_known_versions():
81+
"""Return searchable output from `pyenv versions`."""
82+
known_versions = _pyenv_run(['versions'])[0].split(os.linesep)
83+
return [v.strip() for v in known_versions if v.strip()]
84+
85+
86+
def _pyenv_run(command, **popen_kwargs):
87+
"""Run pyenv command with Popen.
88+
89+
Returns the result tuple as (stdout, stderr, returncode).
90+
"""
7091
try:
71-
pyenv = (getattr(py.path.local.sysfind('pyenv'), 'strpath', 'pyenv')
72-
or 'pyenv')
73-
cmd = [pyenv, 'which', envconfig.basepython]
92+
pyenv = (getattr(
93+
py.path.local.sysfind('pyenv'), 'strpath', 'pyenv') or 'pyenv')
94+
cmd = [pyenv] + command
7495
pipe = subprocess.Popen(
7596
cmd,
7697
stdout=subprocess.PIPE,
7798
stderr=subprocess.PIPE,
78-
universal_newlines=True
99+
universal_newlines=True,
100+
**popen_kwargs
79101
)
80102
out, err = pipe.communicate()
103+
out, err = out.strip(), err.strip()
81104
except OSError:
82105
raise PyenvMissing(
83106
"pyenv doesn't seem to be installed, you probably "
84-
"don't want this plugin installed either.")
85-
if pipe.poll() == 0:
86-
return out.strip()
107+
"don't want this plugin (tox-pyenv) installed either.")
108+
returncode = pipe.poll()
109+
if returncode != 0:
110+
cmdstr = ' '.join([str(x) for x in cmd])
111+
LOG.debug("The command `%s` executed by the tox-pyenv plugin failed. "
112+
"STDERR: \"%s\" STDOUT: \"%s\"", cmdstr, err, out)
113+
raise subprocess.CalledProcessError(returncode, cmdstr, output=err)
114+
return out, err
115+
116+
117+
def _extrapolate_to_known_version(desired, known):
118+
"""Given the desired version, find an acceptable available version."""
119+
match = PYTHON_VERSION_RE.match(desired)
120+
if match:
121+
match = match.groups()[0]
122+
if match in known:
123+
return match
124+
else:
125+
matches = sorted([LooseVersion(j) for j in known
126+
if j.startswith(match)])
127+
if matches:
128+
# Select the latest.
129+
# e.g. python2 gets 2.7.10
130+
# if known_versions = ['2.7.3', '2.7', '2.7.10']
131+
return matches[-1].vstring
132+
raise NoSuitableVersionFound(
133+
'Given desired version {0}, no suitable version of python could '
134+
'be matched in the list given by `pyenv versions`.'.format(desired))
135+
136+
137+
def _set_env_and_retry(envconfig):
138+
# Let's be smart, and resilient to 'command not found'
139+
# especially if we can reasonably figure out which
140+
# version of python is desired, and that version of python
141+
# is installed and available through pyenv.
142+
desired_version = ntpath.basename(envconfig.basepython)
143+
LOG.debug("tox-pyenv is now looking for the desired python "
144+
"version (%s) through pyenv. If it is found, it will "
145+
"be enabled and this operation retried.", desired_version)
146+
147+
def _enable_and_call(_available_version):
148+
LOG.debug('Enabling %s by setting $PYENV_VERSION to %s',
149+
desired_version, _available_version)
150+
_env = os.environ.copy()
151+
_env['PYENV_VERSION'] = _available_version
152+
return _pyenv_run(
153+
['which', envconfig.basepython], env=_env)[0]
154+
155+
known_versions = _get_pyenv_known_versions()
156+
157+
if desired_version in known_versions:
158+
return _enable_and_call(desired_version)
159+
else:
160+
match = _extrapolate_to_known_version(
161+
desired_version, known_versions)
162+
return _enable_and_call(match)
163+
164+
165+
@tox_hookimpl
166+
def tox_get_python_executable(envconfig):
167+
"""Hook into tox plugins to use pyenv to find executables."""
168+
169+
try:
170+
out, err = _pyenv_run(['which', envconfig.basepython])
171+
except subprocess.CalledProcessError:
172+
try:
173+
return _set_env_and_retry(envconfig)
174+
except (subprocess.CalledProcessError, NoSuitableVersionFound):
175+
if not envconfig.tox_pyenv_fallback:
176+
raise PyenvWhichFailed(err)
177+
LOG.debug("tox-pyenv plugin failed, falling back. "
178+
"To disable this behavior, set "
179+
"tox_pyenv_fallback=False in your tox.ini or use "
180+
" --tox-pyenv-no-fallback on the command line.")
87181
else:
88-
if not envconfig.tox_pyenv_fallback:
89-
raise PyenvWhichFailed(err)
90-
LOG.debug("`%s` failed thru tox-pyenv plugin, falling back. "
91-
"STDERR: \"%s\" | To disable this behavior, set "
92-
"tox_pyenv_fallback=False in your tox.ini or use "
93-
" --tox-pyenv-no-fallback on the command line.",
94-
' '.join([str(x) for x in cmd]), err)
182+
return out
95183

96184

97185
def _setup_no_fallback(parser):
@@ -137,4 +225,5 @@ def _pyenv_fallback(testenv_config, value):
137225

138226
@tox_hookimpl
139227
def tox_addoption(parser):
228+
"""Add the --tox-pyenv-no-fallback command line option to tox."""
140229
_setup_no_fallback(parser)

0 commit comments

Comments
 (0)