Skip to content

Commit

Permalink
Merge pull request #28 from gocept/preferences-update-lockfile
Browse files Browse the repository at this point in the history
Update lockfile with preferences
  • Loading branch information
ctheune authored Jul 16, 2021
2 parents ee7f8d6 + bcfe1e4 commit 390110a
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 2 deletions.
55 changes: 55 additions & 0 deletions src/appenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,57 @@ def ensure_venv(target):
target=target))


def ensure_minimal_python():
current_python = os.path.realpath(sys.executable)
preferences = None
if os.path.exists('requirements.txt'):
with open('requirements.txt') as f:
for line in f:
# Expected format:
# # appenv-python-preference: 3.1,3.9,3.4
if not line.startswith("# appenv-python-preference: "):
continue
preferences = line.split(':')[1]
preferences = [x.strip() for x in preferences.split(',')]
preferences = list(filter(None, preferences))
break
if not preferences:
# We have no preferences defined, use the current python.
print("Update lockfile with with {}.".format(current_python))
print("If you want to use a different version, set it as via")
print(" `# appenv-python-preference:` in requirements.txt.")
return

preferences.sort(key=lambda s: [int(u) for u in s.split('.')])

for version in preferences[0:1]:
python = shutil.which("python{}".format(version))
if not python:
# not a usable python
continue
python = os.path.realpath(python)
if python == current_python:
# found a preferred python and we're already running as it
break
# Try whether this Python works
try:
subprocess.check_call([python, "-c", "print(1)"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError:
continue

argv = [os.path.basename(python)] + sys.argv
os.environ["APPENV_BEST_PYTHON"] = python
os.execv(python, argv)
else:
print("Could not find the minimal preferred Python version.")
print("To ensure a working requirements.lock on all Python versions")
print("make Python {} available on this system.".format(
preferences[0]))
sys.exit(66)


def ensure_best_python():
if "APPENV_BEST_PYTHON" in os.environ:
# Don't do this twice to avoid being surprised with
Expand Down Expand Up @@ -354,6 +405,7 @@ def reset(self, args=None, remaining=None):
cmd("rm -rf {appenvdir}".format(appenvdir=self.appenv_dir))

def update_lockfile(self, args=None, remaining=None):
ensure_minimal_python()
os.chdir(self.base)
print("Updating lockfile")
tmpdir = os.path.join(self.appenv_dir, "updatelock")
Expand Down Expand Up @@ -393,6 +445,9 @@ def update_lockfile(self, args=None, remaining=None):
if line.strip().startswith('-e '):
extra_specs.append(line.strip())
continue
# filter comments, in particular # appenv-python-preferences
if line.strip().startswith('#'):
continue
spec = list(pkg_resources.parse_requirements(line))[0]
requested_versions[spec.project_name] = spec

Expand Down
69 changes: 67 additions & 2 deletions tests/test_update_lockfile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import appenv
import io
import unittest.mock
import os

import appenv
import pytest
import shutil
import sys


def test_init_and_create_lockfile(workdir, monkeypatch):
Expand All @@ -21,3 +24,65 @@ def test_init_and_create_lockfile(workdir, monkeypatch):
assert (lockfile_content == """\
ducker==2.0.1
""")


@pytest.mark.skipif(
sys.version_info[0:2] != (3, 6), reason='Isolated CI builds')
def test_update_lockfile_minimal_python(workdir, monkeypatch):
"""It uses the minimal python version even if it is not best python."""
monkeypatch.setattr('sys.stdin',
io.StringIO('pytest\npytest==6.1.2\nppytest\n'))

env = appenv.AppEnv(os.path.join(workdir, 'ppytest'))
env.init()

lockfile = os.path.join(workdir, "ppytest", "requirements.lock")
requirements_file = os.path.join(workdir, "ppytest", "requirements.txt")

with open(requirements_file, "r+") as f:
lines = f.readlines()
lines.insert(0, "# appenv-python-preference: 3.8,3.6,3.9\n")
f.seek(0)
f.writelines(lines)

env.update_lockfile()

assert os.path.exists(lockfile)
with open(lockfile) as f:
lockfile_content = f.read()
assert "pytest==6.1.2" in lockfile_content
assert "importlib-metadata==" in lockfile_content
assert "typing-extensions==" in lockfile_content


@pytest.mark.skipif(
sys.version_info[0:2] < (3, 8), reason='Isolated CI builds')
def test_update_lockfile_missing_minimal_python(workdir, monkeypatch):
"""It raises an error if the minimal python is not available."""
monkeypatch.setattr('sys.stdin',
io.StringIO('pytest\npytest==6.1.2\nppytest\n'))

env = appenv.AppEnv(os.path.join(workdir, 'ppytest'))
env.init()

requirements_file = os.path.join(workdir, "ppytest", "requirements.txt")

with open(requirements_file, "r+") as f:
lines = f.readlines()
lines[0] = "# appenv-python-preference: 3.8,3.6,3.9\n"
f.seek(0)
f.writelines(lines)

old_which = shutil.which

def new_which(string):
if string == "python3.6":
return None
else:
return old_which(string)

with unittest.mock.patch('shutil.which') as which:
which.side_effect = new_which
with pytest.raises(SystemExit) as e:
env.update_lockfile()
assert e.value.code == 66

0 comments on commit 390110a

Please sign in to comment.