From 24e6e1470cdfdbbae8d4a07c8d78ab8e5b525500 Mon Sep 17 00:00:00 2001 From: Jonathan Burman Date: Fri, 31 Mar 2023 14:11:51 +0100 Subject: [PATCH 1/4] check / install python version using pyenv --- README.md | 2 +- timeout_tools/cli.py | 72 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5a98c07..ffb0f01 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/timeout_tools/cli.py b/timeout_tools/cli.py index 2af9450..14a1ddc 100644 --- a/timeout_tools/cli.py +++ b/timeout_tools/cli.py @@ -7,6 +7,10 @@ import sys +class PyEnvFailure(Exception): + pass + + def main(): parser = argparse.ArgumentParser( description='Timeout Tools', @@ -158,9 +162,13 @@ def python_setup_func(args): def python_setup(app, branch, python_version): + try: + check_python_version_installed(python_version) + 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: @@ -274,5 +282,67 @@ 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 + (status, result) = run('pyenv versions --bare --skip-aliases') + if status: + raise PyEnvFailure({"message": "failed to run\n"}) + for py_ver in result.replace(' ', '').split('\n'): + if py_ver == python_version: + py_ver_present = True + break + + if not py_ver_present: + 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) + else: + print(f"- Python `{python_version}` is installed ✅") + + +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 + (status, result) = run('pyenv install --list') + if status: + raise PyEnvFailure({"message": "failed to run\n"}) + for py_ver in result.replace(' ', '').split('\n'): + if py_ver == python_version: + py_ver_available = True + break + if not py_ver_available: + print(""" + Please update pyenv with latest versions of python by running: + cd ~/.pyenv/plugins/python-build/../.. && git pull && cd - + """) + raise PyEnvFailure({"message": "requires update\n"}) + else: + print(f"- Python `{python_version}` is available for installation ✅") + + +def install_python_version(python_version): + ### + # Use pyenv to install new version of python + ### + (status, _) = run('pyenv install {python_version}') + if status: + raise BaseException("Failed to install python version") + else: + print(f"- Python `{python_version}` installation successful ✅") + + if __name__ == '__main__': main() From 1b22d34a157970342799a5471d3694f7e1bd8562 Mon Sep 17 00:00:00 2001 From: Jonathan Burman Date: Fri, 31 Mar 2023 16:47:50 +0100 Subject: [PATCH 2/4] add some tests --- requirements.in | 1 + requirements.txt | 18 ++++- timeout_tools/cli.py | 20 +++--- timeout_tools/tests/__init__.py | 0 timeout_tools/tests/test_cli.py | 114 ++++++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 timeout_tools/tests/__init__.py create mode 100644 timeout_tools/tests/test_cli.py diff --git a/requirements.in b/requirements.in index b199528..b16c08d 100644 --- a/requirements.in +++ b/requirements.in @@ -2,3 +2,4 @@ argparse invoke pip-tools pre-commit +pytest diff --git a/requirements.txt b/requirements.txt index a824f16..faa45a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 @@ -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 diff --git a/timeout_tools/cli.py b/timeout_tools/cli.py index 14a1ddc..c8af2bd 100644 --- a/timeout_tools/cli.py +++ b/timeout_tools/cli.py @@ -289,7 +289,7 @@ def check_python_version_installed(python_version): py_ver_present = False (status, result) = run('pyenv versions --bare --skip-aliases') if status: - raise PyEnvFailure({"message": "failed to run\n"}) + raise PyEnvFailure({"message": "Failed to run"}) for py_ver in result.replace(' ', '').split('\n'): if py_ver == python_version: py_ver_present = True @@ -307,7 +307,7 @@ def check_python_version_installed(python_version): print(e.args[0]['message']) sys.exit(1) else: - print(f"- Python `{python_version}` is installed ✅") + print(f'- Python `{python_version}` is installed ✅') def check_python_version_available(python_version): @@ -318,30 +318,30 @@ def check_python_version_available(python_version): py_ver_available = False (status, result) = run('pyenv install --list') if status: - raise PyEnvFailure({"message": "failed to run\n"}) + 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(""" + print(''' Please update pyenv with latest versions of python by running: cd ~/.pyenv/plugins/python-build/../.. && git pull && cd - - """) - raise PyEnvFailure({"message": "requires update\n"}) + ''') + raise PyEnvFailure({"message": "Pyenv requires update"}) else: - print(f"- Python `{python_version}` is available for installation ✅") + print(f'- Python `{python_version}` is available for installation ✅') def install_python_version(python_version): ### # Use pyenv to install new version of python ### - (status, _) = run('pyenv install {python_version}') + (status, _) = run(f'pyenv install {python_version}') if status: - raise BaseException("Failed to install python version") + raise BaseException(f'Failed to install python version {python_version}') else: - print(f"- Python `{python_version}` installation successful ✅") + print(f'- Python `{python_version}` installation successful ✅') if __name__ == '__main__': diff --git a/timeout_tools/tests/__init__.py b/timeout_tools/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/timeout_tools/tests/test_cli.py b/timeout_tools/tests/test_cli.py new file mode 100644 index 0000000..f1ae5fb --- /dev/null +++ b/timeout_tools/tests/test_cli.py @@ -0,0 +1,114 @@ +from unittest import mock + +import pytest + +from ..cli import (PyEnvFailure, 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' + ) + + with mock.patch('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_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'] == 'Pyenv requires update' + + 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}') From e3017d8b072accfe754b7c44d88a70d6b4e95e27 Mon Sep 17 00:00:00 2001 From: Jonathan Burman Date: Fri, 31 Mar 2023 17:35:56 +0100 Subject: [PATCH 3/4] update logic --- timeout_tools/cli.py | 47 +++++++++++++++++++++------------ timeout_tools/tests/test_cli.py | 44 +++++++++++++++++++++++------- 2 files changed, 64 insertions(+), 27 deletions(-) diff --git a/timeout_tools/cli.py b/timeout_tools/cli.py index c8af2bd..509774a 100644 --- a/timeout_tools/cli.py +++ b/timeout_tools/cli.py @@ -11,6 +11,10 @@ class PyEnvFailure(Exception): pass +class PyEnvPythonNotInstalled(Exception): + pass + + def main(): parser = argparse.ArgumentParser( description='Timeout Tools', @@ -167,6 +171,17 @@ def python_setup(app, branch, python_version): 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) ret, out = run(f'pyenv virtualenv {python_version} {pyenv_name}') @@ -287,8 +302,10 @@ 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(' failed ❌') raise PyEnvFailure({"message": "Failed to run"}) for py_ver in result.replace(' ', '').split('\n'): if py_ver == python_version: @@ -296,18 +313,10 @@ def check_python_version_installed(python_version): break if not py_ver_present: - 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) + print(' failed ❌') + raise PyEnvPythonNotInstalled({"message": "python version not installed"}) else: - print(f'- Python `{python_version}` is installed ✅') + print(' succeeded ✅') def check_python_version_available(python_version): @@ -316,32 +325,36 @@ def check_python_version_available(python_version): # 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(' failed ❌') 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(''' + print(' failed ❌') + raise PyEnvFailure({"message": ''' Please update pyenv with latest versions of python by running: cd ~/.pyenv/plugins/python-build/../.. && git pull && cd - - ''') - raise PyEnvFailure({"message": "Pyenv requires update"}) + '''}) else: - print(f'- Python `{python_version}` is available for installation ✅') + print(' succeeded ✅') 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: - raise BaseException(f'Failed to install python version {python_version}') + print(' failed ❌') + raise PyEnvFailure(f'Failed to install python version {python_version}') else: - print(f'- Python `{python_version}` installation successful ✅') + print(' succeeded ✅') if __name__ == '__main__': diff --git a/timeout_tools/tests/test_cli.py b/timeout_tools/tests/test_cli.py index f1ae5fb..cbce7f7 100644 --- a/timeout_tools/tests/test_cli.py +++ b/timeout_tools/tests/test_cli.py @@ -2,8 +2,11 @@ import pytest -from ..cli import (PyEnvFailure, check_python_version_available, - check_python_version_installed, install_python_version) +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(): @@ -13,14 +16,15 @@ def test_check_python_version_installed_success(): ' 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_installed(python_version) - except Exception: - pytest.fail(f'Error checking version installed {python_version}') + timeout_tools.cli.run = mock_run - mock_run.assert_called_with('pyenv versions --bare --skip-aliases') + 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(): @@ -37,6 +41,23 @@ def test_check_python_version_installed_cmd_exception(): 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 = ( @@ -80,7 +101,10 @@ def test_check_python_version_available_version_missing_exception(): python_version = '0.0.0' with pytest.raises(PyEnvFailure) as e: check_python_version_available(python_version) - assert e.value.args[0]['message'] == 'Pyenv requires update' + 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') From 3e07efc2e045a6d3dd8141597903035453e288dc Mon Sep 17 00:00:00 2001 From: Jonathan Burman Date: Fri, 31 Mar 2023 17:47:35 +0100 Subject: [PATCH 4/4] remove unnecessary words --- timeout_tools/cli.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/timeout_tools/cli.py b/timeout_tools/cli.py index 509774a..681b958 100644 --- a/timeout_tools/cli.py +++ b/timeout_tools/cli.py @@ -305,7 +305,7 @@ def check_python_version_installed(python_version): print(f'- Checking Python `{python_version}` is installed', end='', flush=True) (status, result) = run('pyenv versions --bare --skip-aliases') if status: - print(' failed ❌') + print(' ❌') raise PyEnvFailure({"message": "Failed to run"}) for py_ver in result.replace(' ', '').split('\n'): if py_ver == python_version: @@ -313,10 +313,10 @@ def check_python_version_installed(python_version): break if not py_ver_present: - print(' failed ❌') + print(' ❌') raise PyEnvPythonNotInstalled({"message": "python version not installed"}) else: - print(' succeeded ✅') + print(' ✅') def check_python_version_available(python_version): @@ -328,20 +328,20 @@ def check_python_version_available(python_version): print(f'- Python checking `{python_version}` is available for installation', end='', flush=True) (status, result) = run('pyenv install --list') if status: - print(' failed ❌') + 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(' failed ❌') + print(' ❌') raise PyEnvFailure({"message": ''' Please update pyenv with latest versions of python by running: cd ~/.pyenv/plugins/python-build/../.. && git pull && cd - '''}) else: - print(' succeeded ✅') + print(' ✅') def install_python_version(python_version): @@ -351,10 +351,10 @@ def install_python_version(python_version): print('- Python installing `{python_version}`', end='', flush=True) (status, _) = run(f'pyenv install {python_version}') if status: - print(' failed ❌') + print(' ❌') raise PyEnvFailure(f'Failed to install python version {python_version}') else: - print(' succeeded ✅') + print(' ✅') if __name__ == '__main__':