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

check / install python version using pyenv #1

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Requires recent pip
sudo pip3 install -U pip
```

Install globally so its always avalible
Install globally so its always available

```
sudo pip3 install git+https://github.com/timeoutdigital/timeout-tools
Expand Down
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ argparse
invoke
pip-tools
pre-commit
pytest
18 changes: 15 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile --resolver=backtracking
# pip-compile requirements.in
#
argparse==1.4.0
# via -r requirements.in
attrs==22.2.0
# via pytest
build==0.10.0
# via pip-tools
cfgv==3.3.1
Expand All @@ -14,30 +16,40 @@ click==8.1.3
# via pip-tools
distlib==0.3.6
# via virtualenv
exceptiongroup==1.1.1
# via pytest
filelock==3.10.0
# via virtualenv
identify==2.5.21
# via pre-commit
iniconfig==2.0.0
# via pytest
invoke==2.0.0
# via -r requirements.in
nodeenv==1.7.0
# via pre-commit
packaging==23.0
# via build
# via
# build
# pytest
pip-tools==6.12.3
# via -r requirements.in
platformdirs==3.1.1
# via virtualenv
pluggy==1.0.0
# via pytest
pre-commit==3.2.0
# via -r requirements.in
pyproject-hooks==1.0.0
# via build
pytest==7.2.2
# via -r requirements.in
pyyaml==6.0
# via pre-commit
tomli==2.0.1
# via
# build
# pyproject-hooks
# pytest
virtualenv==20.21.0
# via pre-commit
wheel==0.40.0
Expand Down
85 changes: 84 additions & 1 deletion timeout_tools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
import sys


class PyEnvFailure(Exception):
pass


class PyEnvPythonNotInstalled(Exception):
pass


def main():
parser = argparse.ArgumentParser(
description='Timeout Tools',
Expand Down Expand Up @@ -158,9 +166,24 @@ def python_setup_func(args):


def python_setup(app, branch, python_version):
try:
check_python_version_installed(python_version)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel the logic shouldn't be inside the check function, ie if the check returns false here then call the install here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

certainly makes the tests easier

except PyEnvFailure as e:
print(e.args[0]['message'])
sys.exit(1)
except PyEnvPythonNotInstalled:
try:
check_python_version_available(python_version)
try:
install_python_version(python_version)
except PyEnvFailure as e:
print(e.args[0]['message'])
sys.exit(1)
except PyEnvFailure as e:
print(e.args[0]['message'])
sys.exit(1)
pyenv_name = f'{app}-{python_version}'
print(f'- Creating virtualenv `{pyenv_name}`', end='', flush=True)
run(f'pyenv install -s {python_version}')
ret, out = run(f'pyenv virtualenv {python_version} {pyenv_name}')
if ret != 0:
if 'already exists' in out:
Expand Down Expand Up @@ -274,5 +297,65 @@ def load_python_version(ws=None):
return False


def check_python_version_installed(python_version):
###
# Check whether a version of python is already installed via pyenv
###
py_ver_present = False
print(f'- Checking Python `{python_version}` is installed', end='', flush=True)
(status, result) = run('pyenv versions --bare --skip-aliases')
if status:
print(' ❌')
raise PyEnvFailure({"message": "Failed to run"})
for py_ver in result.replace(' ', '').split('\n'):
if py_ver == python_version:
py_ver_present = True
break

if not py_ver_present:
print(' ❌')
raise PyEnvPythonNotInstalled({"message": "python version not installed"})
else:
print(' ✅')


def check_python_version_available(python_version):
###
# Check whether a version of python is already is available for install by pyenv
# if not exit and tell user to update pyenv installation
###
py_ver_available = False
print(f'- Python checking `{python_version}` is available for installation', end='', flush=True)
(status, result) = run('pyenv install --list')
if status:
print(' ❌')
raise PyEnvFailure({"message": "Failed to run"})
for py_ver in result.replace(' ', '').split('\n'):
if py_ver == python_version:
py_ver_available = True
break
if not py_ver_available:
print(' ❌')
raise PyEnvFailure({"message": '''
Please update pyenv with latest versions of python by running:
cd ~/.pyenv/plugins/python-build/../.. && git pull && cd -
'''})
else:
print(' ✅')


def install_python_version(python_version):
###
# Use pyenv to install new version of python
###
print('- Python installing `{python_version}`', end='', flush=True)
(status, _) = run(f'pyenv install {python_version}')
if status:
print(' ❌')
raise PyEnvFailure(f'Failed to install python version {python_version}')
else:
print(' ✅')


if __name__ == '__main__':
main()
Empty file added timeout_tools/tests/__init__.py
Empty file.
138 changes: 138 additions & 0 deletions timeout_tools/tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from unittest import mock

import pytest

import timeout_tools
from timeout_tools.cli import (PyEnvFailure, PyEnvPythonNotInstalled,
check_python_version_available,
check_python_version_installed,
install_python_version)


def test_check_python_version_installed_success():
mock_run = mock.Mock()
mock_run.return_value = (
0,
' 3.7.9\n 3.10.10\n 3.10.11'
)

timeout_tools.cli.run = mock_run

python_version = '3.10.10'
try:
check_python_version_installed(python_version)
except Exception:
pytest.fail(f'Error checking version installed {python_version}')

mock_run.assert_called_with('pyenv versions --bare --skip-aliases')


def test_check_python_version_installed_cmd_exception():

mock_run = mock.Mock()
mock_run.return_value = (1, "")

with mock.patch('timeout_tools.cli.run', mock_run):
python_version = '3.10.10'
with pytest.raises(PyEnvFailure) as e:
check_python_version_installed(python_version)
assert e.value.args[0]['message'] == 'Failed to run'

mock_run.assert_called_with('pyenv versions --bare --skip-aliases')


def test_check_python_version_installed_version_missing_exception():

mock_run = mock.Mock()
mock_run.return_value = (
0,
' 3.7.9\n 3.10.10\n 3.10.11'
)

with mock.patch('timeout_tools.cli.run', mock_run):
python_version = '0.0.0'
with pytest.raises(PyEnvPythonNotInstalled) as e:
check_python_version_installed(python_version)
assert e.value.args[0]['message'] == 'python version not installed'

mock_run.assert_called_with('pyenv versions --bare --skip-aliases')


def test_check_python_version_available_success():
mock_run = mock.Mock()
mock_run.return_value = (
0,
' 3.7.9\n 3.10.10\n 3.10.11'
)

with mock.patch('timeout_tools.cli.run', mock_run):
python_version = '3.10.10'
try:
check_python_version_available(python_version)
except Exception:
pytest.fail(f'Error checking {python_version}')

mock_run.assert_called_with('pyenv install --list')


def test_check_python_version_available_cmd_exception():

mock_run = mock.Mock()
mock_run.return_value = (1, "")

with mock.patch('timeout_tools.cli.run', mock_run):
python_version = '3.10.10'
with pytest.raises(PyEnvFailure) as e:
check_python_version_available(python_version)
assert e.value.args[0]['message'] == 'Failed to run'

mock_run.assert_called_with('pyenv install --list')


def test_check_python_version_available_version_missing_exception():

mock_run = mock.Mock()
mock_run.return_value = (
0,
' 3.7.9\n 3.10.10\n 3.10.11'
)

with mock.patch('timeout_tools.cli.run', mock_run):
python_version = '0.0.0'
with pytest.raises(PyEnvFailure) as e:
check_python_version_available(python_version)
assert e.value.args[0]['message'] == '''
Please update pyenv with latest versions of python by running:
cd ~/.pyenv/plugins/python-build/../.. && git pull && cd -
'''

mock_run.assert_called_with('pyenv install --list')


def test_install_python_version_success():

mock_run = mock.Mock()
mock_run.return_value = (0, "")

with mock.patch('timeout_tools.cli.run', mock_run):
python_version = '3.10.10'
try:
install_python_version(python_version)
except Exception:
pytest.fail(f'Error installing {python_version}')

mock_run.assert_called_with(f'pyenv install {python_version}')


def test_install_python_version_exception():

mock_run = mock.Mock()
mock_run.return_value = (1, "")

with mock.patch('timeout_tools.cli.run', mock_run):
python_version = '0.0.0'
with pytest.raises(BaseException) as e:
install_python_version(python_version)
assert str(e.value) == f'Failed to install python version {python_version}'

mock_run.assert_called_with(f'pyenv install {python_version}')